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

LXF83:Qt/KDE

Материал из Linuxformat
(перенаправлено с «LXF83:Qt KDE»)
Перейти к: навигация, поиск

Интегрированное совершенство

ЧАСТЬ 6 Созданное нами на прошлом уроке приложение было подобно любительским фотоснимкам: вроде бы все есть, но у профессионалов все равно получается лучше. Сегодня Андрей Боровский расскажет, что отличает мастера от ремесленника

Если уж я делаю машину времени, то почему бы не придать ей стильный вид?
Доктор Эмет Браун, «Назад в будущее!»
Структуры данных и алгоритмы могут работать вместе только потому, что они ничего не знают друг о друге.
Алекс Степанов (один из архитекторов стандартной библиотеки C++).

Присутствие стиля желательно не только для самодельной машины времени. В прошлый раз мы написали KDE-приложение «images», обладающее всеми основными признаками настоящей программы для малого или домашнего офиса. В этот раз мы займемся шлифовкой программы images и добавим в неё некоторые полезные возможности, которыми обладает каждая (ну или почти каждая) программа KDE. Вносить улучшения мы начнем с той области, в которой наличие или отсутствие стиля обычно сразу же бросается в глаза — с интерфейса программы.

В прошлый раз мы конструировали интерфейс программно, таким образом, что элементы интерфейса, соответствующие определенным в программе действиям, добавлялись с помощью создания соответствующих объектов явным образом с последующим вызовом метода plug(). В результате интерфейс нашей программы был жестко (хотя и с возможностью настройки пользователем) зашит в ее код. Windows-программисту такое положение дел могло бы показаться естественным, но в Unix этот подход не считается слишком удачным. При жестком «приваривании» интерфейса к коду программы нарушается один из основных архитектурных принципов Unix — принцип разделения «движка» и «оснастки». Согласно этому принципу все части программы, которые можно изменить, не нарушая принципов ее работы, следует вынести за пределы программы и настраивать отдельно. Примеры реализации принципа разделения движка и оснастки можно найти на всех уровнях Unix-системы. На фундаментальном уровне этот принцип выражается в строгом разделении алгоритмов и данных. Код программы должен содержать только алгоритмы, все настройки и данные следует вынести в отдельные файлы. Примером разделения на более высоком уровне является система X Window, которая содержит описание механизмов работы графического интерфейса, но не навязывает программам-клиентам какой-либо конкретный набор визуальных элементов (таких наборов существует довольно много и все они, в принципе, независимы от X-сервера). На уровне программирования интерфейса KDE-программы принцип разделения выражается в том, что описание интерфейса (оснастка) может храниться и настраиваться отдельно от самой программы (движок). Мы уже знаем, что пиктограммы кнопок могут храниться в собственных графических файлах. То же самое касается и других медиа-ресурсов, например, звуковых. Но это не все. Описание визуальных элементов-контейнеров, содержащих другие элементы интерфейса, также может храниться в отдельном файле. Примерами визуальных контейнеров, описание которых может храниться отдельно от двоичного файла программы, являются главное меню, контекстные меню и панели быстрого доступа.

Файлы, хранящие описания элементов интерфейса KDE, имеют расширение rc. Если вы откроете один из таких файлов (их можно найти, например, в директориях $KDEDIR/share/apps/<имя_приложения>), то увидите, что это файлы в формате XML (файлы rc являются частью специальной модели KDE, именуемой XMLGUI). Мы научимся создавать файлы описания интерфейса на примере программы images. Из метода images::setup_Controls() мы удалим все строки, связанные с созданием и отображением меню и панели быстрого доступа. Оставим только вызовы

KAction * command = new KAction(...);

и

command->setToolTip(...):

Далее откроем вкладку Automake Manager (по умолчанию она припаркована у правого края главного окна KDevelop), Раскроем группу «Данные в rc» и найдем файл imagesui.rc (как нетрудно догадаться, имя файла сгенерировано по шаблону <имя_приложения>ui.rc). Этот файл создается автоматически в процессе генерации проекта, но по умолчанию он пустой и заполнить его мы должны сами. Далее следует текст файла imagesui.rc, который можно ввести с помощью текстового редактора:

