Разработка приложений с Akonadi: добавление задач в календарь
Недавно, благодаря хорошей идее на KDE Brainstorm я создал плагин для KDE Plasma Runner, позволяющий быстро добавлять задачи и события в календарь, исходный код которого доступен на GitHub.
Сегодня я хотел бы поделиться опытом создания, а конкретно рассмотреть тему написания приложений, использующих Akonadi.
В качестве примера я рассмотрю простое консольное приложение, которое позволяет добавлять задачи в календарь. Почему консольное приложение? Во-первых, чтобы не отвлекаться на аспекты, не имеющие прямого отношения к Akonadi. Во-вторых, чтобы
Требования
Я использую Ubuntu 9.10 Karmic, в нем для работы необходимо наличие следующих пакетов:
- kdelibs5-dev - библиотеки KDE
- kdepimlibs5-dev - библиотеки PIM KDE
- libboost-dev - Boost
Соответственно:
sudo aptitude install kdelibs5-dev kdepimlibs5-dev libboost-dev
Каркас приложения
Итак, приступим к созданию такого приложения. Назовем его, например, addtodo. Для начала в директории будущего приложения создадим файлы для исходников:
CMakeLists.txt, файл для конфигурации и сборки:
PROJECT(add-todo)
find_package(KDE4 REQUIRED) # Находим модули KDE4
find_package(KdepimLibs REQUIRED) # Находим модули KDE PIM
include(KDE4Defaults)
add_definitions (${QT_DEFINITIONS} ${KDE4_DEFINITIONS})
include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${KDEPIMLIBS_INCLUDE_DIR} ${KDE4_INCLUDES})
set(CMAKE_CXX_FLAGS "-fexceptions")
kde4_add_executable(add-todo add_todo.cpp) # Добавляем цель
target_link_libraries(add-todo ${KDE4_PLASMA_LIBS} ${KDE4_KDEUI_LIBS} ${KDE4_AKONADI_LIBS} ${KDEPIMLIBS_KCAL_LIBS}) # Добавляем соответствующие библиотеки
add_todo_app.h, заголовочный файл:
#ifndef ADD_TODO_H
#define ADD_TODO_H
#include <QCoreApplication>
#include <KJob>
class AddTodo : public QCoreApplication {
Q_OBJECT
public:
AddTodo( int argc, char ** argv );
public slots:
void collectionsFetched( KJob * job ); // Будет вызван, когда мы получим список коллекций
void todoCreated( KJob * job ); // Будет вызван, когда мы создадим задачу
};
#endif // ADD_TODO_H
add_todo_app.cpp, здесь будет содержаться основной код:
#include "add_todo.h"
#include <QTextStream>
static QTextStream out( stdout ); // Поток для вывода данных
AddTodo::AddTodo( int argc, char ** argv ) : QCoreApplication( argc, argv ) {
out << "Application started" << endl;
}
void AddTodo::collectionsFetched( KJob * job ) {
}
void AddTodo::todoCreated( KJob * job ) {
}
int main( int argc, char ** argv ) {
AddTodo app( argc, argv ); // Создаем экземпляр приложения
return app.exec(); // И входим в цикл обработки сигналов
}
Теперь можно проверить, что наше пока еще ничего не делающее приложение корректно собирается:
mkdir build
cd build
cmake ..
make
Если мы запустим приложение, то оно напишет "Application started" и уйдет в бесконечный цикл ожидания сигналов. Пускай, теперь будем добавлять полезную работу.
Получения списка коллекций через Akonadi
Для того, чтобы создать задачу через Akonadi, необходимо сначала получить ссылку на коллекцию (Akonadi::Collection), в которой мы будем ее создавать. Для этого мы получим все коллекции и выберем ту, которая поддерживает подходящий тип элементов. Получение коллекции в Akonadi осуществляется путем создания задачи Akonadi::CollectionFetchJob.
В начало add_todo.cpp добавим инклуды, импорт пространства имен Akonadi и объявим одну константу, в которой будет записан MIME-тип для задачи. Он необходим нам для того, чтобы выбрать подходящую задачу.
#include <QStringList>
#include <Akonadi/Collection>
#include <Akonadi/CollectionFetchJob>
using namespace Akonadi;
static QString todoMimeType( "text/calendar" ); // MIME-тип задачи
В конструктор приложения добавляем:
CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::FirstLevel, this ); // Создаем задачу
connect( job, SIGNAL(result(KJob*)), this, SLOT(collectionsFetched(KJob*)) ); // Связываем сигнал и слот
В первой строке аргументы означают то, что мы получаем подколлекции корневой коллекции, причем только первого уровня.
В метод collectionsFetched добавляем код обработки и выбора нужной нам коллекции:
out << "Collections fetched" << endl;
if ( job->error() ) {
out << "Error occurred: " << job->errorText() << endl;
exit( -1 );
return;
}
const CollectionFetchJob * fetchJob = qobject_cast<CollectionFetchJob*>( job ); // Приводим задачу к нужному типу
const Collection * selectedCollection = 0; // Переменная для выбранной коллекции
foreach( const Collection & collection, fetchJob->collections() ) {
out << "Name: " << collection.name(); // Печатаем имя коллекции для отладки
if ( collection.contentMimeTypes().contains( todoMimeType ) ) { // Проверяем, принимает ли коллекция нужный тип данных
selectedCollection = &collection;
break;
}
}
if ( !selectedCollection ) { // Если не нашли подходящей коллекции, то печатаем ошибку и выходим
out << "Error occurred: no valid collection found"<< endl;
exit( -1 );
return;
}
// А здесь будем создавать задачу
Создание задачи
Теперь у нас есть коллекция, в которую можно наконец добавить задачу. Для этого, необходимо сделать три вещи:
- Создать объект KCal::Todo, описывающий нашу задачу
- Создать объект Akonadi::Item, представляющий элемент данных в Akonadi
- Создать задачу создания нового элемента
- Обработать ее результат
Итак, приступим. Сначала подключим заголовочные файлы:
#include <Akonadi/Item>
#include <Akonadi/ItemCreateJob>
#include <kcal/todo.h>
#include <boost/shared_ptr.hpp>
Теперь напишем код для первых трех пунктов в конце метода collectionsFetched:
KDateTime dueDate = KDateTime::fromString( arguments()[2], "%d.%m.%Y" ); // Парсим дату
if ( !dueDate.isValid() ) { // Проверяем, что дата распарсилась
out << "Error occured: invalid date '" << arguments()[2] << "'" << endl;
exit( -2 );
}
KCal::Todo::Ptr todo( new KCal::Todo() );
todo->setSummary( arguments()[1] ); // Текст
todo->setDtDue( dueDate ); // Дата
todo->setPercentComplete( 0 ); // Пока не выполнена
todo->setHasStartDate( false ); // Начальная дата не установлена
todo->setHasDueDate( true ); // Установлена дата выполнения
Item item( todoMimeType );
item.setPayload<KCal::Todo::Ptr>( todo );
ItemCreateJob * itemCreateJob = new ItemCreateJob( item, *selectedCollection, this ); // Создаем задачу
connect( itemCreateJob, SIGNAL(result(KJob*)), this, SLOT(todoCreated(KJob*)) ); // Связываем сигнал и слот
А в метод todoCreated добавим проверку:
if ( job->error() ) {
out << "Error occurred: " << job->errorText() << endl;
exit( -1 );
return;
}
out << "TODO created" << endl;
quit();
Также, неплохо добавить в начало main проверку количества аргументов:
if ( argc < 3 ) { // Проверяем количество аргументов
out << "Usage: add-todo [text] [date]" << endl;
return -2;
}
Все, теперь программа завершена, можно скомпилировать ее и запустить следующим образом:
./add-todo "Something" 21.01.2010
После выполнения у вас в календаре должна появиться новая задача. Теперь можно что-то улучшать, например, добавить возможность распознавания ссылок на даты вида "today", "tomorrow", поддержку времени, категорий и много чего интересного...
Полный код можно посмотреть по ссылке.
Код примеров построен на основе плагина для Plasma Runner, который можно найти здесь и здесь.

on March 06, 2010 at 15:03 Krupin wrote:
а как все это можно практически применить? кстати у вас текст вылезает на сайдбар.
on March 25, 2010 at 12:03 Korolev wrote:
Согласен с Krupin, какая польза от этой проги?
on March 26, 2010 at 08:03 Alno wrote:
Применение конкретно этой программы - при работе в консоли можно добавлять события/задачи не переключаясь на календарь и не делая 10 кликов. А так, конечно, это скорее иллюстрация работы с Akonadi.
Вообще, я еще сделал Plasma Runner, который позволяет делать ровно то же самое. Код на гитхабе есть - http://github.com/alno/plasma-runner-events - вся разница в окружении - здесь консоль, а там строка по Alt+F2.
Как минимум мне его использование очень удобно, значительно проще добавить событие или задачу, если не нужно развернутое описание.
on April 05, 2010 at 12:04 Sasuker wrote:
под Ubuntu x64 будет работать?
on April 11, 2010 at 09:04 Dimon wrote:
Sasuker, думаю да. Если руки прямые.
on April 14, 2010 at 05:04 Alno wrote:
Я сейчас как раз на Ubuntu x64, все прекрасно работает =)