Журнал LinuxFormat - перейти на главную

LXF84:Qt/KDE

Материал из Linuxformat
Перейти к: навигация, поиск

Содержание

Модули KDE

ЧАСТЬ 7: Модули, расширения, расширения расширений…. всKIPIте от напряжения и нетерпения под присмотром Андрея Боровского!

Модули KDE

Модули KDE (KDE plugins) представляют собой логическое развитие идеи объектно-ориентированной оболочки. Читатели этой статьи наверняка знакомы с базовыми концепциями модулей как средств расширения уже существующих приложений. Механизм модулей позволяет нам не умножать сущностей (приложений) без необходимости всякий раз, когда нам требуется новая функциональность. По самой своей природе модули тесно связаны с теми приложениями, чьи возможности они расширяют. Нельзя написать «просто модуль». Мы рассмотрим программирование модулей для наиболее популярных приложений KDE и начнем с написания модуля для компонента KParts.

K-Части

Модули KParts широко применяются, например, в браузере Konqueror. Сами компоненты KParts представляют собой еще один способ расширения возможностей приложения. Примером их использования являются встроенные в Konqueror утилиты просмотра файлов различных типов. Каждый раз, когда Konqueror открывает какой-либо файл для просмотра, он загружает соответствующий компонент, который встраивается в интерфейс браузера и отображает содержимое файла. Благодаря динамическим компонентам KParts, Konqueror выполняет функцию универсального просмотрщика файлов, сохраняя при этом простой дизайн и небольшие размеры основного приложения. Следует отметить, что универсализм Konqueror иногда подвергается критике, поскольку в Unix традиционно преобладает другой подход — использование большого числа независимых приложений, каждое из которых делает что-то одно. Мы оставим в стороне вопрос о том, нужно ли писать модули, и ответим на вопрос, как это можно сделать. Модули KParts расширяют возможности компонентов KParts, то есть, с точки зрения основной программы Konqueror, модули являются расширениями расширений. Поддержка модулей KParts возможна, естественно, не только в Konqueror, но и в любом приложении, поддерживающем соответствующие компоненты KParts.

Возможно, на этом этапе у читателя возникает вопрос — а зачем вообще нужны модули Konqueror? Необходимость в механизме модулей была бы очевидна, если бы Konqueror был закрытым приложением, однако, при наличии исходных текстов, мы можем вносить изменения непосредственно в код компонентов KParts. Это верно, и тем не менее модули оказываются полезны и в случае открытого приложения. Главное преимущество использования модулей заключается в лучшей структурной организации кода. Нам легче писать и отлаживать модули, связанные с приложением с помощью строго определенного интерфейса, нежели вносить изменения в исходные тексты приложения, написанного другими программистами. К этому следует добавить и чисто организационный момент — для того, чтобы изменения, сделанные вами в приложении, попали в его основную версию, они должны быть одобрены другими разработчиками, тогда как в случае с модулями у вас есть возможность самостоятельно решить, какие функции являются полезными, а какие — нет.

Что же представляет собой модуль KParts с точки зрения взаимодействия с приложением? Модуль — это динамическая библиотека (файл *.so), которая загружается приложением, использующим компонент KParts. Модуль может добавлять собственные элементы управления в меню и на панель инструментов приложения. В некоторых случаях модули также изменяют содержимое главного окна приложения (которое в этот момент управляется соответствующим компонентом KParts).

В качестве примера мы напишем модуль для компонента KHTMLPart. Как нетрудно догадаться, этот компонент используется для просмотра страниц HTML. Фактически KHTMLPart представляет собой полноценный встраиваемый HTML-браузер с поддержкой JavaScript. Класс KHTMLPart является потомком класса KParts::ReadOnlyPart, то есть он предназначен для просмотра, а не для модификации открываемого файла. Наш модуль будет сохранять открытую в компоненте KHTMLPart HTML-страницу в простом текстовом формате.

(thumbnail)
(Рис. 1) Создаем проект модуля KParts.

Модуль для KHTMLPart удобен в качестве примера потому, что в KDevelop есть шаблон проекта такого модуля. В окне Создать новый проект (Рис. 1) мы выбираем пункт KHTMLPart Plugin. Назовем наш проект «textsaver» (полные исходные тексты модуля вы найдете на диске, в файле textsaver.tar.gz). Наш модуль, как таковой, состоит только из двух классов — Plugintextsaver (основной класс модуля) и textsaverFactory (вспомогательный класс). Оба класса реализованы в файлах plugin_textsaver.h и plugin_textsaver.cpp. Прежде чем вносить изменения в классы, добавим в файл plugin_textsaver.cpp дополнительные заголовочные файлы:

include <stdio.h>
#include <kfiledialog.h>
#include <qcstring.h>

Рассмотрим сначала класс textsaverFactory, являющийся потомком класса KParts::Factory и управляющий созданием объекта класса Plugintextsaver (то есть, этот класс реализует фабрику объектов). Такой объект создается методом textsaverFactory::createObject(), который сгенерирован автоматически. Нам вообще ничего не нужно было бы менять в классе textsaverFactory, если бы не чистый виртуальный метод createPartObject(). Сам по себе он нам не нужен, но для того, чтобы наш модуль мог скомпилироваться, мы должны создать его реализацию. Странно, что KDevelop, по крайней мере в версии 3.4.2, не делает этого автоматически:

KParts::Part* createPartObject(
QWidget*, const char*, QObject*,
const char*, const char*, const QStringList&)
{return 0;}

Помимо прочего, разделяемая библиотека, содержащая наш модуль, должна экспортировать функцию, создающую объект класса textsaverFactory. Эта функция, сгенерированная автоматически, экспортируется в формате C:

extern “C”
{
void* init_libtextsaverplugin()
{
KGlobal::locale()->insertCatalogue(“textsaver”);
return new textsaverFactory;
}
}

Приложение, вызывающее функцию init_libtextsaverplugin(), знает, что делать с возвращенным указателем.

Перейдем к классу Plugintextsaver. Приложение, а точнее, компонент KHTMLPart, взаимодействует с нашим модулем при помощи вызова трех методов этого класса — конструктора, деструктора и метода slotAction(). Метод slotAction() представляет собой слот, который вызывается всякий раз, когда пользователь выбирает пункт меню или нажимает кнопку, связанную с модулем. Этот метод содержит код, который, собственно, и отвечает за выполнение команды. В конструкторе класса Plugintextsaver создается объект-действие:

Plugintextsaver::Plugintextsaver( QObject* parent, const char* name )
: Plugin( parent, name )
{
(void) new KAction( i18n(“Save as &Text”), “textsaver”, 0,
this, SLOT(slotAction()),
actionCollection(), “plugin_action” );
}

Этот объект будет добавлен в коллекцию действий приложения, вызвавшего компонент KHTMLPart, загрузивший наш модуль. Тем, кто не совсем понимает, о очем идет речь, рекомендуем прочитать позапрошлую статью этого цикла (LXF82), в которой мы познакомились с объектами-действиями. Мы указываем объекту-действию имя соответствующего элемента управления и слот, который должен обрабатывать команду. Очевидно, что в общем случае мы можем зарегистрировать несколько действий для нашего модуля. Нам осталось написать только сам метод slotAction():

void Plugintextsaver::slotAction()
{
if ( !parent()->inherits(“KHTMLPart”) )
{
QString title( i18n(“HTML Only”));
QString text(i18n(“Only HTML documents can be saved with this plugin.”));
KMessageBox::sorry( 0, text, title );
return;
}
KHTMLPart *part = dynamic_cast<KHTMLPart *>(parent());
QString FN = KFileDialog::getSaveFileName(“”, “”, 0, “Save Text File As”);
if (FN == “”) return;
QString Cmd = “html2text >+ FN;
QCString Text = part->documentSource().local8Bit();
FILE * F = popen(Cmd.ascii(), “w”);
fwrite((char*) Text.data(), 1, Text.length(), F);
pclose(F);
}