<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
<kpartgui name="images" version="1">
<MenuBar>
 <Menu name="proc_menu"><text>&amp;Processing</text>
  <Action name="bw_command" />
  <Action name="contrast_command" />
  <Action name="intensity_command" />
 </Menu>
</MenuBar>
<ToolBar>
  <Action name="bw_command" />
  <Action name="contrast_command" />
  <Action name="intensity_command" />
</ToolBar>
</kpartgui>

Корневым тэгом документа является тэг <kpartgui>. Модификации подлежат атрибуты name и version этого тэга. Структура главного меню определяется парным тэгом <MenuBar>. Тэг <name> позволяет задать внутренне имя ниспадающего меню, а тэг <text> — название, которое пользователь увидит на экране. Сущность & (амперсанд) указывает, как и обычно, подчеркнутый символ для быстрого вызова команды с помощью Alt. Для того, чтобы наполнить ниспадающее меню Processing командами мы добавляем серию непарных тэгов Action, которые, как вы наверное догадались, соответствуют объектам KAction (действиям) нашей программы. Атрибут name содержит имя объектадействия, соответствующее его имени в коллекции actionCollection. Команды будут добавлены в меню в том же порядке, в котором перечислены тэги Action. Тэг <ToolBar> позволяет нам определить структуру панели инструментов. Формат у этого тэга такой же, как и у тэга <Menu>, за исключением того, что у панели инструментов нет видимого имени (а невидимое можно и не указывать, если мы не обращаемся к объекту панели).

На этом работа над описанием интерфейса нашей программы закончена. В прошлый раз нам не требовалось вызывать метод plug() для стандартных объектов-действий KDE. Не требуется указывать их и в файле *ui.rc. Файлы описания интерфейсов позволяют создавать и другие элементы меню и панелей инструментов — горячие клавиши, всплывающие подсказки, разделители, выравнивание и т. п. Более подробную информацию вы сможете найти на сайте KDE (см. врезку).

Для того, чтобы приложение могло воспользоваться файлом *ui.rc, этот файл должен быть размещен в одной из специальных директорий. Программа не увидит неправильно расположенный файл даже если он будет у нее под носом — в рабочем каталоге. Наше KDE-приложение ожидает найти файл *ui.rc либо в директории $KDEDIR/share/apps/<имя_приложения>, либо в локальной директории ~/.kde/share/apps/<имя_приложения>. Файл imagesui.rc будет автоматически установлен в первую директорию, если вы воспользуетесь командой KDevelop «Сборка|Установить (с правами root)».

Возможно, у вас возник вопрос — а зачем, собственно, все это нужно. В качестве ответа я предлагаю вам вспомнить, как легко можно изменить внешний вид приложения KDE с помощью менеджера тем. Менеджер тем может модифицировать файлы *ui.rc, например, припарковать меню Help у правого края окна, как это принято в Motif. Несмотря на то, что исходные тексты приложений KDE доступны, интерфейс программы будет проще настраивать, если его описание вынесено в отдельный файл простого формата. Мы можем доработать и метод установки пиктограмм для кнопок панели инструментов.

(thumbnail)
Рисунок 1. Automake manager

Программа images способна считывать файлы пиктограмм из своей рабочей директории (что она и делала в прошлом примере), однако в KDE существуют специальные правила расположения и наименования этих файлов. Следуя этим правилам, вы не только приобретете репутацию профессионального разработчика, но и позволите другим пользователям настраивать внешний вид панели быстрого доступа с помощью все того же менеджера тем KDE. Сравните простоту изменения облика KDE-приложений, в которых данные об интерфейсе вынесены в легко читаемые файлы, с решением аналогичной задачи для ОС Windows, где программам, модифицирующим интерфейс, приходится получать доступ к приложению различными недокументированными и несанкционированными путями.

Я надеюсь, что я вас убедил. Тогда приступим к редактированию иконок. Имя файла иконки, содержащейся в KDE-проекте, должно начинаться либо с префикса hi, либо с префикса lo, в зависимости от количества цветов. За префиксом следует число, указывающее размер иконки — 16 для 16x16, 22 для 22x22 и т. д.

