В этот раз я хочу рассказать о том как работать с еще одним online-сервисом, а именно с сервисом online-переводов Google Translate.

Для работы с этим сервисом у Google есть свой программный интерфейс, а именно AJAX Language API for Translation And Detection. Именно его мы и будем использовать.

Для того чтобы осуществить online-перевод текста необходимо сделать http-запрос к сервису переводов, доступному по адресу:

http://ajax.googleapis.com/ajax/services/language/translate

Параметры, которые необходимо передать сервису:

  • v=1.0 – версия сервиса
  • q=<sometext> – URL-encoded текст для перевода.
  • langpair=<source_language>%7C<result_language> – пара названий языков: исходного и результирующего


Например для перевода текста “Hello World” с английского на русский строка запроса будет выглядеть так:

http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&q=Hello%20World&langpair=en%7Cru

Ну вот, небольшой теоретический экскурс закончен, можно приступать к кодингу:
wxGoogleTranslate.h

#ifndef _WX_GOOGLE_TRANSLATE_H
#define _WX_GOOGLE_TRANSLATE_H

#include <wx/wx.h>

class wxGoogleTranslate
{
	/// \brief Language Info
	struct wxGoogleTranslateLanguageInfo
	{
		/// \brief Full language name
		wxString languageName;
		/// \brief Language code
		wxString languageCode;
	};

	/// \brief Stores information about all supported languages
	static wxGoogleTranslateLanguageInfo m_LanguageInfoArray[];
	/**
	\brief Parses JSON response from Google Translate service
	\param response contains JSON response from Google Translate service
	\param translatedText contains translated text if parsing of JSON response was correct
	\param translateionDetails contains additional information from Google Translate service
		   usually it is error message
    \param translationStatus contains parsing status (error code)
	\return true if translation and parsing was successfull, otherwise false
	*/
	static bool ParseJSONResponse(const wxString & response,
		wxString & translatedText,
		wxString & translateionDetails,
		int & translationStatus);
public:
	/// \brief Returns the list of all suported languages
	/// \param result array for storing language names
	static void GetLanguages(wxArrayString & result);
	/**
	\brief Returns language code by language name
	\param languageName language name
	\return language code on success or empty string on error
	*/
	static wxString GetLanguageCode(const wxString & languageName);
	/**
	\brief Translates given text with Google Translate service
	\param source string to translate
	\param result translation result
	\param sourceLanguageCode source language code
	\param resultLanguageCode destination language code
	\param translationDetails error message
	\param errorCode error code
	\return true on success, otherwise false
	*/
	static bool Translate(const wxString & source, 
		wxString & result, 
		const wxString & sourceLanguageCode, 
		const wxString & resultLanguageCode,
		wxString & translationDetails,
		int & errorCode);
};

#endif

Итак, как я уже писал выше, для того чтобы сформировать корректный URL запроса нам необходимо чтобы текст для перевода был URL-encoded. Для этоко пишем функцию URLEncode()

wxString HexFromInt(const int &value)
{
	wxString szHexHolder;

	if( value < 16)
		szHexHolder.Printf(wxT("0%x"), value );
	else
		szHexHolder.Printf(wxT("%x"), value );

	return szHexHolder.MakeUpper();
}

