В этот раз я расскажу о том, как работать с журналом звонков на С++ в 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.

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

Leave a Reply

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

Н.

Настраиваем средства разработки для Windows Mobile (C++/wxWinCE) в Linux

Благодаря стараниям Станислава Блинова у нас появилась возможность разрабатывать ПО для Windows Mobile в Linux, используя компилятор cegcc и библиотеку wxWidgets. О том как настроить средства разработки для Windows Mobile под Linux рассказано в ниже следующей статье:

Захотелось мне тут выяснить, насколько “умерла” бесплатная разработка софта под WinCE. Великий Гугель вывел меня на штуку под названием cegcc – кросс-компилятор, позволяющий собирать WinCE приложения под Линуксом. После возвращения из осадка, в который я выпал, узрев данный инструмент, я решил его проверить на практике.

Проверка прошла успешно – приложения действительно собираются и работают. Правда, местами приходится кое-что править… И тогда захотелось экзотики, а именно – собрать с помощью cegcc библиотеку wxWinCE – порт wxWidgets для WinCE. Сделать это оказалось, мягко говоря, непросто, но все же реально.

В процессе поиска информации по данному вопросу я наткнулся на вот этот пост: http://article.gmane.org/gmane.comp.gnu.cegcc.devel/827. Erik van Pienbroek подкинул отличную шпаргалку. Его патчи действительно встали на ревизию 49149 и после небольшой доработки напильником она собралась. Правда, работала она, мягко говоря, плохо. Рисовала окна как попало, меню размещала вверху окна, само окно, кстати, зачем-то ополовинивала, ну и т.д. Но это уже было что-то.
Потом я умудрился адаптировать (напильник был большой) эти патчи к релизу 2.8.9, т.е. так же собрать сам релиз и получить такую же библиотеку с весьма странным поведеением. И уже только потом стал разбираться, что же не так. Оказалось все достаточно просто – всего лишь правильно указать версию и тип системы… Ну да хватит, как говорится, от слов – к делу!

Далее я опишу способ, как собрать wxWinCE-2.8.9 для PocketPC. Для начала определимся с исходными данными. Система, на которой все это производилось – Debian Lenny:

Linux 2.6.26-1-686 #1 SMP Thu Oct 9 15:18:09 UTC 2008 i686 GNU/Linux

Перечень вещей, которые понадобятся:

  • bison, yacc
  • mingw32ce
  • Bakefile
  • wxWidgets-2.8.9
  • Патч для wxWidgets
  • Файл autogen.mk

Итак, приступим…
Марлезонский балет, часть первая. Установка mingw32-ce.

В принципе, данный процесс достаточно хорошо документирован здесь. Но раз уж мы за это беремся, несколько слов скажу.

  • Если у вас вдруг нету, поставьте bison и yacc (для Debian ставятся через apt-get).
  • Нужно взять с svn текущую версию cegcc:
$ svn co https://cegcc.svn.sourceforge.net/svnroot/cegcc/trunk/cegcc cegcc

Когда скачается, в текущей директории появится директория cegcc.

$ cd cegcc/src
$ mkdir build-mingw32ce

Собирать в корне исходников скрипт не даст, сам попросит создать директорию.

$ cd build-mingw32ce
$ sh ../build-mingw32ce.sh

Все, пошла сборка (и установка). По умолчанию компилятор встанет в /opt/mingw32ce. Этот путь можно поменять с помощью опции --prefix к скрипту (например ../build-mingw32ce.sh --prefix=/my/super/puper/dir/mgwce)

После установки компилятора добавьте /opt/mingw32ce/bin (соответственно, если вы не меняли префикс) в PATH. На Debian это можно сделать в /etc/profile.

С компилятором все, переходим ко второй части балета.