Далее через дефис следует название контекста иконки (смысловой группы, к которой принадлежит иконка) — action для иконки обозначающий действие, app — для иконки приложения, mime — для значка MIME, device — для значка устройства. Затем, снова через дефис, следует название самой иконки. Например, пиктограмма для приложения images в проекте KDE может носить имя hi32-app-images.png (наименование пиктограммы должно совпадать с именем приложения), а иконка для команды «Настроить контрастность» — hi32-action-contrast. png (чаще всего иконки хранятся в файлах PNG, но можно использовать и другие растровые форматы, поддерживаемые KDE). Во время установки приложения система модифицирует имя иконки. Например, иконка hi32-action-contrast.png переименовывается в contrast.png и помещается в директорию $KDEDIR/share/icons/hicolor/32x32/actions. Иконки также могут устанавливаться в директории $KDEDIR/share/apps/<имя_приложения>, $KDEDIR/share/apps/<имя_приложения>/pics и ~/.kde/share/apps/<имя_приложения>. Для того, чтобы добавить иконку в наш проект, раскроем группу «Данные о пиктограммах» на вкладке Automake Manager (вы, наверное, уже поняли, что Automake Manager (рис. 1) — важнейший инструмент KDevelop после редактора исходных текстов) и выберем команду контекстного меню «Добавить пиктограмму…». Перед нами откроется диалоговое окно добавления пиктограммы (рис. 2) в котором можно выбрать размеры и тип файла. Если в поддиректории src директории проекта KDE не существует файла с выбранным именем, в этой поддиректории будет создан пустой графический файл.

(thumbnail)
Рисунок 2. Окно добавления пиктограммы

В исходный текст images также нужно внести некоторые изменения. В новом варианте программы вызов конструктора объекта-действия «contrast_command» будет выглядеть так:

command = new KAction(i18n("&Contrast"),
          QIconSet(BarIcon("contrast")), 0, this,
          SLOT(procContrast()),
          actionCollection(), "contrast_command");
(thumbnail)
Рисунок 3. Обновленная программа «Images».

Для загрузки графического файла мы используем стандартную функцию BarIcon(), которой передаем только имя иконки. Функция сама будет искать соответствующий файл в одной из стандартных директорий (но не в рабочей директории программы). Теперь модифицировать пиктограммы нашего приложения стало не трудно, чем мы и воспользуемся (рис. 3).

Обмен данными с помощью DCOP

Пора закончить издевательства над пользовательским интерфейсом и заняться расширением функциональности images. Разработчики объектно-ориентированной оболочки KDE не устояли перед искушением создать объектно-ориентированный механизм межпроцессного обмена данными и автоматизации. Протокол DCOP (Desktop COmmunication Protocol) построен на основе стандартного протокола межпроцессного взаимодействия X Window Inter Client Exchange. В клиент-серверной модели DCOP все программы, с которыми работает пользователь, являются клиентами. Роль сервера DCOP выполняет специальный демон, который осуществляет диспетчеризацию запросов и передачу данных между программами. С точки зрения разработчика модель DCOP напоминает распределенные объектные модели COM и CORBA. Приложение-поставщик сервисов DCOP экспортирует интерфейсы своих классов. Методы этих интерфейсов могут быть вызваны другим приложением посредством специального механизма.

В проекте приложения images уже содержатся элементы, необходимые для того, чтобы превратить это приложение в источник данных DCOP. Откройте файл imagesiface.h. В этом файле вы найдете объявление класса imagesIface, который является потоком класса DCOPObject.

class imagesIface : virtual public DCOPObject
{
 K_DCOP
public:
k_dcop:
 virtual void show_Image(const QPixmap data) = 0;
};


Класс DCOPObject — базовый для всех, экспортирующих интерфейсы DCOP. Макрос K_DCOP указывает препроцессору KDE, что данный класс содержит описания интерфейса. Перед заголовками методов интерфейса следует ставить еще один макрос — k_dcop. Класс ImagesIface экспортирует один метод — show_Image(). Данный метод получает объект класса QPixmap и выводит переданное в нем изображение в окно программы. Благодаря методу show_Image() другие программы смогут выводить изображение с помощью программы images. Поскольку imagesIface — всего лишь интерфейс, метод show_Image() объявлен в нем как виртуальный метод без реализации (pure virtual, в некоторых кругах, вероятно, сказали бы «чисто виртуальный метод»). Если теперь вы посмотрите на объявление класса imagesView, то увидите, что этот класс наследует ImagesIface. Именно в классе imagesView следует поместить реализацию метода show_Image(). Добавить реализацию просто. В объявлении класса imagesView, в разделе public напишите:

void show_Image(const QPixmap data);

Теперь щелкните правой кнопкой мыши по этому имени и в контекстном меню выберите команду «Генерировать член класса». В файле imagesview.cpp должна появиться заготовка метода, в которую мы впишем одну строчку кода:

void imagesView::show_Image(const QPixmap data)
{
  setPixmap(KPixmap(data));
}

Как видим, все очень просто. В конструктор класса imagesView следует добавить код, регистрирующий классы приложения на сервере DCOP (при этом регистрируются сразу все классы, в которых присутствует макрос K_DCOP):

DCOPClient * client = kapp->dcopClient();
client->attach();
client->registerAs("images");

Прежде всего, нам нужен объект класса DCOPClient. Этот объект предоставляется нам главным объектом приложения (объектом класса KApplication), к которому мы можем получить доступ из методов любых наших классов с помощью переменной kapp. Метод attach() объекта DCOPClient связывает наше приложение с сервером DCOP, а метод registerAs() регистрирует приложение (регистрировать нужно только те программы, которые экспортируют интерфейсы DCOP, но не те, которые только вызывают методы других объектов). Зарегистрированное имя images будет использоваться другими приложениями для доступа к нашей программе. Теперь наша программа images может быть источником сервисов DCOP.

Для того чтобы проверить механизм DCOP в работе, напишем программу, использующую экспортируемый метод show_image(). На диске вы найдете программу iclient, созданную в KDevelop на основе заготовки Simple KDE Application. Логика работы этой программы очень проста. Щелчок кнопки открывает диалоговое окно, в котором нужно выбрать графический файл. Выбранный файл считывается и по протоколу DCOP передается для отображения в предварительно запущенной программе images. В конструкторе главного и единственного визуального элемента, — кнопки KPushButton, мы связываем программу iclient с сервером DCOP:

DCOPClient *client = kapp->dcopClient();
client->attach();

Программа iclient не экспортирует никаких сервисов, так что метод registerAs() вызывать не нужно. В обработчике сигнала clicked, методе button_Click(), мы выполняем загрузку файла и вызываем метод show_Image() приложения images:

void iclient::button_Clicked()
{
  QString fn = KFileDialog::getImageOpenURL(NULL, (QWidget *) this, i18n("Open Image")).path();
  QPixmap pixmap;
  pixmap.load(fn);
  QByteArray param;
  QDataStream dataStream(param, IO_WriteOnly);
  dataStream << pixmap;
  DCOPClient * client = kapp->dcopClient();
  if (!client->send("images*", "imagesIface", "show_Image(QPixmap)", param))
    kdDebug() << "DCOP error\n";
}

Разбор листинга мы начнем с конца. Метод send() класса DCOPClient осуществляет обращение к серверу DCOP. Первый параметр метода send() — имя приложения-поставщика сервисов DCOP (имя, под которым приложение зарегистрировалось на сервере DCOP). Поскольку сервер по умолчанию добавляет к имени приложения его PID (почему — мы увидим ниже), мы используем «звездочку», чтобы указать, что мы обращаемся к любому (точнее — к каждому) приложению, у которого зарегистрированное имя начинается с «images». Второй параметр — имя интерфейса, к которому мы хотим обратиться (подчеркнем, что указывается имя интерфейса, а не класса реализации), третий параметр — имя вызываемого метода, записанное так же, как записывается имя сигнала или слота при вызове QObject::connect(). Четвертый параметр представляет собой массив значений параметров вызываемого метода. Значением этого параметра должен быть объект класса QByteArray. Для того, чтобы записать значение параметров (в нашем случае — единственного параметра объекта класса QPixmap) в QByteArray, мы создаем объект класса QDataStream (поток данных), указываем этому объекту, что приемник данных — объект param касса QByteArray, и с помощью оператора << записываем значение объекта pixmap в этот поток.