wxString URLEncode(const wxString &value)
{
	wxString szToReturn = wxT("");
	unsigned int nPos = 0;

	while( value.length() > nPos ) 
	{
		wxChar cChar = value.GetChar(nPos);

		if( (cChar >= wxT('0') && cChar <= wxT('9')) || 
			(cChar >= wxT('a') && cChar <= wxT('z')) || 
			(cChar >= wxT('A') && cChar <= wxT('Z')) || 
			(cChar == wxT('-')) || (cChar == wxT('@')) || 
			(cChar == wxT('*')) || (cChar == wxT('_')) )
		{
			szToReturn.Append( cChar );
		}
		else
		{
			switch( cChar )
			{
			case wxT(' '):  szToReturn.Append(wxT('+')); break;
			case wxT('\n'): szToReturn.Append(wxT("%0D%0A")); break;
			default:
				{
					szToReturn.Append(wxT("%"));
					szToReturn += HexFromInt( cChar );
				}
			}
		}
		nPos++;
	}
	return szToReturn;
}
&#91;/sourcecode&#93;
На самом деле большая часть приведенного выше кода позаимствована из библиотеки wxHTTPEngine, но, к сожалению, исходная реализация не работала с русскими символами и пришлось ее немного доработать напильником.
Так, с URL-encoding'ом разобрались, но это еще не все. Перед тем как формировать URL-encoded строку нам необходимо текст преобразовать в UTF-8. Для этого можно использовать метод <strong>wxString::ToUTF8()</strong>.
Список поддерживаемых сервисом языков можно найти на странице описания Google Language API. У нас в программе для этого будет массив структур, каждая из которых будет содержать полное название языка и его код (который можно будет передать в параметр <strong>languagepair</strong>):

wxGoogleTranslate::wxGoogleTranslateLanguageInfo 
	wxGoogleTranslate::m_LanguageInfoArray[] =
{
	{wxT("AFRIKAANS"),				wxT("af")},
	{wxT("ALBANIAN"),				wxT("sq")},
	{wxT("AMHARIC"),				wxT("am")},
	{wxT("ARABIC"),					wxT("ar")},
	{wxT("ARMENIAN"),				wxT("hy")},
	{wxT("AZERBAIJANI"),			wxT("az")},
	{wxT("BASQUE"),					wxT("eu")},
	{wxT("BELARUSIAN"),				wxT("be")},
	{wxT("BENGALI"),				wxT("bn")},
	{wxT("BIHARI"),					wxT("bh")},
	{wxT("BULGARIAN"),				wxT("bg")},
	{wxT("BURMESE"),				wxT("my")},
	{wxT("CATALAN"),				wxT("ca")},
	{wxT("CHEROKEE"),				wxT("chr")},
	{wxT("CHINESE"),				wxT("zh")},
	{wxT("CHINESE_SIMPLIFIED"),		wxT("zh-CN")},
	{wxT("CHINESE_TRADITIONAL"),	wxT("zh-TW")},
	{wxT("CROATIAN"),				wxT("hr")},
	{wxT("CZECH"),					wxT("cs")},
	{wxT("DANISH"),					wxT("da")},
	{wxT("DHIVEHI"),				wxT("dv")},
	{wxT("DUTCH"),					wxT("nl")},
	{wxT("ENGLISH"),				wxT("en")},
	{wxT("ESPERANTO"),				wxT("eo")},
	{wxT("ESTONIAN"),				wxT("et")},
	{wxT("FILIPINO"),				wxT("tl")},
	{wxT("FINNISH"),				wxT("fi")},
	{wxT("FRENCH"),					wxT("fr")},
	{wxT("GALICIAN"),				wxT("gl")},
	{wxT("GEORGIAN"),				wxT("ka")},
	{wxT("GERMAN"),					wxT("de")},
	{wxT("GREEK"),					wxT("el")},
	{wxT("GUARANI"),				wxT("gn")},
	{wxT("GUJARATI"),				wxT("gu")},
	{wxT("HEBREW"),					wxT("iw")},
	{wxT("HINDI"),					wxT("hi")},
	{wxT("HUNGARIAN"),				wxT("hu")},
	{wxT("ICELANDIC"),				wxT("is")},
	{wxT("INDONESIAN"),				wxT("id")},
	{wxT("INUKTITUT"),				wxT("iu")},
	{wxT("ITALIAN"),				wxT("it")},
	{wxT("JAPANESE"),				wxT("ja")},
	{wxT("KANNADA"),				wxT("kn")},
	{wxT("KAZAKH"),					wxT("kk")},
	{wxT("KHMER"),					wxT("km")},
	{wxT("KOREAN"),					wxT("ko")},
	{wxT("KURDISH"),				wxT("ku")},
	{wxT("KYRGYZ"),					wxT("ky")},
	{wxT("LAOTHIAN"),				wxT("lo")},
	{wxT("LATVIAN"),				wxT("lv")},
	{wxT("LITHUANIAN"),				wxT("lt")},
	{wxT("MACEDONIAN"),				wxT("mk")},
	{wxT("MALAY"),					wxT("ms")},
	{wxT("MALAYALAM"),				wxT("ml")},
	{wxT("MALTESE"),				wxT("mt")},
	{wxT("MARATHI"),				wxT("mr")},
	{wxT("MONGOLIAN"),				wxT("mn")},
	{wxT("NEPALI"),					wxT("ne")},
	{wxT("NORWEGIAN"),				wxT("no")},
	{wxT("ORIYA"),					wxT("or")},
	{wxT("PASHTO"),					wxT("ps")},
	{wxT("PERSIAN"),				wxT("fa")},
	{wxT("POLISH"),					wxT("pl")},
	{wxT("PORTUGUESE"),				wxT("pt-PT")},
	{wxT("PUNJABI"),				wxT("pa")},
	{wxT("ROMANIAN"),				wxT("ro")},
	{wxT("RUSSIAN"),				wxT("ru")},
	{wxT("SANSKRIT"),				wxT("sa")},
	{wxT("SERBIAN"),				wxT("sr")},
	{wxT("SINDHI"),					wxT("sd")},
	{wxT("SINHALESE"),				wxT("si")},
	{wxT("SLOVAK"),					wxT("sk")},
	{wxT("SLOVENIAN"),				wxT("sl")},
	{wxT("SPANISH"),				wxT("es")},
	{wxT("SWAHILI"),				wxT("sw")},
	{wxT("SWEDISH"),				wxT("sv")},
	{wxT("TAJIK"),					wxT("tg")},
	{wxT("TAMIL"),					wxT("ta")},
	{wxT("TAGALOG"),				wxT("tl")},
	{wxT("TELUGU"),					wxT("te")},
	{wxT("THAI"),					wxT("th")},
	{wxT("TIBETAN"),				wxT("bo")},
	{wxT("TURKISH"),				wxT("tr")},
	{wxT("UKRAINIAN"),				wxT("uk")},
	{wxT("URDU"),					wxT("ur")},
	{wxT("UZBEK"),					wxT("uz")},
	{wxT("UIGHUR"),					wxT("ug")},
	{wxT("VIETNAMESE"),				wxT("vi")},
	{wxT("UNKNOWN"),				wxEmptyString}
};

