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

LXF82:Qt/KDE

Материал из Linuxformat
Перейти к: навигация, поиск
Учебник Qt/KDE

Создаем стандартное KDE-приложение

ЧАСТЬ 5 Сегодня Андрей Боровский расскажет вам, как создаются настоящие KDE-приложения. Ну, или почти настоящие…

Если в чем-то уверен — проверь еще раз.
Девиз параноика.
(thumbnail)
Рисунок 1. Программа «images».

В прошлый раз мы научились создавать простейшие KDE-приложения, а также выполнять переводы приложений KDE на разные языки. Было показано, как можно заставить наше приложение использовать ресурсы другого приложения и как добавлять переводы к уже существующим программам. В принципе, изложенных навыков достаточно, чтобы заново выполнить перевод всей среды KDE (сделать, например, перевод с особым цинизмом). Однако, приложения из прошлой статьи не выполняли никакой полезной работы, в том числе потому, что у них отсутствовал сколько-нибудь развитый пользовательский интерфейс. В этой статье мы не будем заниматься переводами (если захотите, можете выполнить их сами), зато напишем «почти настоящее» приложение KDE, использующее меню, панель быстрого доступа и другие интерфейсные элементы, предоставляемые KDE. Наше приложение «почти» (а не совсем) настоящее потому, что оно все еще игнорирует некоторые важные функции среды KDE, однако его уже можно использовать в практических целях. Работающее приложение (исходные тексты вы найдете на диске) выглядит вполне серьезно (рис. 1). Программа позволяет просматривать графические файлы, а также применять к изображениям преобразования: из цветного в черно-белое, изменение контраста и интенсивности (соответствующая функция называется именно так — intensity).

(thumbnail)
Рисунок 2. Взаимодействие классов приложения.