У модуля должна быть какая-то возможность взаимодействовать с другими элементами приложения-хозяина (этот термин не следует понимать в том смысле, что наш модуль паразитирует на браузере Konqueror). Такая возможность у модуля есть, благодаря тому, что конструктору объекта модуля был передан указатель на вызвавший его объект класса KTHMLPart. Доступ к этому указателю можно получить с помощью метода parent(). Первый блок кода в методе slotAction() проверяет, действительно ли объект parent() принадлежит классу KHTMLPart. На практике это излишне, поскольку модуль никогда не будет вызван для какого-либо иного компонента. Далее мы получаем указатель на родительский объект KHTMLPart, запрашиваем у пользователя имя файла для сохранения текста (надеюсь, вы еще не забыли, что должен делать наш модуль) и, с помощью метода documentSource() получаем HTML-текст страницы, открытой компонентом KHTMLPart. Метод documentSource() возвращает данные в строке QString в двухбайтовой кодировке. Мы преобразуем полученную строку в локальную однобайтовую кодировку и сохраняем результат в переменной Text типа QCString. Далее, с помощью функции popen(3), мы запускаем внешнюю утилиту html2text, преобразующую данные HTML в простой текст. Этой утилите мы передаем содержимое переменной Text и имя файла для сохранения результата. Применяемый нами прием — использование в программе внешней утилиты для решения нашей задачи, очень широко распространен в Unix-системах.

Хотя мы и определили все методы классов нашего модуля, работа над ним еще не закончена. Модуль создает новый объект-действие для вызвавшего его приложения, но приложение пока не знает, как оно должно (и должно ли) отображать соответствующий этому действию элемент интерфейса. Наш модуль может дополнить пользовательский интерфейс хозяйского приложения с помощью механизма XMLGUI, описанного в прошлой статье (LXF83). Откройте файл plugin_textsaver.rc:

<!DOCTYPE kpartgui>
<kpartplugin name=”textsaver” library=”libtextsaverplugin” version=”1”>
<MenuBar>
<Menu name=”tools”><Text>&amp;Tools</Text>
<Action name=”plugin_action”/>
</Menu>
</MenuBar>
<ToolBar name=”extraToolBar”>
<Action name=”plugin_action”/>
</ToolBar>
</kpartplugin>

Те, кто читал предыдущую статью, не найдут в этом тексте ничего неожиданного. Мы регистрируем два элемента управления — для меню и панели быстрого доступа. Команда вызова нашего модуля (Save as Text) будет расположена в меню Tools (Сервис), хотя, возможно, следовало расположить ее в меню Файл. Кнопка команды по умолчанию будет расположена на дополнительной панели инструментов, которая (опять-таки, по умолчанию) не видна.

(thumbnail)
(Рис. 2) Команд вызова модуля.

Нам осталось скомпилировать и установить наш модуль. Обычно модули устанавливаются в общую системную директорию, например, в $KDEDIR/lib/kde3/, так что для установки понадобятся права root. При отладке модуля следует учитывать, что текущая версия модуля кэшируется оболочкой Konqueror (и даже ldconfig не помогает), так что для того, чтобы увидеть изменения, внесенные после повторной установки расширения, вам, возможно, придется перезапускать оболочку.

Скомпилируйте и установите модуль, после чего запустите новый экземпляр Web-браузера (но не файл-менеджера) Konqueror или откройте HTML-страницу в файл-менеджере. В меню Сервис вы увидите команду, вызывающую наш модуль (Рис. 2). Выше уже было сказано, что система следит за тем, чтобы наш модуль не был вызван для неподходящего компонента. Это выражается в том, что соответствующая команда меню видна тогда, когда мы открываем HTML-страницу, и не видна в остальных случаях.

Если при просмотре сохраненной нашим модулем страницы в каком-нибудь текстовом редакторе вы увидите нечто странное, то не спешите ругаться. Используемая нами утилита html2text, во-первых, пытается сохранить расположение текста, соответствующее его расположению на странице HTML, а во-вторых, сохраняет текст в формате команды cat (в этом формате гиперссылка выделяется повторением каждой буквы ее надписи по два раза).

Помимо модулей KParts, браузер Konqueror поддерживает еще несколько типов модулей. Модули панели навигации позволяют добавить новые команды на панель навигации Konqueror (эта панель по умолчанию припаркована к левому краю окна менеджера файлов). Все, что требуется от модуля панели навигации — возвратить указатель на визуальный элемент, который и будут отображен браузером при вызове соответствующей команды. Модули KFile позволяют добавлять элементы в окно свойств файла (это окно можно вызвать с помощью команды Свойства контекстного меню менеджера файлов). Обычно модули KFile используются для вывода метаданных файлов. Поскольку для всех мыслимых типов файлов такие модули уже написаны, вам не придется писать модуль KFile, если только вы не разрабатываете собственный файловый формат.

Модули KIPI

