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

LXF85:Qt/KDE

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

ЧАСТЬ 8: В заключение этой длинной серии Андрей Боровский даст вам несколько советов по написанию компонентов KParts.

Компоненты KParts

Целое всегда больше суммы своих частей
Аристотель

Серия статей, посвященных программированию Qt и KDE, подходит к своему финалу, и вполне логично, что в заключительной статье мы рассмотрим написание компонентов KParts. Это можно считать хорошим финалом нашей серии не только потому, что программирование KParts — одна из самых сложных тем, но еще и потому, что сами компоненты KParts представляют собой венец развития многих технологий KDE. Например, система XMLGUI, которая изначально задумывалась только как средство настройки интерфейса пользователя, нашла неожиданно удачное применение в рамках KParts. В детской истории, ни названия, ни автора которой я, к сожалению, не помню, главный герой плавал по морям и все время носил подтяжки. Подтяжки эти пригодились ему только один раз, когда он встретился… с кашалотом, и в этот момент они спасли ему жизнь (увы, я не помню, что именно отважный мореход сделал с кашалотом). Такие элементы KDE, как XMLGUI и KAction напоминают подтяжки из рассказа. Они кажутся ненужными до тех пор, пока программист не встретится с кашалотом, то есть с компонентами и модулями. Материал этой статьи может служить отправной точкой для изучения программирования некоторых специализированных типов компонентов, таких, например, как компоненты KOffice Parts. Если вы задумаете программировать компоненты KOffice, вам придется набраться терпения, потому что эти компоненты реализовать сложнее, чем рассмотренные в этой статье обычные компоненты KParts.

Все пользователи KDE сталкивались с компонентами KParts, хотя не все, возможно, знают об этом. Каждый раз, когда вы щелкаете мышью по значку графического файла (или файла другого поддерживаемого типа) в окне Konqueror, файл-менеджер переходит в режим просмотра. При этом Konqueror загружает компонент KParts, способный отобразить содержимое файла данного типа. В режиме просмотра меняется и интерфейс браузера — в строке меню и на панели быстрого доступа появляются команды компонента-просмотровщика. Откуда берутся все эти компоненты KParts? Хотя компонент может существовать и сам по себе, как правило, он является частью какого-либо самостоятельного приложения. Например, для просмотра растровой графики Konqueror использует компонент GVImage_part, который не является частью какого либо приложения (по крайней мере, в моей системе SUSE 10.1 нет приложения gvimage), тогда как для просмотра документов в формате PDF используется компонент kpdf_part, который является основой самостоятельного приложения KPDF. Следует подчеркнуть, что приложение, основанное на компоненте KParts, обычно взаимодействует с ядром этого компонента напрямую, минуя оболочку KParts. KParts — стандартизированный интерфейс, предназначенный для взаимодействия между частями разных приложений, которые не учитывают внутренних особенностей друг друга. Так, например, приложение KPDF не использует функциональность KParts для взаимодействия с компонентом kpdf_part, хотя такое взаимодействие, в принципе, возможно. Интерфейс KParts, предоставляемый компонентом kpdf_part, используют другие приложения, например, Konqueror. На практике все это означает, что если у нас есть какое-либо приложение KDE, мы можем превратить его в источник компонента KParts, ничего не меняя в его основе. Само превращение самостоятельной программы в компонент KParts выполняется очень просто, что мы сейчас и увидим.

Почему компоненты KParts создаются на основе самостоятельных приложений? Во-первых, таким образом расширяется сфера их применения. Зачастую более функционально иметь и приложение, и соответствующий ему компонент. Пользователь может работать с приложением, поставляющим KParts, как с самостоятельным инструментом или как с компонентом, загружаемым другой программой. Вовторых, отлаживать самостоятельные приложения проще, нежели отлаживать компоненты KParts. Обычно сначала пишут и отлаживают программу, а затем на ее основе создают компонент, добавляя интерфейс KParts к уже проверенному в работе ядру приложения. Мы тоже пойдем этим путем и создадим компонент KParts на основе программы images, которую мы рассматривали в позапрошлой статье. Если у вас нет позапрошлого номера журнала, — не беда, исходные тексты программы images (не модифицированные) вы найдете на диске журнала в фале images.tar.gz. На основе программы images мы создадим компонент images_part, который сможет открывать графические файлы для просмотра и выполнять над ними простые преобразования (исходные тексты приложения с компонентом вы найдете в файле images_part.tar.gz).