Мы начнем разработку нашего приложения в среде KDevelop, используя заготовку Application framework (надеюсь, вы помните, как ее найти). Назовем наш проект images. В результате выполнения соответствующего мастера будет создана директория images с поддиректориями и много файлов .cpp, .h и других типов. Это обилие может напугать начинающего программиста, но на самом деле все не так страшно. Application framework представляет собой заготовку классического офисного приложения KDE. У него есть строка меню, панель быстрого доступа, строка состояния и центральная область, которая используется для ввода/вывода данных (в терминологии «офисного» программирования эта область называется представлением (view). Все эти элементы в нашем приложении уже присутствуют. Самые важные и интересные файлы для нас — images.h/cpp и imagesview.h/cpp. Эти файлы содержат определения классов images и imagesView соответственно. Имя первого класса совпадает с именем проекта и приложения, имя второго класса получено из первого добавлением View. Класс images является потомком класса KMainWindow, реализующего главное окно офисного приложения. Окно KMainWindow позволяет легко управлять такими элементами интерфейса как главное меню, панель инструментов, строка состояния и, конечно, представление. Представление реализовано в классе imagesView. Объект класса images выполняет роль главного визуального элемента для объекта app, класса KApplication. Объект app объявлен в файле main.cpp, в котором реализована функция main() нашего приложения (нам не нужно модифицировать этот файл). Объекты menuBar, toolBar и statusBar (все они являются членами класса KMainWindow) представляют соответственно строку меню, панель инструментов и строку состояния. Схема взаимодействия классов приложения не должна выглядеть очень сложной (рис. 2, под именами классов подписаны имена объектов этих классов, объявленных в классе images). Класс images выполняет роль связующего звена между элементами пользовательского интерфейса и представлением данных imagesView. Методы этого класса являются обработчиками событий интерфейса и вызывают соответствующие методы класса представления.

ЕДИНЫЕ ДЕЙСТВИЯ

Концепция единых действий должна быть хорошо знакома тем, кто программировал в Borland Delphi и C++ Builder. В приложениях с графическим интерфейсом, как правило, одну и ту же команду можно вызвать несколькими способами (из основного меню, с панели инструментов, из контекстного меню). При этом во всех формах вызова команды обычно используются одни и те же элементы – пиктограмма, всплывающая подсказка, и, конечно, функция-обработчик команды. До появления концепции единых действий (actions) программистам приходилось выполнять много повторяющейся работы: для каждого элемента интерфейса нужно было явным образом указывать пиктограмму, подсказку и обработчик команды.

Далее сложности нарастали: если в определенном состоянии программы команду требовалось заблокировать, необходимо было отслеживать состояния всех элементов интерфейса, связанных с этой командой. Как вы уже догадались, концепция единых действий существенно упрощает работу программиста. В рамках этой концепции все общие элементы команды объединяются в один объект (в случае KDE, объект класса KAction). Создав объект KAction для определенной команды, мы указываем объекту, в каких элементах интерфейса должен присутствовать вызов этой команды. Об остальном объект KAction позаботится сам. Если мы свяжем этот объект с одним из меню, будет создана строка меню, если с панелью инструментов – будет создана кнопка и т.п. Если в какой-то момент команда должна быть заблокирована, нам достаточно вызвать метод setEnabled(FALSE) для объекта KAction для того, чтобы все визуальные представления команды были заблокированы. Аналогично выполняется и разблокирование команды. Можно сказать, что объект KAction – это центр управления всеми элементами интерфейса, связанными с соответствующей командой.

Для того, чтобы открыть в KDevelop файлы исходных текстов, нужно открыть вкладку Группы файлов, расположенную у левого края главного окна среды, и выбрать в ней группу Sources. Перед вами появится список файлов исходных текстов. Откройте нужный файл щелчком мыши.

В файле imagesview.h внесите изменения в объявление класса imagesView так, чтобы он стал потомком класса QLabel (напомню, что класс QLabel способен выводить изображения). Класс imagesView является также потомком класса imagesIface, но эта «наследственная линия» нас сейчас не интересует, и мы оставим ее без изменений. Естественно, в файл imagesview.h следует добавить директиву

#include <qpixmap.h>

Удалим из объявления imagesView ненужные члены и добавим нужные. После этого объявление класса будет выглядеть так:

class imagesView : public QLabel, public imagesIface
{
Q_OBJECT
public:
imagesView(QWidget *parent);
virtual ~imagesView();
void toBlacknWhite();
void setContrast(int c);
void setIntensity(int i);
};

Помимо конструктора и деструктора у класса imagesView появились три новых метода. Эти методы выполняют преобразования изображения, хранящегося в imagesView. Метод toBlacknWhite() выполняет преобразование палитры из цветной в черно-белую, метод setContrast() позволяет настроить контрастность изображения, а с помощью метода setIntensity() можно изменить интенсивность.

Теперь перейдем в файл imagesview.cpp. Добавим в этот файл две директивы включения заголовочных файлов:

#include <kpixmap.h>
#include <kpixmapeffect.h>
Теперь приступим к реализации методов:
imagesView::imagesView(QWidget *parent)
: QLabel(parent),
DCOPObject("imagesIface")
{
setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
}
imagesView::~imagesView()
{
}
void imagesView::toBlacknWhite()
{
if (pixmap() == NULL)
{
kdDebug() << k_funcinfo << "Trying to modify an empty pixmap" <<
endl;
return;
}
KPixmap kpm(* pixmap());
KPixmapEffect::toGray(kpm, FALSE);
setPixmap(kpm);
}
void imagesView::setContrast(int c)
{
if (pixmap() == NULL)
{
kdDebug() << k_funcinfo << "Trying to modify an empty pixmap" <<
endl;
return;
}
KPixmap kpm(* pixmap());
KPixmapEffect::contrast(kpm, (c – 50)*5);
setPixmap(kpm);
}
void imagesView::setIntensity(int i)
{
if (pixmap() == NULL)
{
kdDebug() << k_funcinfo << "Trying to modify an empty pixmap" <<
endl;
return;
}
KPixmap kpm(* pixmap());
KPixmapEffect::intensity(kpm, (i-50)/10);
setPixmap(kpm);
}
АЛЬТЕРНАТИВЫ QT DESIGNER

В свое время Linux-разработчиками было начато немало проектов, предназначенных для расширения возможностей визуального программирования Qt. Некоторые из этих проектов (их еще можно найти на сайтах Qt Community) в идеале должны были приблизиться по удобству и простоте разработке к таким средствам, как Delphi. Однако, проверку временем и популярностью выдержали лишь некоторые из них, являющиеся простыми надстройками над Qt Designer. В вашем дистрибутиве Linux, скорее всего, присутствуют два таких альтернативных редактора пользовательского интерфейса: KDevelop Designer и Kommander. KDevelop Designer отличается от Qt Designer примерно также, как KPixmap от QPixmap, то есть, практически ничем. Стоит упомянуть также «динамический редактор диалогов» Kommander, который создает самостоятельные диалоговые окна и окна мастеров, для использования, например, в сценариях оболочки. В этом редакторе можно не только сконструировать окно со всеми дочерними визуальными элементами, но и проверить, как работают связки сигнал-слот в новом окне.

В конструкторе класса мы устанавливаем горизонтальное и вертикальное выравнивание для содержимого метки.

Для того, чтобы понять, как работают три метода, выполняющих преобразования изображения, рассмотрим классы KDE KPixmap и KPixmapEffect. Первый из этих классов представляет собой улучшенный вариант QPixmap, второй класс предназначен для применения различных эффектов к изображению, хранящемуся в KPixmap. Эффекты реализованы в виде статических методов класса KPixmapEffect. Методы модифицируют содержимое переданного им объекта (а не создают новый). Для выполнения обработки изображения нам приходится преобразовывать QPixmap в KPixmap. Фактически наш класс imagesView представляет собой модифицированный вариант QLabel, способный выполнять преобразования над загруженным изображением. Для вывода сообщения об ошибке (которая никогда не должна возникнуть) мы используем функцию kdDebug(), которая возвращает нам объект потока C++ для вывода отладочных сообщений (можно было бы воспользоваться и printf(), но это же все-таки C++!). Макрос k_funcinfo выводит информацию о функции, из которой был вызван. Функция kdDebug() и макрос k_funcinfo объявлены в заголовочном файле kdebug.h. В том же файле объявлен макрос k_lineinfo, который ограничивается вводом информации о файле строке исходного текста, в которой был вызван.

Прежде чем мы перейдем к написанию класса images, ответственного за поведение интерфейса нашей программы, нам понадобится создать один дополнительный элемент интерфейса — диалоговое окно для настройки параметров изображения. Это окно изображено на приведенном выше снимке экрана. Оно содержит горизонтальный ползунок (объект класса QSlider) и две кнопки — OK и Cancel. Будет логично, если для создания диалогового окна мы воспользуемся методами визуального программирования, и спроектируем окно в самой среде KDevelop, используя встроенный в нее редактор KDevelop Designer (см. врезку). В окне Automake Manager, щелкните правой кнопкой мыши по строке images (Программа в bin) и выберите команду Создать файл.

(thumbnail)
Рисунок 3. Окно создания нового файла.
(thumbnail)
Рисунок 4. Проект диалогового окна.

В открывшемся диалоговом окне (рис. 3) укажите имя нового файла — SettingsDialog и тип — Dialog (ui). После этого будет открыт редактор окон, подобный Qt Designer, однако для того, чтобы добраться до окна формы, придется «разгрести» множество служебных окон (при разрешении 1024x768 окно формы оказывается полностью «похороненным» под ними). Перенесите в окно формы две кнопки PushButton и ползунок Slider и объедините их в группы (рис. 4). Сигнал clicked() кнопки OK соедините со слотом accept() класса Form1 (для тех, кто забыл, напомним, что соответствующее окно вызывается командой Соединения… контекстного меню или с помощью клавиши F3). Сигнал clicked() кнопки Cancel следует соединить со слотом reject() класса Form1. Вызов слотов accept() и reject() приводит к закрытию диалогового окна. При этом значение, которое окно передает программе после своего закрытия, определяет, какая из кнопок была нажата.

Теперь вернитесь в Automake Manager, щелкните правой кнопкой мыши по имени файла SettingsDialog.ui и в контекстном меню выберите команду Создать или выбрать класс реализации….

На самом деле мы создаем не класс реализации, а наследуем от класса формы. В открывшемся окне введите имя нового класса — SettingsDialogImpl. Закройте окна созданных файлов settingsdialogimpl.h и settingsdialogimpl.cpp. В редакторе форм создайте новый слот (команда Слоты… контекстного меню) void sliderMoved(int i) и соедините его с сигналом sliderMoved(int) объекта slider1. Теперь закройте окно визуального редактора (не забыв сохранить файл SettingsDialog.ui) и откройте файл settingsdialogimpl.h. В объявление класса SettingsDialogImpl добавьте закрытую переменную _sliderpos и метод getSliderValue():

class SettingsDialogImpl: public Form1 {
Q_OBJECT
public:
SettingsDialogImpl(QWidget *parent = 0, const char *name = 0);
public slots:
int getSliderValue();
protected slots:
void sliderMoved(int i);
private:
int _sliderpos;
};

В файл settingsdialogimpl.cpp добавьте реализацию метода

getSliderValue()и слота sliderMoved():
void SettingsDialogImpl::sliderMoved(int i)
{
_sliderpos = i;
}
int SettingsDialogImpl::getSliderValue()
{
return _sliderpos;
}

На этом разработка диалогового окна закончена. В файл images. cpp необходимо добавить директиву

#include "settingsdialogimpl.h"

Теперь мы можем приступить к написанию, а точнее, к модификации класса images. Дело в том, что этот класс, связующее звено нашего приложения, довольно сложен и значительная его часть была сгенерирована автоматически, так что лучше всего трансформировать класс, созданный по умолчанию, в тот класс, который нам нужен, путем постепенных изменений. Что мы хотим изменить в классе images? Мы хотим удалить все ненужное. Наше приложение предназначено для редактирования существующих графических файлов, но не для создания новых, поэтому удалим метод fileNew() Кроме того, дабы не усложнять наш проект сверх меры, мы удалим все, что связано с выводом данных на печать, в том числе метод filePrint() и поле m_printer. На поле m_printer ссылается конструктор images, в него тоже нужно внести изменения. Удалим методы saveProperties() и readProperties() — они нам сейчас не нужны. Также следует удалить содержимое методов load(), fileSave() и fileSaveAs(), а сами методы — оставить. Если удаление всего ненужного выполнено грамотно, приложение должно скомпилироваться и запуститься. Возможно, вы заметили, что хотя код, выполняющий команды File|New и File|Print удален, пункт меню и кнопка быстрого доступа остались. Для того, чтобы удалить их, нужно прейти в метод setupActions() и удалить строки

KStdAction::openNew(this, SLOT(fileNew()), actionCollection());
KStdAction::print(this, SLOT(filePrint()), actionCollection());

Что делали две удаленные строки? Они добавляли в приложения два стандартных действия (см. врезку). Для стандартных действий система сама предоставляет пиктограмму, название и клавишу быстрого доступа. Статическим методам класса KStdAction передается указатель на класс, реализующий обработку команды, слот, обрабатывающий команду, а также указатель на коллекцию действий actionCollection (возвращается методом actionCollection()). Программа будет работать даже если слотов, связанных с действиями, не существует, только в стандартный поток вывода будет выведено предупреждающее сообщение. Такова динамическая природа модели сигнал/слот.

Обратите внимание, что мы не прибегаем к визуальному программированию при проектировании главного окна нашего офисного приложения. Дело в том, что для разработки главного окна визуальное программирование нам и не нужно, поскольку вид этого окна однозначно определен принципами построения приложения «офисного» типа.

Теперь подумаем о том, что следует добавить в класс images. В главную строку меню приложения мы добавим пункт Processing, который будет открывать подменю, содержащее команды обработки изображения To Black & White (в черно-белый), Contrast (контраст) и Intensity (интенсивность). Кнопки новых команд нужно добавить на панель быстрого доступа. Все эти изменения должны быть отражены в классе images, отвечающем за интерфейс. Ну и конечно, в images нужно добавить код, выполняющий новые команды. Именно в таком порядке мы и будем вносить изменения в класс.

Весь код, отвечающий за добавление новых элементов интерфейса, мы сгруппируем в новом методе setup_Controls(). В среде KDevelop новые методы классов можно добавить вручную, а можно — автоматически. Откройте вкладку Классы, расположенную с правой стороны главного окна KDevelop. Раскройте группу src, щелкните правой кнопкой мыши по классу images и в открывшемся контекстном меню выберите команду Добавить метод…. Откроется окно создания нового метода, похожее на окно создания нового слота в Qt Designer.

После добавления нового метода можно приступать к его реализации. В методе setup_Controls() мы создаем подменю Processing главного меню, добавляем разделительную полосу на панель быстрого доступа (просто для красоты), создаем и настраиваем три объекта действия (объекты классы KAction), и делаем соответствующие команды доступными в меню и на панели быстрого доступа. Рассмотрим все эти этапы подробнее.

void images::setup_Controls()
{
KPopupMenu * procMenu = new KPopupMenu(); //Processing
menuBar()->insertItem(i18n("&Processing"), procMenu, 0, 1);

Создаем меню Processing. Подменю главного меню должно быть объектом класса KPopupMenu. Для того, чтобы указать, что новое всплывающее меню принадлежит главному меню, мы вызываем метод insertItem() поля menuBar (к которому мы получаем доступ с помощью одноименного метода). Кроме указателя на объект класса KPopupMenu методу insertItem() передается имя нового меню, его идентификатор id и индекс — позиция в главной строке меню (значению 1 соответствует вторая позиция, после File). Идентификатор должен быть положительным числом. Если insertItem() передать отрицательное значение идентификатора, insertItem() вернет значение нового уникального идентификатора для меню.

toolBar()->insertLineSeparator(3);

Добавляем линию-разделитель

KAction * command = new KAction(i18n(“To &Black && White”),
QIconSet(QPixmap(“c1.xpm)), 0, this,
SLOT(proc_toBlacknWhite()),
actionCollection(), “bw_command”);

Создаем объект-действие для команды To Black & White. Файл c1.xpm содержит пиктограмму команды (этот файл должен быть доступен программе во время выполнения). Класс QIconSet преобразует переданный ему графический файл в набор пиктограмм различный размеров и форм (в том числе, в черно-белую пиктограмму для заблокированной команды). Слот proc_toBlacknWhite() будет содержать код обработки команды. Последний параметр — имя объекта-команды. Имена могут быть у всех объектов Qt/KDE, наследующих QObject, но это первый случай, когда они нам действительно пригодятся.

command->setToolTip(i18n(“Transforms image palette to grayscale”));
command->setEnabled(FALSE);

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

command->plug(procMenu);
command->plug(toolBar(), -1);

С помощью метода plug() мы указываем объекту действия, что соответствующая команда должна быть доступна в меню procMenu и на панели быстрого доступа.

Добавление двух остальных команд выполняется аналогично. Добавим вызов метода setup_Controls() в конструктор images после вызова setupGUI(). Теперь можно запустить программу и полюбоваться на новый интерфейс. Правда, слоты для новых команд еще не созданы, так что новые команды пока не работают. Прежде чем создать слоты, вернемся в конструктор images и добавим в него строки

actionCollection()->setHighlightingEnabled(TRUE);
connect(actionCollection(),SIGNAL(actionStatusText(const QString&)),statusBar(), SLOT(message ( const QString& )));
connect(actionCollection(),SIGNAL(clearStatusText()),statusBar(), SLOT(clear()));

после вызова statusBar()->show(). Это стандартная последовательность вызовов, предназначенная для того, чтобы всплывающие подсказки отображались в строке состояния. Теперь мы можем добавить слот proc_toBlacknWhite(), и слоты для двух других команд: proc_Contrast() и proc_Intensity(). В коде этих слотов (см. исходные тексты на диске) для нас нет почти ничего нового, отметим только один момент. Для того, чтобы разблокировать/заблокировать команды пользовательского интерфейса, нужно получить доступ к соответствующему объекту действия. Доступ к коллекции объектов мы получаем с помощью метода actionCollection(), а сами объекты можем найти по имени — строке, переданной конструктору Kaction.

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

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