Структура модуля обычно настолько тесно связана со структурой приложения, для которого этот модуль предназначен, что изучение программирования модулей имеет смысл только в контексте изучения разработки для конкретного приложения. Однако из этого правила есть и исключения. Примером модулей, которые могут использоваться для работы с несколькими разными приложениями, являются модули KIPI. KIPI (KDE Image Plugin Interface — интерфейс KDE для обработки изображений) — это проект, целью которого является создание единого интерфейса модулей для основанных на KDE приложений, предназначенных для просмотра и редактирования растровых изображений, — Digikam, KimDaBa (ныне KPhotoAlbum), ShowFoto и Gwenview. Хотя «KIPI» представляет собой аббревиатуру, на сайте http://extragear.kde.org/apps/kipi/ используется написание «Kipi». Мы будем писать KIPI, дабы читатели понимали, что это аббревиатура, а не русский глагол, написанный транслитом. Модули, соответствующие требованиям KIPI, должны работать одинаково во всех перечисленных приложениях. Думаю, не нужно распространяться о том, насколько это упрощает жизнь программиста, стремящегося к тому, чтобы его разработками могли пользоваться поклонники всех графических программ KDE. Конечно, не все модули могут иметь смысл в контексте каждого приложения. Это становится очевидно, если учесть, что в то время как одни приложения KDE (например, Gwenview) обладают исключительно функциями просмотра, то другие (Digikam) включают еще и базовые функции редактирования. Если же все программы будут эволюционировать в одном и том же направлении, система утратит разнообразие и само наличие нескольких независимых программ потеряет смысл.

В отличие от других интерфейсов KDE, интерфейс KIPI сравнительно слабо документирован. Это значит, что наилучшим источником сведений о программировании модулей KIPI являются исходные тексты модулей, написанных другими программистами.

Информацию о модулях KIPI можно получить на сайте http://extragear.kde.org. Там же можно загрузить и исходные тексты модулей (уже модифицированные тексты вы можете найти на прилагаемом диске, в файле kipi-plugins-0.1.2m.tar.gz). Прежде чем устанавливать модули необходимо установить библиотеку libkipi (ее исходные тексты тоже есть на диске, в файле libkipi-0.1.4.tar.bz2).

Рассмотрим структуру модуля KIPI на примере модуля HelloWorld, который, как следует из названия, как раз и должен служить примером для начинающих модулеписателей (исходные тексты этого модуля находятся в директории kipi-plugins-0.1.2/kipi-plugins/helloworld/ файла kipi-plugins-0.1.2m.tar.gz). Как вы, наверное, догадались, модуль KIPI представляет собой разделяемую библиотеку. Явным образом эта библиотека экспортирует один-единственный класс — класс модуля. Определение соответствующего класса Plugin_HelloWorld мы найдем в файлах plugin_helloworld.cpp и plugin_helloworld.h.

Пробуем разобраться в том, что делает каждый метод этого класса. Конструктор Plugin_HelloWorld вызывает конструктор базового класса всех классов модулей KIPI KIPI::Plugin и выводит сообщение о том, что модуль загружен. Отметим здесь, что поскольку модули подключаются динамически, вывод такого сообщения полезен не только в процессе отладки модуля. Рассмотрим метод setup(), который, как можно догадаться из названия, будет вызван приложением-хозяином модуля сразу после создания объекта.:

void Plugin_HelloWorld::setup( QWidget* widget )
{
KIPI::Plugin::setup( widget );
// this is our action shown in the menubar/toolbar of the mainwindow
m_actionHelloWorld = new KAction (i18n(“Hello World...”),
“misc”,
0, // do never set shortcuts from plugins.
this,
SLOT(slotActivate()),
actionCollection(),
“helloworld”);
addAction( m_actionHelloWorld );
m_interface = dynamic_cast< KIPI::Interface* >( parent() );
if ( !m_interface )
{
kdError( 51000 ) << “Kipi interface is null!<< endl;
return;
}
}

Этот метод вызывает одноименный метод базового класса и создает объект-действие (таких объектов может быть зарегистрировано несколько). В методе setup() модуль получает также указатель на объект класса KIPI::Interface, который реализует интерфейс взаимодействия между модулем и программой-хозяином.

Далее в классе Plugin_HelloWorld определен метод slotActivate(), который и выполняет всю работу модуля (именно этот слот был указан при создании объекта-действия). Метод slotActivate() мы пока пропустим и рассмотрим последний метод класса Plugin_HelloWorld::category():