Как и рассмотренные в предыдущей статье модули KDE, компоненты KParts экспортируются из разделяемых библиотек, поэтому разработку нашего компонента мы начнем с того, что преобразуем основную часть программы images в разделяемую библиотеку. Как вы помните (а если не помните, перечитайте пятую часть этой серии), главным визуальным элементом программы images является класс imagesView. Этот класс не только отображает содержимое графического файла, но и содержит методы, выполняющие преобразование изображения. Наша задача заключается в том, чтобы вынести класс imagesView в разделяемую библиотеку. Сделать это очень просто, нам даже не придется менять ни одной строчки исходных текстов images. Откройте в текстовом редакторе файл Makefile.am (тот, который лежит в images/src в старом варианте и в images-part/src — в новом). Файл Makefile.am используется инструментом autoconf для генерации Make-файла и содержит описание основных параметров целевого приложения. Ниже приводится фрагмент этого файла, который нас сейчас интересует (для сокращения листинга комментарии удалены):

bin_PROGRAMS   = images images_client
INCLUDES       = $(all_includes)
images_LDFLAGS = $(KDE_RPATH) $(all_libraries)
images_LDADD   = $(LIB_KFILE) $(LIB_KDEPRINT)
images_SOURCES = main.cpp images.cpp imagesview.cpp pref.cpp

Заменим этот фрагмент следующим:

lib_LTLIBRARIES = libimages1.la
bin_PROGRAMS    = images 
libimages1_la_SOURCES = images.cpp imagesview.cpp pref.cpp imagesiface.skel SettingsDialog.ui settingsdialogimpl.cpp images_part.cpp
libimages1_la_LDFLAGS = $(all_libraries) -version-info 1:0:0 -module
libimages1_la_LIBADD  = $(LIB_KFILE) $(LIBVM) -lKParts
INCLUDES        = $(all_includes)
images_LDFLAGS  = $(KDE_RPATH) $(all_libraries)
images_LDADD    = $(LIB_KDECORE) libimages1.la 
images_SOURCES  = main.cpp
Строка
lib_LTLIBRARIES = libimages1.la
указывает, что мы добавляем в проект images новую библиотеку. В моей системе уже есть библиотека libimages, так что я добавил к имени цифру 1. Далее мы сообщаем, что теперь большая часть исходных текстов исполняемого файла images становится частью библиотеки libimages1 (имена файлов исходных текстов библиотеки содержит переменная libimages1_la_SOURCES). Сравните значение, присваиваемое libimages1_la_SOURCES в новом варианте файла и значение, присваиваемое images_SOURCES в старом варианте. Переменная libimages1_la_LIBADD содержит список библиотек, от которых зависит библиотека libimages1. Сюда «перешли» все библиотеки, от которых зависела программа images, плюс библиотека libKParts, которая понадобится нашему компоненту KParts. Сама программа images ссылается теперь только на библиотеку libimages1 (переменная images_LDADD), а ее исходные тексты состоят из одного файла main.cpp. Теперь мы можем заново сгенерировать Make-файл для нашего приложения и пересобрать его. Новый вариант программы images работает так же, как и прежний, но теперь программа устроена по-другому. Основная ее часть теперь содержится в библиотеке libimage1, а исполняемый файл images играет роль оболочки для этой библиотеки.

Следующим шагом нужно добавить в библиотеку libimages1 оснастку, превращающую класс imagesView в компонент KParts images_part. В библиотеку libimages1 мы добавим реализацию трех классов, обеспечивающих работу компонента. Если эти классы покажутся вам очень сложными, не отчаивайтесь — их реализация почти не зависит от специфики компонента, для которого они предназначены. Создавая новый компонент, вы можете просто перенести в него код этих классов, после чего нужно будет изменить в нем лишь несколько строк. Добавим в проект библиотеки два новых файла — images_part.h и images_part.cpp (в среде KDevelop это можно сделать с помощью Automake Manager). В файле images_part.h объявим три класса:

class ImagesFactory : public KLibFactory
{
    Q_OBJECT
public:
    ImagesFactory();
    virtual ~ImagesFactory();
    static KInstance *instance();
    static KAboutData *aboutData();
protected:
    QObject* createObject( QObject* parent = 0, const char* name = 0,
                                   const char* className = "QObject",
                                   const QStringList &args = QStringList() );
private:
    static KInstance *s_instance;
};
class ImagesPart: public KParts::ReadOnlyPart
{
    Q_OBJECT
public:
    ImagesPart(QWidget *parent, const char *name);
    virtual ~ImagesPart();
protected:
    virtual bool openFile();
    virtual bool closeURL();
protected slots:
      void proctoBlacknWhite();
private:
    imagesView *widget;
    ImagesBrowserExtension *m_extension;
    KAction * actiontoBlacknWhite;
};
class ImagesBrowserExtension : public KParts::BrowserExtension
{
    Q_OBJECT
    friend class ImagesPart;
public:
    ImagesBrowserExtension(ImagesPart *parent);
    virtual ~ImagesBrowserExtension();
};