Сборка wxWinCE

  1. Нам понадобится Bakefile. Если его нет (или если он древнее, чем 2.3.0) – взять можно на www.bakefile.org. Эта утилита будет новые Makefile’ы создавать.
  2. Теперь надо скачать архив wxWidgets-2.8.9 и куда-нибудь его распаковать. Все остальные действия надо производить, находясь в директории wxWidgets-2.8.9. (Здесь и далее на скриншотах зеленым выделены моменты, на которые стоит обратить внимание).
  3. tar xzf wxWidgets-2.8.9.tar.gz
  4. Прилагаемый файл autogen.mk помещается в директорию build. Затем можно применить патч.
  5. cp /home/capricorn/work/articles/mingw32ce-wxWinCE/autogen.mk build
    patch -p0 /home/capricorn/work/articles/mingw32ce-wxWinCE/wxWinCE.patch-2
    
  6. Немного поправим WinCE SDK, а именно – файл /opt/mingw32ce/arm-mingw32ce/include/aygshell.h. Нужно добавить в него следующие строки:
    #define COMCTL32_VERSION 0x020e
    #define SHA_INPUTDIALOG  0x00000001
    #define GN_CONTEXTMENU   1000
    #define SHRG_RETURNCMD   0x00000001
    

    Эти определения присутствуют в “родном” aygshell.h из Windows Mobile 5.0 SDK. При желании можете посмотреть, где точно они располагаются в этом файле, и добавить их в те же места.

  7. Этот шаг необходимо выполнить, если Bakefile у вас не 2.3.0 (здесь я отметил файлы, которые должны быть заменены). Путь /usr/local/share/aclocal, естественно, действителен для Debian. На другой системе может быть другим.

    cp /usr/local/share/aclocal/bakefile* build/aclocal
    
  8. Теперь нужно создать новые билд-скрипты

    ./autogen.sh && make -f build/autogen.mk
    

    Процесс займет некоторое время, в результате должно получиться примерно следующее:

  9. Выполнение этого шага сконфигурирует среду на сборку статической монолитной библиотеки с поддержкой юникода. Опция --prefix нужна, чтобы указать, куда впоследствии будет инсталлироваться wxWinCE. Поскольку mingw32ce у меня лег в /opt, wxWinCE я решил положить рядышком, так что я выбрал --prefix=/opt/wxWinCE-2.8.9:

    По завершении конфигурации вы должны получить такую картину:
  10. Теперь остается только выполнить $ make И можно смело идти пить кофе. Когда же, наконец-таки, все будет собрано, останется только проинсталлировать:
    $ sudo make install
    

    Последний шаг нужно выполнять от имени root’а (если только вы не поставили в --prefix директорию, в которую вам разрешена запись). Так что – либо sudo, либо su.

    Вот и все. Теперь можно смело собирать сэмплы или свои собственные приложения. Для получения флагов компилятора не забывайте использовать <prefix>/bin/wx-config. Примерно таким же образом можно собрать и динамическую, и debug библиотеку (см. configure --help). Вот только чтобы собрать wxWinCE для смартфона надо руками править configure. К сожалению, я не спец по autotools. То, что было необходимо подправить – догадался, откопал, подсмотрел… А по-хорошему надо бы дописать строчки в configure.in, чтобы после autogen скрипт configure позволял бы с помощью опций указать версию WinMobile и тип платформы. Но это уже будет, видимо, другая история…

Н.

Ну почему? Visual Studio 2010 Beta 1 не поддерживает разработку для мобильных устройств

Microsoft делает странное. Самая ожидаемая IDE – Visual Studio 2010 Beta 1 не поддерживает Smart Device Project! И хотя в Microsoft говорят, что работа над Windows Mobile 6.5 уже завершена, ни эмулятора WM6.5 ни средств разработки в новой версии IDE нет. Все это очень странно… в то время как разработка для Google Android и даже для iPhone (для владельцев Mac’ов, естественно) бесплатна, инструментарий от Microsoft можно установить только если есть платная версия Visual Studio, а в новой версии вобще о мобильных устройствах забыли. И это после того, как недавно был анонсирован Windows Marketplace for Mobile, что должно было только увеличить интерес разработчиков.