KIPI::Category Plugin_HelloWorld::category( KAction* action ) const
{
if ( action == m_actionHelloWorld )
return KIPI::IMAGESPLUGIN;
kdWarning( 51000 ) << “Unrecognized action for plugin category
identification” << endl;
return KIPI::IMAGESPLUGIN; // no warning from compiler, please
}

С помощью этого метода программа-хозяин может выяснить, к какой категории должен принадлежать элемент управления, который она создает для данного объекта-действия. Модуль HelloWorld возвращает значение KIPI::IMAGESPLUGIN, которое указывает, что соответствующий элемент управления должен относиться к категории Images.

Для того чтобы написать свой модуль KIPI, нам, фактически, необходимо только переписать метод slotActivate() модуля HelloWorld. В архиве kipi-plugins-0.1.2m.tar.gz вы найдете модуль mykipi, который и был создан на основе модуля HelloWorld (поскольку заготовки проекта KDevelop для такого модуля в KDevelop нет, его проще распространять как часть пакета kipi-plugins). Все, что отличает модуль mykipi от модуля-прародителя, сосредоточено в методе slotActivate() класса Plugin_MyKIPI:

void Plugin_MyKIPI::slotActivate()
{
kdDebug( 51000 ) << “Plugin_MyKIPI slot activated” << endl;
KIPI::ImageCollection selection = m_interface->currentSelection();
if ( !selection.isValid() ) {
kdDebug( 51000) << “No Selection!<< endl;
}
else {
KURL::List images = selection.images();
QStringList SL = images.toStringList();
QString S = SL.join(“\n”);
KMessageBox::information(0, S, “Selected Images”);
}
}

Метод начинает работу с вывода диагностического сообщения. Затем мы создаем объект класса KIPI::ImageCollection, который содержит ссылки на объекты, представляющие изображения, выбранные пользователем в окне графической программы (ваш разум еще не кипит от этих модулей KIPI?). Если метод currentSelection() вернул значение NULL, значит, пользователь не выбрал ни одного изображения и тогда нашему модулю просто нечего делать. Впрочем, вызов модуля при отсутствии выбранных изображений невозможен, как невозможен вызов модуля KParts для неподходящего компонента. Как и положено всякому учебному примеру, модуль mykipi не делает ничего полезного. С помощью метода selection.images() мы получаем список URL выделенных изображений (не забывайте, что мы имеем дело с программами, предназначенными, в основном, для просмотра и редактирования существующих графических файлов). Далее мы преобразуем список URL в QStringList и затем QString, и выводим его содержимое в диалоговом окне.

Когда я писал, что у нашего модуля есть только один класс, я немного упростил ситуацию. На самом деле, модуль реализует еще один класс, а именно класс Factory, управляющий созданием основного объекта класса модуля, однако генерация этого класса выполняется автоматически с помощью определения типа и вызова макроса:

typedef KGenericFactory<Plugin_MyKIPI> Factory;
K_EXPORT_COMPONENT_FACTORY( kipiplugin_mykipi,Factory("kipiplugin_mykipi"));

Прежде, чем мы установим наш модуль, нам следует отредактировать файл kipiplugin_mykipi.desktop. Ниже приводится его отредактированный текст (структура файлов *.desktop похожа на структуру файлов *.ini Windows, и это не случайное совпадение):

[Desktop Entry]
Encoding=UTF-8
Name=MyKIPI
Name[cs]=My KIPI
Comment=Sample KIPI Plugin
Type=Service
ServiceTypes=KIPI/Plugin
X-KDE-Library=kipiplugin_mykipi
author=Andrei Borovsky, borovsky@tochka.ru
(thumbnail)
(Рис. 3) Модуль mykipi в работе.

Файл kipiplugin_mykipi.desktop cодержит сведения о типе модуля (ServiceTypes=KIPI/Plugin), а также имя модуля и комментарий.

Теперь мы можем скомпилировать и установить модуль (естественный путь — make install). Запустим программу Gwenview и в меню Модули, в группе Изображения (Images) мы увидим команду Sample KIPI Plugin, которая будет работать так, как мы и ожидаем (Рис. 3).

В этой статье мы рассмотрели, среди прочих, модули для компонентов KParts. В следующей, заключительной части серии мы узнаем, как создавать собственные компоненты (и модули для них).

Персональные инструменты
купить
подписаться
Яндекс.Метрика