Каждый компонент KParts должен создать два класса — класс-фабрику объектов KParts и класс, инкапсулирующий функциональность KParts. Класс, реализующий фабрику объектов, должен происходить от класса KLibFactory, а класс KParts — либо от класса KParts::ReadOnlyPart, либо от класса KParts::ReadWritePart. Классы, происходящие от KParts::ReadOnlyPart соответствуют компонентам, предназначенным для просмотра (но не модификации файлов). Классы-потомки KParts::ReadWritePart соответствуют компонентам, реализующим функции редактирования. Главным классом нашего компонента является класс ImagesPart, происходящий от класса KParts::ReadOnlyPart.Рассмотрим подробнее класс-фабрику ImagesFactory. В файле images_part.cpp должна быть определена функция init_libimages1(), которая создает объект класса-фабрики объектов:!!!Имя этой функции состоит из префикса init_ и имени библиотеки, экспортирующей компонент (приложение, загружающее нашу библиотеку для того, чтобы получить компонент images_part, ожидает найти в ней функцию с таким именем). Когда приложению понадобится создать компонент images_part, оно вызовет эту функцию и получит указатель на объект ImagesFactory, который затем и создаст главный объект компонента. Основная задача объекта класса-фабрики заключается в том, чтобы не допустить появления двух объектов класса ImagesPart. Поскольку приложение ничего не знает о классе ImagesFactory, оно будет работать с соответствующим объектом как с объектом базового класса KLibFactory. Соответственно, мы должны перекрыть в классе ImagesFactory виртуальные методы, унаследованные им от классов-предков. Метод ImagesFactory::createObject(), объявленный как чистый виртуальный, создает объект класса ImagesPart.В предыдущих версиях KDE эту функцию выполнял метод create().Метод instance() либо создает объект класса KInstance, либо возвращает ссылку на уже созданный объект этого класса. Объект класса KInstance позволит нашему компоненту получить доступ к тем глобальным объектам KDE, которые обычно доступны самостоятельным приложениям. Как уже отмечалось, вы можете просто скопировать весь код класса-фабрики в проект своего компонента. Для того, чтобы этот код работал в вашем проекте, нужно изменить только имя главного класса компонента, который создает метод createObject().Перейдем теперь к конструктору класса ImagesPart: Каждый компонент KParts должен создать два класса — класс-фабрику объектов KParts и класс, инкапсулирующий функциональность KParts. Класс, реализующий фабрику объектов, должен происходить от класса KLibFactory, а класс KParts — либо от класса KParts::ReadOnlyPart, либо от класса KParts::ReadWritePart. Классы, происходящие от KParts::ReadOnlyPart соответствуют компонентам, предназначенным для просмотра (но не модификации файлов). Классы-потомки KParts::ReadWritePart соответствуют компонентам, реализующим функции редактирования. Главным классом нашего компонента является класс ImagesPart, происходящий от класса KParts::ReadOnlyPart.Рассмотрим подробнее класс-фабрику ImagesFactory. В файле images_part.cpp должна быть определена функция init_libimages1(), которая создает объект класса-фабрики объектов:

extern "C"
{
    void *init_libimages1()
    {
        return new ImagesFactory;
    }
};

Имя этой функции состоит из префикса init_ и имени библиотеки, экспортирующей компонент (приложение, загружающее нашу библиотеку для того, чтобы получить компонент images_part, ожидает найти в ней функцию с таким именем). Когда приложению понадобится создать компонент images_part, оно вызовет эту функцию и получит указатель на объект ImagesFactory, который затем и создаст главный объект компонента. Основная задача объекта класса-фабрики заключается в том, чтобы не допустить появления двух объектов класса ImagesPart. Поскольку приложение ничего не знает о классе ImagesFactory, оно будет работать с соответствующим объектом как с объектом базового класса KLibFactory. Соответственно, мы должны перекрыть в классе ImagesFactory виртуальные методы, унаследованные им от классов-предков. Метод ImagesFactory::createObject(), объявленный как чистый виртуальный, создает объект класса ImagesPart.В предыдущих версиях KDE эту функцию выполнял метод create().Метод instance() либо создает объект класса KInstance, либо возвращает ссылку на уже созданный объект этого класса. Объект класса KInstance позволит нашему компоненту получить доступ к тем глобальным объектам KDE, которые обычно доступны самостоятельным приложениям. Как уже отмечалось, вы можете просто скопировать весь код класса-фабрики в проект своего компонента. Для того, чтобы этот код работал в вашем проекте, нужно изменить только имя главного класса компонента, который создает метод createObject().