Для получения списка языков будет использоваться метод wxGoogleTranslate::GetLanguages()

void wxGoogleTranslate::GetLanguages(wxArrayString & result)
{
	result.Clear();
	int count = sizeof(m_LanguageInfoArray) / 
		sizeof(wxGoogleTranslateLanguageInfo);
	for(int i = 0; i < count; i++)
	{
		result.Add(m_LanguageInfoArray&#91;i&#93;.languageName);
	}
}
&#91;/sourcecode&#93;
Для получения кода языка предназначен метод <strong>wxGoogleTranslate::GetLanguageCode()</strong>:

wxString wxGoogleTranslate::GetLanguageCode(const wxString & languageName)
{
	int count = sizeof(m_LanguageInfoArray) / 
		sizeof(wxGoogleTranslateLanguageInfo);
	for(int i = 0; i < count; i++)
	{
		if(m_LanguageInfoArray&#91;i&#93;.languageName.Lower().IsSameAs(
			languageName.Lower()))
		{
			return m_LanguageInfoArray&#91;i&#93;.languageCode;
		}
	}
	return wxEmptyString;
}
&#91;/sourcecode&#93;
Теперь можно реализовывать непосредственно метод для перевода текста:
&#91;sourcecode language="cpp"&#93;
bool wxGoogleTranslate::Translate(const wxString & source, 
		wxString & result, 
		const wxString & sourceLanguageCode, 
		const wxString & resultLanguageCode,
		wxString & translationDetails,
		int & errorCode)
{
	do
	{
		wxString urlEncodedSource = 
			URLEncode(wxString::FromAscii(source.ToUTF8()));
		wxString srcURL = wxString::Format(
			wxT("%s?v=1.0&q=%s&langpair=%s%%7C%s"),
			wxT("http://ajax.googleapis.com/ajax/services/language/translate"),
			urlEncodedSource.GetData(),
			sourceLanguageCode.GetData(),
			resultLanguageCode.GetData());
		wxURL url = srcURL;
		if(url.GetError() != wxURL_NOERR) break;
		wxInputStream * in = url.GetInputStream();
		if(!in) break;
		wxString response;
		wxStringOutputStream out(&response);
		in->Read(out);
		wxDELETE(in);
		if(response.IsEmpty()) break;
		if(!wxGoogleTranslate::ParseJSONResponse(response, result, 
			translationDetails, errorCode)) break;
		return true;
	}
	while(false);
	return false;
}

