Разработка приложений с 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, который можно найти здесь и здесь.

 Подписаться на RSS

 #  #  #  #  #  #  #  #  #  #

Комментарии

  1. а как все это можно практически применить? кстати у вас текст вылезает на сайдбар.

  2. Согласен с Krupin, какая польза от этой проги?

  3. Применение конкретно этой программы - при работе в консоли можно добавлять события/задачи не переключаясь на календарь и не делая 10 кликов. А так, конечно, это скорее иллюстрация работы с Akonadi.

    Вообще, я еще сделал Plasma Runner, который позволяет делать ровно то же самое. Код на гитхабе есть - http://github.com/alno/plasma-runner-events - вся разница в окружении - здесь консоль, а там строка по Alt+F2.

    Как минимум мне его использование очень удобно, значительно проще добавить событие или задачу, если не нужно развернутое описание.

  4. под Ubuntu x64 будет работать?

  5. Sasuker, думаю да. Если руки прямые.

  6. Я сейчас как раз на Ubuntu x64, все прекрасно работает =)

Добавить комментарий