Pull to refresh

Разработка приложений с Akonadi в KDE4

Desktop environments *
На Хабре уже писалось о том, что такое 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}) # Добавляем соответствующие библиотеки


* This source code was highlighted with Source Code Highlighter.

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


* This source code was highlighted with Source Code Highlighter.

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-тип задачи


* This source code was highlighted with Source Code Highlighter.

В конструктор приложения добавляем:
  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;
  }
  
  // А здесь будем создавать задачу


* This source code was highlighted with Source Code Highlighter.

Создание задачи



Теперь у нас есть коллекция, в которую можно наконец добавить задачу. Для этого, необходимо сделать три вещи:
  • Создать объект 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*)) ); // Связываем сигнал и слот


* This source code was highlighted with Source Code Highlighter.

А в метод todoCreated добавим проверку:
  if ( job->error() ) {
    out << "Error occurred: " << job->errorText() << endl;
    exit( -1 );
    return;
  }

  out << "TODO created" << endl;
  quit();


* This source code was highlighted with Source Code Highlighter.

Также, неплохо добавить в начало main проверку количества аргументов:
  if ( argc < 3 ) { // Проверяем количество аргументов
    out << "Usage: add-todo [text] [date]" << endl;
    return -2;
  }


* This source code was highlighted with Source Code Highlighter.

Все, теперь программа завершена, можно скомпилировать ее и запустить следующим образом:

./add-todo "Something" 21.01.2010

После выполнения у вас в календаре должна появиться новая задача. Теперь можно что-то улучшать, например, добавить возможность распознавания ссылок на даты вида «today», «tomorrow», поддержку времени, категорий и много чего интересного…

Полный код можно посмотреть по ссылке.

Код примеров построен на основе плагина для Plasma Runner, созданный мной несколько дней назад на основе идеи из KDE Brainstorm, который можно найти здесь и здесь.
Tags:
Hubs:
Total votes 8: ↑7 and ↓1 +6
Views 929
Comments Comments 2