Результат запроса приходит в формате JSON. В стандартной поставке wxWidgets библиотеки для парсинга JSON нет, зато есть сторонняя библиотека wxJSON, которую мы и будем использовать.
Формат ответа от сервиса Google Translate:

{"responseData": {"translatedText":"sometext"}, "responseDetails": "sometext", "responseStatus": 200}

Где

  • translatedText – переведенный текст
  • responseDetails – дополнительная информация от сервиса (сообщение об ошибке)
  • responseStatus – код ошибки

Метод для парсинга ответа сервиса:

bool wxGoogleTranslate::ParseJSONResponse(const wxString & response,
		wxString & translatedText,
		wxString & translateionDetails,
		int & translationStatus)
{
	do
	{
		wxJSONValue  root;
		wxJSONReader reader;
		if(reader.Parse(response, &root) > 0) break;
		translatedText = 
			root[wxT("responseData")][wxT("translatedText")].AsString();
		translateionDetails = 
			root[wxT("responseDetails")].AsString();
		translationStatus = 
			root[wxT("responseStatus")].AsInt();
		return true;
	}
	while(false);
	return false;
}

Ну вот, работу над классом переводчика закончили. Теперь посмотрим как его использовать:

void wxGoogleTranslateClientMainFrame::OnTRANSLATEClick( wxCommandEvent& event )
{
	do
	{
		wxString result;
		wxString details;
		int errorCode(0);
		int sourceLangselection = m_SourceLanguageChoice->GetSelection();
		int resultLangselection = m_ResultLanguageChoice->GetSelection();
		if((sourceLangselection < 0) || (resultLangselection < 0)) break;
		if(wxGoogleTranslate::Translate(m_SourceTextCtrl->GetValue(), 
			result, 
			wxGoogleTranslate::GetLanguageCode(
				m_SourceLanguageChoice->GetString(sourceLangselection)), 
			wxGoogleTranslate::GetLanguageCode(
			m_ResultLanguageChoice->GetString(resultLangselection)), 
				details, errorCode))
		{
			m_ResultTextCtrl->SetValue(result);
		}
		else
		{
			wxMessageBox(wxString::Format(_("Erorr occured: %s"), details.GetData()));
		}
	}
	while(false);
}

Хотелось бы отметить вот что: для загрузки ответа от сервиса используется класс wxURL. Используется синхронно, поэтому работа вызывающего потока приложения блокируется до тех пор пока загрузка не будет завершена.
Использование же класса переводчика заключается в вызове метода Translate(), никаких дополнительных действий по инициализации или деинициализации не требуется.

Исходный код библиотеки wxGoogleTranslate, а также исходный код и исполняемые файлы примера для Windows NT/2000/XP/Vista и для Windows Mobile 2003/5/6 можно загрузить здесь.

Previous ArticleNext Article
Технический директор IT-Dimension, компании-разработчика кросс-платформенного программного обеспечения

This post has 4 Comments

4
  1. ну все пишешь, пишешь, работаешь аки пчела 🙂
    Вот и я говорю, молодец.

