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

LXF83:Qt/KDE

Материал из Linuxformat
(Различия между версиями)
Перейти к: навигация, поиск
(Новая: == Интегрированное совершенство == '' '''ЧАСТЬ 6''' Созданное нами на прошлом уроке приложение было подобн...)

Версия 16:37, 10 марта 2008

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

ЧАСТЬ 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>&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 доступны, интерфейс программы будет проще настраивать, если его описание вынесено в отдельный файл простого формата. Мы можем доработать и метод установки пиктограмм для кнопок панели инструментов.

Программа 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 не существует файла с выбранным именем, в этой поддиректории будет создан пустой графический файл.

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

command = new KAction(i18n(“&Contrast”),
          QIconSet(BarIcon(“contrast”)), 0, this,
          SLOT(procContrast()),
          actionCollection(), “contrast_command”);

Для загрузки графического файла мы используем стандартную функцию 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 в этот поток. Чтобы скомпилировать программу 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

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

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