Перейдем теперь к конструктору класса ImagesPart:

ImagesPart::ImagesPart(QWidget *parent, const char *name)
    : KParts::ReadOnlyPart(parent, name)
{
    setInstance(ImagesFactory::instance());
    QWidget *canvas = new QWidget(parent);
    canvas->setFocusPolicy(QWidget::ClickFocus);
    setWidget(canvas);
    m_extension = new ImagesBrowserExtension(this);
    actiontoBlacknWhite = new KAction(i18n("To &Black && White"),  
                      QIconSet(BarIcon("blacknwhite")), 0, this,
                      SLOT(proctoBlacknWhite()),
                      actionCollection(), "bw_command");
    actiontoBlacknWhite->setToolTip(i18n("Transforms image palette to grayscale"));
    setXMLFile("images_part.rc");
    widget = new imagesView(canvas);
    widget->show();
}

В конструкторе мы инициализируем поля этого класса. Обратите внимание на объект canvas класса QWidget. С помощью метода setWidget() мы указываем приложению, загружающему компонент, что объект canvas является главным визуальным элементом компонента. На самом деле объект canvas невидим, но он будет родителем нашего «настоящего» главного визуального элемента в иерархии графических элементов. Пустой объект класса QWidget нужен для того, чтобы «настоящий» визуальный элемент компонента мог сам управлять своим размером. Дело в том, что когда Konqueror (или другое приложение) создаст экземпляр нашего компонента, его главный визуальный элемент (тот, который зарегистрирован в приложениихозяине с помощью метода setWidget()) автоматически будет «растянут» на все главное окно приложения. Чтобы избежать нежелательных эффектов, мы передаем приложению не «настоящий» графический объект, а «пустой» объект canvas, и получаем возможность самостоятельно контролировать размеры «настоящего» визуального элемента. Указатель на «настоящий» главный визуальный элемент, — объект класса imagesView, хранится в поле widget.

Класс ImagesBrowserExtension — потомок класса KParts::BrowserExtension, должен способствовать более тесной интеграции компонента, основанного на ReadOnlyPart и приложения-браузера. Браузеры KDE обладают способностью работать с сетевыми ресурсами как с локальными файлами, и класс KParts::BrowserExtension помогает основному классу компонента обрабатывать URL. Наш компонент способен загружать только локальные файлы, так что мы не заботимся о поддержке сетевых URL.

Пропустим пока остальные элементы конструктора и рассмотрим метод openFile():

bool ImagesPart::openFile()
{
    QPixmap pm(m_file);
    widget->setPixmap(pm);
    widget->setAutoResize(true);
    return !pm.isNull();
}

Этот метод перекрывает виртуальный метод класса-предка. Приложение-хозяин вызывает метод openFile() для того, чтобы компонент мог открыть соответствующий файл. Имя файла (точнее, его URL) хранится в этот момент в поле m_file. Как и в самом приложении images, мы загружаем изображение с помощью объекта класса QPixmap. Метод openFile() должен возвращать значение true, если ему удалось открыть URL, и false в противном случае (мы проверяем результат операции с помощью метода isNull()). Метод closeURL(), который также объявлен в базовом классе, нужен для тех компонентов, которые поддерживают работу с сетевыми ресурсами.

Этот метод вызывается приложением, если нужно прервать текущий процесс загрузки сетевого ресурса (это может понадобиться, например, в том случае, если пользователь отменил загрузку ресурса или начал загружать другой ресурс, не дожидаясь завершения загрузки предыдущего).

Добавим кнопочки

В принципе, у нашего компонента теперь есть все, что нужно для того, чтобы отображать содержимое файла, однако большинство компонентов, даже тех, что предназначены только для просмотра, этим не ограничивается. Как правило, у компонентов есть визуальные элементы управления, которые позволяют контролировать процесс просмотра. Например, стандартный компонент просмотра изображений предоставляет пользователю команды увеличения, уменьшения и поворота, а также команды перехода к следующему и предыдущему файлам. Мы тоже добавим один элемент управления в компонент images_part. Как вы помните, приложение images может модифицировать открытое изображение, в частности, конвертировать полноцветную палитру в палитру оттенков серого. Эту команду мы и добавим в наш компонент. Вернемся к конструктору ImagesPart. В этом конструкторе мы создаем объект-действие actiontoBlacknWhite. Мы уже создавали объекты-действия в седьмой и пятой частях этой серии, так что останавливаться подробно на создании объекта actiontoBlacknWhite мы не будем. Наш объект-действие связан со слотом proctoBlacknWhite(), который выполняет команду преобразования палитры:

void ImagesPart::proctoBlacknWhite()
{
   widget->toBlacknWhite();
}

Вы уже знаете, что для того, чтобы добавить новый элемент интерфейса, недостаточно создать объект-действие. Нужно еще и указать приложению, где и как отображать соответствующие элементы меню и панели быстрого доступа. В этом нам, как всегда, поможет технология XMLGUI. Обратите внимание на вызов метода setXMLFile() в конструкторе ImagesPart. Этот метод указывает имя файла, из которого компонент должен взять описание интерфейса. Добавим в проект сам файл images_part.rc:

<!DOCTYPE kpartgui>
<kpartgui name="images">
<MenuBar>
        <Menu name="Processing"><text>&amp;Processing</text>
            <Action name="bw_command"/>
        </Menu>
    </MenuBar>
    <ToolBar name="Images-ToolBar">
        <Action name="bw_command"/>
    </ToolBar>
</kpartgui>

Мы указываем, что команда преобразования изображения должна располагаться в подменю Processing главного меню (такого подменю в Konqueror нет, значит, оно будет создано автоматически). В этом файле нет ничего, что было бы нам не знакомо, за исключением, пожалуй, тэга kpartgui, который указывает, что мы имеем дело с компонентом images.

Мы почти закончили программирование нашего компонента. Однако, если мы установим его сейчас, то не увидим никаких изменений. Дело в том, что среда KDE по-прежнему ничего не знает о нем. Мы должны еще отредактировать файл images.kdelnk, который описывает наше приложение images. В этот файл следует добавить три строчки:

MimeType=image/jpeg;image/gif;image/bmp;image/png
ServiceTypes=Browser/View
X-KDE-Library=libimagess1

Первая строчка указывает типы MIME, с которыми может работать наше приложение images и соответствующий ему компонент. Мы могли не указывать эти данные, когда программа images была простым самостоятельным приложением, однако их необходимо указывать для компонента, иначе браузер Konqueror просто не будет знать, когда следует отображать соответствующие компоненту элементы управления. Между прочим, теперь Konqueror будет знать, что не только компонент images_part, но и приложение images способно работать с графическими файлами и добавит в свои меню соответствующие команды. Вторая строчка указывает, какие сервисы предоставляет наш компонент. В данном случае это сервисы просмотра содержимого файлов указанных типов. Третья строчка указывает имя библиотеки компонента. Теперь, наконец, мы можем установить компонент images_part с помощью make install. Запустите экземпляр файл-менеджера Konqueror после установки компонента. В окне браузера щелкните правой кнопкой мыши значок какого-нибудь файла одного из поддерживаемых типов. В открывшемся контекстном меню выберите команду «Просмотреть в images». В результате выбранный файл должен быть открыт для просмотра с помощью нашего компонента (рис. 1).

Как видите, Konqueror добавил новое подменю в главное меню и кнопку на панель быстрого доступа.

Финальный аккорд

(thumbnail)
Рисунок 1. Компонент images_part в работе KParts.

Компонент images_part предназначен только для просмотра изображений. Скажем несколько слов о компонентах, допускающих редактирование. Как уже упоминалось, главный класс компонента-редактора должен быть потомком класса KParts::ReadWritePart. Помимо уже известных нам методов openFile() и closeURL(), в этом классе объявлен ряд новых виртуальных методов, предназначенных для перекрытия в классах-потомках. Самым важным из этих методов является метод saveFile(), который должен выполнять сохранение файла, открытого в компоненте. Этот метод работает так же, как и openFile(), то есть приложение-хозяин вызывает его, когда пользователь дает соответствующую команду. Вы также, возможно, захотите перекрыть метод setReadWrite(), который переключает компонент между состояниями «только для чтения» и «чтение и запись».

На этом мы, как я уже обещал, заканчиваем серию статей по программированию Qt и KDE. Надеюсь, что теперь вы сможете оценить возможности этой технологии и, главное, дать квалифицированный ответ на вопрос, может ли Qt/KDE служить основой для вашей собственной разработки.

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