Leave a Reply

Your email address will not be published. Required fields are marked *

О.

Отображаем анимированный GIF под Windows Mobile

Как-то печально обстоят дела с отображением анимации на устройствах под управлением Windows Mobile. Искал решение на .NET Compact Framework, нашел на Stack Overflow. Там предлагают писать собственный контрол, который будет делить изображение на кадры и отображать их с заданной периодичностью. Там в ответах ссылка на статью в MSDN. Собственно, везде это решение рекомендуют, но мне оно как-то не очень понравилось ввиду того, что позволяет отображать только специально подготовленное изображение, что во многих случаях очень неудобно.

А вот для тех, кто пишет ПО для Windows Mobile на C++ с wxWinCE эта проблема решается намного проще, с помощью wxAnimationCtrl. Вобще никаких лишних телодвижений делать не надо:

void wxAnimateMobileMainFrame::OnOPENClick( wxCommandEvent& event )
{
	wxFileDialog * dlg = new wxFileDialog(this, wxFileSelectorPromptStr, wxEmptyString,
		wxEmptyString, _("GIF Files (*.gif)|*.gif"));
	if(dlg->ShowModal() == wxID_OK)
	{
		m_AnimationCtrl->LoadFile(dlg->GetPath());
		m_AnimationCtrl->Play();
	}
	dlg->Destroy();
}

Из полезных возможностей:

  • Загрузка GIF-изображений с любым количеством кадров
  • Поддержка различных интервалов задержки для различных кадров
  • Циклическое воспроизведение (прописывается в самом GIF-файле)

Оказывается, все-таки, для некоторых задач C++ пользовать удобнее (хотя все равно для меня остается загадкой почему в .NET CF этого функционала нет).

Исходный код примера можно загрузить здесь.

Р.

Работаем с журналом звонков в Windows Mobile

В этот раз я расскажу о том, как работать с журналом звонков на С++ в Windows Mobile.

Для доступа к журналу звонков Windows Mobile имеет такую вещь, как Phone API.

Непосредственно для наших целей необходима всего небольшая часть функций, доступных в рамках Phone API, а именно:

  • PhoneOpenCallLog – открывает журнал звонков для чтения и возвращает хэндл, использующийся впоследствии для доступа к записям журнала.
  • PhoneGetCallLogEntry – получает данные о записи журнала звонков и заполняет структуру CALLLOGENTRY этими данными
  • PhoneCloseCallLog – закрывает хэндл журнала звонков.

Структура CALLLOGENTRY после успешного завершения работы функции PhoneGetCallLogEntryбудет содержать такую информацию:

  • Телефонный номер, на который был совершен звонок (если этот звонок исходящий) или с которого был совершен звонок (если звонок входящий)
  • Имя записи в адресной книге, соответствующей номеру
  • Тип номера (домашний/рабочий/мобильный), берется также из адресной книги
  • Дата и время начала звонка
  • Дата и время окончания звонка
  • Тип звонка (входящий, исходящий, пропущенный)
  • Флаг, указывающий на то, произошло ли соединение
  • Флаг, указывающий на то, был ли звонок завершен нормально или произошел обрыв
  • Флаг, указывающий на то, был ли использован роуминг.
  • Тип контакта (доступен/недоступен/заблокирован)
  • Текст заметки

Итак, давайте посмотрим, как это все работает. Пример для этой статьи написан с использованием wxWinCE. Главная форма приложения содержит list control со списком звонков, в котором указано имя контакта и с какого номера был произведен звонок.

Windows Mobile - Get Call Log Entries - C++ - Main Screen

При запуске приложения выполняется получения данных из журнала звонков:

void wxCallLogSampleMainFrame::FillCallLogList()
{
	m_CallLogListView->Freeze();
	do 
	{
		m_CallLog.Clear();
		HANDLE callLogHandle = INVALID_HANDLE_VALUE;
		HRESULT hr = PhoneOpenCallLog(&callLogHandle);
		if(FAILED(hr)) break;
		CALLLOGENTRY entry;
		entry.cbSize = sizeof(CALLLOGENTRY);
		do 
		{
			hr = PhoneGetCallLogEntry(callLogHandle, &entry);
			if(hr == S_OK)
			{
				wxCallLogEntry result;
				CreateCallLogEntry(entry, result);
				m_CallLog.Add(result);
			}
		} while (hr != S_FALSE);
		PhoneCloseCallLog(callLogHandle);
		m_CallLogListView->SetCallLogArray(&m_CallLog);
		m_CallLogListView->SetItemCount(m_CallLog.Count());
	} while (false);
	m_CallLogListView->Thaw();
}

Для хранения информации о звонке в виде, доступном для использования библиотекой wxWidgets мне пришлось написать собственный класс:

wxCallLogEntry.h

#ifndef _WX_CALL_LOG_ENTRY_H
#define _WX_CALL_LOG_ENTRY_H

#include <wx/wx.h>
#include <wx/dynarray.h>

enum wxCallType
{
	wxCALL_INCOMING,
	wxCALL_OUTGOING,
	wxCALL_MISSED
};

enum wxCallerIDType
{
	wxCALLER_ID_AVAILABLE,
	wxCALLER_ID_UNAVAILABLE,
	wxCALLER_ID_BLOCKED
};

class wxCallLogEntry : public wxObject
{
	DECLARE_DYNAMIC_CLASS(wxCallLogEntry)
public:
	wxCallLogEntry() {}
	wxCallLogEntry(const wxString & phoneNumber,
		const wxDateTime & startTime,
		const wxDateTime & endTime,
		const wxString & callName,
		const wxString & callNameType,
		wxCallType callType,
		wxCallerIDType callerIDType,
		bool wasConnected,
		bool wasEnded,
		bool roamingEnabled,
		const wxString & note = wxEmptyString);

	const wxString & GetPhoneNumber();
	void SetPhoneNumber(const wxString & value);

	const wxDateTime & GetStartTime();
	void SetStartTime(const wxDateTime & value);

	const wxDateTime & GetEndTime();
	void SetEndTime(const wxDateTime & value);

	const wxString & GetCallName();
	void SetCallName(const wxString & value);

	const wxString & GetCallNameType();
	void SetCallNameType(const wxString & value);
	
	wxCallType GetCallType();
	void SetCallType(wxCallType value);

	wxCallerIDType GetCallerIDType();
	void SetCallerIDType(wxCallerIDType value);

	bool GetConnected();
	void SetConnected(bool value);

	bool GetEnded();
	void SetEnded(bool value);

	bool GetRoamingEnabled();
	void SetRoamingEnabled(bool value);

	const wxString & GetNote();
	void SetNote(const wxString & value);
private:
	wxString m_PhoneNumber;
	wxDateTime m_StartTime;
	wxDateTime m_EndTime;
	wxString m_CallName;
	wxString m_CallNameType;
	wxCallType m_CallType;
	wxCallerIDType m_CallerIDType;
	bool m_Connected;
	bool m_Ended;
	bool m_RoamingEnabled;
	wxString m_Note;
};

WX_DECLARE_OBJARRAY(wxCallLogEntry, wxCallLogArray);

#endif

Код для преобразования данных из CALLLOGENTRY в wxCallLogEntry

