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

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