(thumbnail)
Рисунок 4. Окно «Параметры проекта».

Чтобы скомпилировать программу iclient, необходимо добавить в проект ссылку на библиотеку libkio.so. Эта библиотека является составной частью KDE, однако по умолчанию она не подключается к проекту Simple KDE Application. Для подключения библиотеки мы воспользуемся диалоговым окном «Параметры проекта» (команда «Проект|Параметры проекта»). В этом окне, на вкладке «Параметры make» (рис. 4), можно указать значения переменных для make-файла, в том числе значение переменной LIBS. Для подключения к проекту библиотеки libkio.so добавим значение

LIBS = -lkio

Теперь можно компилировать программу.

Что произойдет, если в системе уже запущено какое-то другое приложение с именем images? Ничего страшного не случится. Данные получит только тот экземпляр программы по имени images, в котором присутствует объект-потомок класса imagesIface и метод show_Image(QPixmap). Такое совпадение маловероятно, так что даже если на сервере DCOP одновременно зарегистрированы два приложения images, данные будут переданы только нашему приложению, но не другому. Сервер передает данные всем экземплярам приложения images, в которых существуют соответствующий объект и метод. Попробуйте запустить несколько экземпляров images, а затем передайте серверу DCOP изображение с помощью программы iclient. Вы увидите, что каждый экземпляр images получил копию картинки. Для того, чтобы данные направлялись только одному экземпляру, необходимо указывать полное DCOP-имя приложения, включающее PID. Впрочем, можно регистрировать приложение и не указывая PID. Для этого в тексте конструктора imagesView заменим вызов.

client->registerAs("images");

на

client->registerAs("images", FALSE);

Теперь приложение будет зарегистрировано под именем «images».

Браузер DCOP

(thumbnail)
Рисунок 5. Браузер DCOP.

Отладка пар приложений, работающих совместно, представляет собой более сложный процесс, чем отладка самостоятельного приложения. При возникновении сбоя в обмене данными между разными приложениями трудно сразу определить, какая из программ виновна в ошибке. При отладке распределенных приложений очень полезно иметь инструмент, представляющий собой эталонную, заведомо верную реализацию клиентской или серверной части. Имея в своем распоряжении такой инструмент, мы можем добиться правильной работы по крайней мере одного компонента системы, а затем использовать этот компонент для отладки второго. В случае отладки приложений, обменивающихся данными с помощью DCOP, мы располагаем эталонным инструментом, позволяющим убедиться в том, что приложение, зарегистрировавшее свой интерфейс на сервере DCOP, работает правильно. Функции отладки нам предоставляет браузер DCOP (DCOP Browser), рис. 5.

Программу-браузер можно запустить с консоли командой kdcop. В окне браузера мы сначала увидим список всех приложений пользователя, зарегистрированных на сервере DCOP. Щелкнув по значку приложения, мы получим список экспортируемых им DCOP-интерфейсов. Если мы запустили приложение images до запуска браузера, мы увидим в списке и его имя. Мы также можем запустить интересующее нас приложение после запуска браузера и воспользоваться кнопкой «Обновить». Щелкнув по имени интересующего нас интерфейса (в нашем случае — imagesIface), мы получим список его методов. Мы можем использовать эти методы для передачи данных экспортирующему приложению прямо из окна браузера. Если мы щелкнем по имени метода, откроется окно ввода данных для параметров вызываемого метода (число полей в окне ввода будет соответствовать числу параметров). В браузере DCOP мы можем передавать не только строковые и численные значения параметров, но и создавать более сложные структуры данных. Например, для того, чтобы создать экземпляр класса QPixmap, нам надо просто указать имя графического файла, из которого будет считано изображение. После того как мы заполним все поля ввода и щелкнем кнопку OK, будет вызван соответствующий метод DCOP-приложения с установленными нами значениями параметров. Таким образом, мы можем проверить, действительно ли приложение images обрабатывает запросы правильно. Если с images все в порядке, мы можем приступить к отладке iclient.

Механизм DCOP — не единственный инструмент интеграции приложений KDE. В следующей статье мы рассмотрим другой мощный инструмент интеграции — подключаемые модули.

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