void wxCallLogSampleMainFrame::CreateCallLogEntry(
	CALLLOGENTRY & entry, wxCallLogEntry & result)
{
	wxCallerIDType idType = wxCALLER_ID_UNAVAILABLE;
	switch(entry.cidt)
	{
	case CALLERIDTYPE_UNAVAILABLE:
		idType = wxCALLER_ID_UNAVAILABLE;
		break;
	case CALLERIDTYPE_BLOCKED:
		idType = wxCALLER_ID_BLOCKED;
		break;
	case CALLERIDTYPE_AVAILABLE:
		idType = wxCALLER_ID_AVAILABLE;
		break;
	default:
		break;
	};
	result.SetCallerIDType(idType);

	wxCallType callType = wxCALL_INCOMING;
	switch(entry.iom)
	{
	case IOM_INCOMING:
		callType = wxCALL_INCOMING;
		break;
	case IOM_OUTGOING:
		callType = wxCALL_OUTGOING;
		break;
	case IOM_MISSED:
		callType = wxCALL_MISSED;
		break;
	default:
		break;
	};
	result.SetCallType(callType);

	SYSTEMTIME systemTime;

	FileTimeToSystemTime(&entry.ftStartTime, &systemTime);
	wxDateTime startTime(TimeFromSystemTime(&systemTime));
	result.SetStartTime(startTime);
	
	FileTimeToSystemTime(&entry.ftEndTime, &systemTime);
	wxDateTime endTime(TimeFromSystemTime(&systemTime));
	result.SetEndTime(endTime);
	
	wxString number = ((entry.pszNumber != NULL) ? 
		wxString::Format(wxT("%s"),entry.pszNumber) :
	wxEmptyString);
	result.SetPhoneNumber(number);
	
	wxString name = ((entry.pszName != NULL) ? 
		wxString::Format(wxT("%s"),entry.pszName) :
	wxEmptyString);
	result.SetCallName(name);
	
	wxString nameType = ((entry.pszNameType != NULL) ? 
		wxString::Format(wxT("%s"),entry.pszNameType) :
	wxEmptyString);
	result.SetCallNameType(nameType);

	wxString note = ((entry.pszNote != NULL) ? 
		wxString::Format(wxT("%s"),entry.pszNote) :
	wxEmptyString);
	result.SetNote(note);

	result.SetConnected(entry.fConnected != 0);
	result.SetEnded(entry.fEnded != 0);
	result.SetRoamingEnabled(entry.fRoam != 0);
}

Структура CALLOGENTRY хранит дату и время начала и окончания звонка в виде структуры FILETIME. Для преобразования FILETIME в wxDateTime сначала необходимо выполнить преобразование в SYSTEMTIME с помощью функции FileTimeToSystemTime(), а затем в time_t:

time_t TimeFromSystemTime(const SYSTEMTIME * pTime)
{
	tm _tm;
	memset(&_tm, 0, sizeof(tm));

	_tm.tm_year = (pTime->wYear-1900);
	_tm.tm_mon = pTime->wMonth - 1;
	_tm.tm_mday = pTime->wDay;

	_tm.tm_hour = pTime->wHour;
	_tm.tm_min = pTime->wMinute;
	_tm.tm_sec = pTime->wSecond;

	return mktime(&_tm);
}

Для отображения данных о звонках я решил использовать виртуальный list control. Почему виртуальный, а не обычный? Потому что добавление большого количества элементов в список происходит довольно долго и пользователь может несколько секунд ждать того момента, когда все данные будут добавлены и приложение начнет как-то реагировать на его действия. При использовании виртуального списка вызывается метод SetItemCount(), который позволяет правильно вычислить размеры скроллеров. Это происходит довльно быстро. Затем виртуальный list control отображает только видимые элементы списка.

Для создания виртуального list control’а необходимо создать класс производный от wxListCtrl или wxListView и переопределить в нем методы:

virtual wxString OnGetItemText(long item, long column) const;
virtual int OnGetItemImage(long item) const;
virtual wxListItemAttr * OnGetItemAttr(long item) const;
  • OnGetItemText – используется для получения текста ячейки списка по указанным номеру строки и номеру колонки
  • OnGetItemImage – используется для получения индекса картинки элемента списка по указанному номеру строки
  • OnGetItemAttr – используется для получения атрибутов элемента списка (цвет текста, цвет фона, шрифт) по указанному номеру строки

И вот, собственно, код этих трех методов

wxString wxCallLogListView::OnGetItemText(long item, long column) const
{
	do 
	{
		if(!m_CallLogArray || 
			item >= (long)m_CallLogArray->Count() || 
			item >= GetItemCount()) break;
		switch(column)
		{
		case 2:
			return m_CallLogArray->Item(item).GetPhoneNumber();
		case 0:
			return m_CallLogArray->Item(item).GetCallName();
		case 1:
			return m_CallLogArray->Item(item).GetCallNameType();
		default:
			break;
		}
	} while (false);
	return wxEmptyString;
}

int wxCallLogListView::OnGetItemImage(long item) const
{
	do 
	{
		if(!m_CallLogArray || 
			item >= (long)m_CallLogArray->Count() || 
			item >= GetItemCount()) break;
		switch(m_CallLogArray->Item(item).GetCallType())
		{
		case wxCALL_INCOMING:
			return 1;
		case wxCALL_OUTGOING:
			return 2;
		case wxCALL_MISSED:
			return 3;
		}
	} while (false);
	return -1;
}

wxListItemAttr * wxCallLogListView::OnGetItemAttr(long item) const
{
	do 
	{
		if(!m_CallLogArray || 
			item >= (long)m_CallLogArray->Count() || 
			item >= GetItemCount()) break;
		return new wxListItemAttr(*wxBLACK, 
			(item%2) ? *wxWHITE : *wxLIGHT_GREY,
			GetFont());
	} while (false);
	return NULL;
}

Для того, чтобы в списке отображались иконки необходимо сначала создать объект wxImageList, добавить в него иконки и затем ассоциировать с list control’ом с помощью метода SetImageList()

static bool imageListCreated = false;
if(!imageListCreated)
{
	wxCallLogListView::ItemImageList.Create(16, 15);
	wxBitmap empty(16, 15);
	empty.SetMask(new wxMask(empty, *wxBLACK));
	wxCallLogListView::ItemImageList.Add(empty);
	wxCallLogListView::ItemImageList.Add(wxBitmap(forward_xpm));
	wxCallLogListView::ItemImageList.Add(wxBitmap(back_xpm));
	wxCallLogListView::ItemImageList.Add(wxBitmap(delete_xpm));
	imageListCreated = true;
}

Сейчас наше тестовое приложение отображает информацию о звонках не полностью. Для получения полной информации о звонке создадим диалоговое окно и при выделении элемента списка будем заполнять его данными и отображать.

BEGIN_EVENT_TABLE( wxCallLogSampleMainFrame, wxFrame )
    EVT_LIST_ITEM_ACTIVATED( ID_CallLogListView, wxCallLogSampleMainFrame::OnCallLogListViewItemActivated )
END_EVENT_TABLE()
void wxCallLogSampleMainFrame::OnCallLogListViewItemActivated( wxListEvent& event )
{
	CallInfoDialog * dlg = new CallInfoDialog(this);
	dlg->SetCallLogEntry(&m_CallLog[event.GetSelection()]);
	dlg->ShowModal();
	dlg->Destroy();
}

void CallInfoDialog::SetCallLogEntry(wxCallLogEntry * entry)
{
	if(!entry) return;
	m_PhoneNumberTextCtrl->SetValue(entry->GetPhoneNumber());
	m_CallNameTextCtrl->SetValue(entry->GetCallName());
	m_CallNameTypeTextCtrl->SetValue(entry->GetCallNameType());
	m_StartTextCtrl->SetValue(entry->GetStartTime().Format());
	m_EndTextCtrl->SetValue(entry->GetEndTime().Format());
	m_CallTypeRadio->SetSelection(entry->GetCallType());
	m_CallerTypeRadio->SetSelection(entry->GetCallerIDType());
	m_ConnectedCheck->SetValue(entry->GetConnected());
	m_EndedCheck->SetValue(entry->GetEnded());
	m_RoamingCheck->SetValue(entry->GetRoamingEnabled());
	m_NotesTextCtrl->SetValue(entry->GetNote());
}

В результате у нас должно получиться что-то подобное:
Windows Mobile - Show Call Log Entries - C++ - Show Details

Скачать исходный код к статье + проект для Windows Mobile 5.