Next Previous Table of Contents
KAppWizard, также называемый KDE Application Wizard, предназначен для того, чтобы помочь вам начать работу над новым проектом в KDevelop. Таким образом, для начала любого вашего проекта вы должны использовать KAppWizard. Потом вы сможете модифицировать и дополнять автоматически сгенерированный скелет кода. KAppWizard позволяет выбрать один из нескольких типовых проектов, в зависимости от предназначения вашего приложения:
В этой главе мы увидим, как можно использовать KAppWizard и что должно быть сделано для создания проекта KDE приложения. Это будет первый шаг нашего пути, где мы создадим исходную версию примера проекта. Для всех остальных типов проектов шаги почти такие же, только вам предоставляется меньше опций для выбора.
Чтобы начать создание вашего первого KDE приложения, откройте KDevelop. Затем выберите пункт "New..." из меню "Project". Запустится KAppWizard, и вы увидите дерево на первой странице, содержащее типы проектов. При выборе типа в правой части окна можно увидеть, как будет выглядеть приложение после работы KAppWizard. Выберите ветвь KDE, тип Normal (Обычное приложение KDE). После этого нажмите кнопку "Next" внизу первой страницы KAppWizard. Это приведет к переходу на следующую страницу, где вы сможете установить основные опции проекта.
Для нашего примера мы выберем имя проекта KScribble
; введите это в поле "Projectname". Затем выберите
каталог, в котором вы хотите разместить свой проект; по умолчанию это ваш домашний каталог. Вы можете ввести путь вручную или
нажать кнопку справа от поля для выбора каталога через диалоговое окно.
Теперь вы должны ввести номер версии. Для первой версии установите номер 0.1
. Это обычный номер для нового приложения, которое находится в стадии разработки.
Для таких приложений присваиваются номера, меньшие 1, а стартовая версия вашего приложения будет иметь только исходный набор фреймов, поэтому назовите ее
0.1.
Наконец, введите свое имя в поле "Author" и ваш email адрес. Остальные опции можете оставить с их значениями по умолчанию.
Для получения дополнительной информации обо всех других опциях, вы можете нажать правую кнопку мыши над соответствующим полем и выбрать пункт меню quick-help. Откроется окно помощи с кратким описанием назначения опции. Это:
Теперь переключаемся на следующую страницу нажатием кнопки "Next" для создания шаблона файла заголовка вашего проекта.
Страница шаблонов заголовков позволяет вам автоматически включать предисловие в ваш файл шаблона, содержащее имя файла, дату, год, copyright, ваше имя и email адрес. Вы не должны изменять текст, набранный заглавными буквами, самостоятельно, KAppWizard сделает это автоматически и добавит шаблон в ваш проект, вы сможете его использовать в дальнейшем для создания новых файлов.
Вторая часть шаблона файла заголовка, предоставляемого по умолчанию, содержит информацию о лицензировании. Предполагается, что ваш проект попадает под действие GNU General Public License, которая также включается в дистрибутив. Эта лицензия используется, чтобы защитить ваш исходный код от любого, кто захочет его скопировать для своих собственных целей. General Public License предлагает вам эту лицензию и таким образом защищает ваши авторские права, это общепринято при распространении свободного программного обеспечения. Для получения дополнительной информации о лицензии вы можете прочитать COPYING файл в корневом каталоге вашего проекта (он будет туда занесен после завершения работы KAppWizard), который является копией GPL и уже входит в состав вашего приложения.
Вы можете выбрать другую лицензию или другой шаблон для использования с проектом. Также вы можете отредактировать предоставляемый по умолчанию шаблон. Для того, чтобы сделать это, вы должны вызвать шаблон в окно редактирования. Для очистки страницы по умолчанию выберите "New", для использования другого шаблона выберите "Load...", что откроет диалоговое окно выбора файла.
Когда вы закончите с этим, переходите к следующей странице, нажав "Next". Эта страница для создания шаблона вашего исходника. В общем она такая же, как и страница создания шаблона файла заголовка. Единственное отличие в том, что шаблоны используются для создания файлов реализации объявленных в заголовках функций.
Теперь, когда вы заполнили все опции для KScribble, выберите "Next" и нажмите кнопку "Generate" внизу окна KAppWizard. Если кнопка недоступна, значит, вы установили неправильное значение какой-либо опции. Для исправления ошибки вернитесь назад с помощью кнопки "Back".
После нажатия кнопки "Generate" вы должны увидеть, что KAppWizard начал работу - он копирует все шаблоны в каталог вашего проекта. Потом, когда KAppWizard завершит работу, кнопка "Cancel" заменится на "Exit". Ее нажатие приведет к выходу из генератора.
После этого шага вы завершаете процесс создания нового проекта. KDevelop загрузит его и предоставит возможность исследовать структуру, представив проект в виде дерева файлов или классов.
В следующем разделе мы опишем, как откомпилировать и запустить вашу первую версию KScribble, и обсудим, как организован исходный код.
После того, как наш проект сгенерирован, первое, что необходимо сделать, - просмотреть структуру кода, чтобы получить общее понимание того, как работает приложение. Это не только поможет нам начать разработку, а и в дальнейшем мы будем ориентироваться, где необходимо вносить изменения.
Когда мы откроем страничку LFV (Logical File Viewer) в левой части окна KDevelop, мы увидим несколько папок, в которых уже размещены файлы проекта в соответствии с их назначением. Первые две папки - "Headers" и "Sources". Папка заголовков логически содержит все файлы заголовков проекта, папка "Sources" - все исходники. Остальные папки сейчас для нас не интересны, их назначение мы рассмотрим позднее.
Две папки содержат следующие файлы:
Headers:
Sources:
Перед тем, как углубиться в исходный код, позволим KDevelop откомпилировать и запустить наше новое приложение. Чтобы сделать это, выберем "Make" из меню "Build".
или нажмем соответствующую кнопку на панели инструментов. В нижней части KDevelop откроется окно вывода,
и мы увидим сообщения, выдаваемые make
при работе. Они могут несколько отличаться от приводимых ниже,
в зависимости от используемых настроек.
1 Making all in docs
2 make[1]: Entering directory `/home/rnolden/Tutorial/kscribble1/kscribble/docs'
3 Making all in en
4 make[2]: Entering directory `/home/rnolden/Tutorial/kscribble1/kscribble/docs/en'
5 make[2]: Nothing to be done for `all'.
6 make[2]: Leaving directory `/home/rnolden/Tutorial/kscribble1/kscribble/docs/en'
7 make[2]: Entering directory `/home/rnolden/Tutorial/kscribble1/kscribble/docs'
8 make[2]: Nothing to be done for `all-am'.
9 make[2]: Leaving directory `/home/rnolden/Tutorial/kscribble1/kscribble/docs'
10 make[1]: Leaving directory `/home/rnolden/Tutorial/kscribble1/kscribble/docs'
11 make[1]: Entering directory `/home/rnolden/Tutorial/kscribble1/kscribble'
12 g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include -I/usr/X11R6/include -O0 -g -Wall -c
kscribbleview.cpp
13 g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include -I/usr/X11R6/include -O0 -g -Wall -c
kscribbledoc.cpp
14 g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include -I/usr/X11R6/include -O0 -g -Wall -c
kscribble.cpp
15 g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include -I/usr/X11R6/include -O0 -g -Wall -c
main.cpp
16 /usr/bin/moc ./kscribble.h -o kscribble.moc.cpp
17 g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include -I/usr/X11R6/include -O0 -g -Wall -c
kscribble.moc.cpp
18 /usr/bin/moc ./kscribbledoc.h -o kscribbledoc.moc.cpp
19 g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include -I/usr/X11R6/include -O0 -g -Wall -c
kscribbledoc.moc.cpp
20 /usr/bin/moc ./kscribbleview.h -o kscribbleview.moc.cpp
21 g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include -I/usr/X11R6/include -O0 -g -Wall -c
kscribbleview.moc.cpp
22 /bin/sh ../libtool --silent --mode=link g++ -O0 -g -Wall -o kscribble -L/opt/kde/lib -L/usr/X11R6/lib -rpath /opt/kde/lib
-rpath /usr/X11R6/lib kscribbleview.o kscribbledoc.o kscribble.o main.o kscribble.moc.o kscribbledoc.moc.o kscribbleview.moc.o
-lkfile -lkfm -lkdeui -lkdecore -lqt -lXext -lX11
23 make[1]: Leaving directory `/home/rnolden/Tutorial/kscribble1/kscribble'
Мы проставили номера строк напротив каждой строки, которых нет в выводе KDevelop. Это сделано для облегчения дальнейшего обсуждения того,
что происходит при компиляции. Во-первых, make
работает рекурсивно. Это значит, что она начинает с каталога, откуда была вызвана, и
затем в первую очередь заходит в подкаталог, возвращается и заходит в следующий подкаталог. Наконец, обрабатывается стартовый каталог
и make
завершается. make
вызывается из главного каталога проекта, содержащего исходники. В строках 1 и 2
мы видим, как make
входит в каталог docs
, затем в подкаталог en
. Так как там нечего делать, она
выходит из этих каталогов, пока не вернется в каталог исходников kscribble
в строке 11. Теперь начинается настоящая работа: make
вызывает компилятор, g++
, для компиляции файла-исходника kscribbleview.cpp
. Макрос -DHAVE_CONFIG_H
говорит о том, что должен быть использован файл
config.h
. Этот файл содержит макросы для определения платформы и приложения, и расположен в корневом каталоге проекта.
Следующие -I
команды добавляют пути для поиска файлов, указанных в команде "#include"
. Это
текущий каталог, корневой каталог проекта (-I..
), и пути к файлам KDE, Qt и заголовкам библиотек X11.
Эти пути определяются при выполнении скрипта configure
и устанавливаются в файлах Makefile; таким образом, компилятор
знает, где файлы расположены. Наконец, -O0
устанавливает оптимизацию в ноль (без оптимизации), -g
разрешает отладку, -Wall
устанавливает предупреждения компилятора в значение all
, и -c
говорит компилятору, что необходимо генерировать только файлы obj, то есть надо только откомпилировать файлы.
Это делается и для других файлов-исходников нашего проекта в строках 13-15. Очевидно, наш исходный код откомпилирован, но вместо того,
чтобы линковать объектные файлы исходников в конечный бинарный файл, мы видим несколько иные команды. В строке 16 программа "moc"
вызывается для обработки файла-заголовка kscribble.h
, выводя результат в kscribble.moc.cpp
. Затем, в строке 17, этот исходный файл
компилируется. Аналогичные операции проделываются с другими файлами заголовков проекта до строки 21. Это необходимо, поскольку Qt поддерживает механизм сигнал/слот,
но базируется на C++ реализации, и мы используем определенные ключевые слова, не поддерживаемые языком C++, такие как
signals:
и slots:
, в наших классах. Это дает нам возможность легко организовать межобъектное взаимодействие для всех
экземпляров классов, порожденных от QObject
, и избежать использования традиционного механизма callback указателей на функции.
Таким образом, приложению необходим исходный код, который обеспечивает данную функциональность, и вот для чего вызывается
moc
. Moc
- Meta Object Compiler набора инструментов Qt. Он реализует механизм сигнал/слот, обрабатывая файл заголовка и генерируя код, который
должен быть откомпилирован для получения бинарного файла. Поскольку проекты KDevelop используют automoc
для определения того, какие файлы заголовков должны быть обработаны, мы
не должны следить за вызовами moc
и C++ компилятора для обработки сгенерированных moc
файлов. Только запомните правило, что для использования
механизма сигнал/слот класс должен быть порожден от QObject
, или от любого другого класса, порожденного от QObject
; кроме того,
этот класс должен содержать макрос Q_OBJECT
(без ";"!) в начале своего объявления, и объявления сигналов и слотов.
Наконец, генерируется бинарный файл. Выходной бинарный файл называется kscribble
, линкер использует пути к библиотекам KDE и
X11, и линкует исходники с библиотеками kfile, kfm, kdeui, kdecore, qt, Xext
и X11
. На этом
make завершает свою работу.
Чтобы понять и использовать концепцию того, как работает приложение KDE, в первую очередь мы должны очень внимательно изучить структуру скелета исходного кода, сгенерированную KAppWizarg. Как мы уже видели, у нас есть набор файлов заголовков и кода, которые реализуют исходный код приложения, и делают его готовым к выполнению. Таким образом, простейший путь объяснить код - пройти строка за строкой код так, как он обрабатывается в процессе выполнения программы до входа в главный цикл событий и перехода в режим ожидания ввода пользователя. Затем мы рассмотрим, как реализуются функции взаимодействия с пользователем и как некоторые вещи работают. Это, наверное, лучший путь объяснить, как работают элементы окна (framework), и, поскольку это стандартно для большинства приложений KDE, такое объяснение позволит нам читать исходный код других проектов; кроме того, мы узнаем, где что необходимо изменить, чтобы заставить свое приложение работать так, как от него требуется.
main()
Поскольку приложение начинает свое выполнение входом в функцию main()
, она и будет стартовой точкой нашего исследования.
Функция main()
KScribble реализована в файле main.cpp
, а также может быть найдена с помощью
просмотрщика классов (Class Browser) в папке "Globals", подпапке "Functions":
1 #include "kscribble.h"
2
3 int main(int argc, char* argv[]) {
4 KApplication app(argc,argv,"KScribble");
5
6 if (app.isRestored())
7 {
8 RESTORE(KScribbleApp);
9 }
10 else
11 {
12 KScribbleApp* kscribble = new KScribbleApp;
13 kscribble->show();
14 if(argc > 1){
15 kscribble->openFile(argv[1]);
16 }
17 }
18 return app.exec();
19 }
Итак, что же происходит в первую очередь при создании объекта KApplication
, который получает третьим аргументом имя приложения KScribble?
Когда создается новый KApplication
, порождается новый экземпляр объекта KConfig
, который устанавливает связь с конфигурационным файлом,
$HOME/.kde/share/config/appname+rc, содержащим информацию, которую мы хотим использовать при открытии окна приложения. Имя,
которое мы передали конструктору app
, будет использовано позднее в заголовке окна.
По сравнению с примером кода, приведенным при преобразовании первого приложения Qt в приложение KDE, нынешний код имеет отличия.
После создания экземпляра KApplication
мы проверяем, как приложение вызвано: kwm
менеджером сессий или пользователем.
Это можно узнать, вызывая метод isRestored()
объекта app
, который возвращает true
для случая менеджера сессий
и false
при нормальном запуске.
Менеджмент сессий (session management) - одна из основных возможность приложений KDE, и она широко используется приложениями, но ее несколько долго объяснять.
Поэтому мы вначале последуем по ветке else{}
; затем мы вернемся и поясним функционирование менеджмента сессий.
В ветке else{}
создается экземпляр класса KScribbleApp
(строка 12). Этот объект вызывается, чтобы прорисовать себя, в строке
13, как обычно; строка 14 определяет, есть ли аргументы в командной строке. Если есть, то обычно это имя файла, поэтому
объект kscribble
открывает этот файл с помощью метода openFile()
.
Обратите внимание, что мы не вызывали метод setTopWidget(kscribble)
для нашего приложения - это уже сделано предками класса KScribbleApp
.
Теперь посмотрим на наш объект KScribbleApp
- что это такое и что он предлагает? Единственная вещь, которую мы знаем
на текущий момент, это то, что он должен быть видимым элементом (Widget) для представления пользовательского интерфейса в окне приложения. Обратимся к реализации класса
KScribbleApp
, которая находится в файле kscribble.cpp
, или нажмем мышкой на иконке класса в просмотрщике классов.
Экземпляр класса был создан с помощью конструктора. Во-первых, мы видим, что он порожден от класса KTMainWindow
, который является частью kdeui
. Этот класс, в свою очередь,
унаследован от QWidget
, таким образом, мы имеем нормальный видимый элемент в качестве верхнего окна (top-level window).
KTMainWindow
содержит массу функций, которые использует класс KScribbleApp
. Он предоставляет панель меню, панели инструментов, строку статуса и поддержку менеджмента сессий. Единственная вещь,
которую мы должны сделать, когда наследуем новый класс от KTMainWindow
, - создать все объекты, которые нам нужны, и создать другой элемент, который управляется нашим
экземпляром KTMainWindow
и заполняет рабочую область окна; обычно этот объект выглядит как область редактирования текста.
Давайте взглянем на код конструктора и рассмотрим, как создается экземпляр объекта:
1 KScribbleApp::KScribbleApp()
2 {
3 config=kapp->getConfig();
4
5
6 ///////////////////////////////////////////////////////////////////
7 // вызываем методы init для активации всех остальных частей конструктора
8 initMenuBar();
9 initToolBar();
10 initStatusBar();
11 initKeyAccel();
12 initDocument();
13 initView();
14
15 readOptions();
16
17 ///////////////////////////////////////////////////////////////////
18 // запрещаем меню и панели инструментов при старте
19 disableCommand(ID_FILE_SAVE);
20 disableCommand(ID_FILE_SAVE_AS);
21 disableCommand(ID_FILE_PRINT);
22
23 disableCommand(ID_EDIT_CUT);
24 disableCommand(ID_EDIT_COPY);
25 disableCommand(ID_EDIT_PASTE);
26 }
Мы видим, что наш экземпляр KConfig
, отвечающий за конфигурацию, сейчас указывает на конфигурацию приложения, поэтому мы сможем работать
с установками конфигурационного файла в дальнейшем.
Затем, все части приложения, которые необходимы, созданы вызовом соответствующих функций-членов, которые специфичны для нашего приложения.
Наконец, мы запрещаем несколько команд, которые пользователь может выдать, поскольку они не должны быть доступны в нынешнем состоянии приложения. Сейчас мы рассмотрели в общем как создается окно приложения, теперь мы углубимся в детали того, как создаются пользовательские элементы в упомянутых выше методах.
Как показано выше, панель меню KScribble создается методом initMenuBar()
. В нем мы создаем набор
QPopupMenu
, которые выпадают, когда пользователь выбирает пункт меню. Затем мы вставляем их в панель меню и подсоединяем к их входам функции, выполняемые при выборе.
В первую очередь, мы создаем наше recent_file_menu
, которое содержит названия последних 5 открытых файлов. Мы делаем это первым делом, потому что
это меню вставляется в file_menu
. Затем мы добавляем непосредственно связь - мы только что получили сигнал, который испущен
входом меню с определенным номером входа и вызываем slotFileOpenRecent( int )
, который затем вызывает нужный файл из списка последних открытых файлов.
Теперь мы создаем наше "File"-меню. Это будет меню, выпадающее из панели меню. Стандартные действия вставляются одно за другим в выпадающее меню - вначале команда создания нового файла, открытия файла, закрытия файла и т.д., наконец "E&xit" для закрытия приложения. Все входы меню должны быть созданы в том порядке, в каком они будут появляться в дальнейшем, поэтому мы должны следить за тем, что в каком месте необходимо расположить. Рассмотрим, например, следующие входы:
file_menu->insertItem(Icon("fileopen.xpm"), i18n("&Open..."), ID_FILE_OPEN );
file_menu->insertItem(i18n("Open &recent"), recent_files_menu, ID_FILE_OPEN_RECENT );
Первая команда вставляет вход "Open...". Поскольку мы хотим иметь его с иконкой, мы используем insertItem()
с именем иконки.
Чтобы понять процесс загрузки иконок, мы должны знать, где определен Icon()
. На самом деле это не метод, это макрос, предоставляемый
классом KApplication
:
#define Icon(x) kapp->getIconLoader()->loadIcon(x)
Кроме того, он использует внутри следующий макрос для получения доступа к объекту приложения:
#define kapp KApplication::getKApplication()
Это означает, что объект KApplication
уже содержит экземпляр загрузчика иконок - мы должны только получить к нему доступ; после этого он
загрузит соответствующую иконку. Поскольку все наши иконки входят в состав библиотек KDE, мы не должны заботиться ни о чем больше - они устанавливаются
в системе автоматически, следовательно, мы также не должны включать их в состав дистрибутива нашего приложения.
После параметра иконки (который не обязателен), мы вставляем вход меню с именем i18n("&Open...")
. Таким образом, мы должны разобраться с двумя вещами.
Во-первых, вход вставлен с использованием метода i18n()
. Как и Icon()
, это макрос, определенный в kapp.h
,
который вызывает KLocale
объект класса KApplication
для перевода входа на используемый язык:
#define i18n(X) KApplication::getKApplication()->getLocale()->translate(X)
Однако, кто-то может сказать "Я не хочу использовать макросы!" - вы можете делать это в большинстве случаев. Но в данной ситуации
вы должны использовать i18n()
, потому что процедура интернационализации приложения предполагает генерацию файлов,
содержащих то, что надо интернационализировать. А ход этого процесса зависит от строки i18n()
. Поэтому вы должны использовать макрос.
Как вы уже, наверное, догадались, амперсант внутри входа меню интерпретируется как подчеркивание следующей буквы в
тексте входа меню. Это позволяет осуществлять быстрый доступ к командам меню с клавиатуры, когда пользователь нажимает клавишу Alt
и одновременно
подчеркнутую букву.
Наконец, мы назначаем входу меню идентификатор, который является числом типа integer, по которому мы сможем найти вход меню в дальнейшем.
Чтобы иметь контроль над значениями используемых идентификаторов, они определяются как макросы. Все они собраны в файле resource.h
нашего проекта.
Для обеспечения однообразия, эти макросы все набраны заглавными буквами и начинаются с ID_, затем следует имя меню, а за ним имя входа. Это делает очень простым
определение смысла каждого входа, где бы он ни встретился в коде, и вы не должны возвращаться к коду панели меню для поиска входов.
Следующий пример входа показывает другой вариант метода insertItem()
. Здесь мы добавляем выпадающее меню recent_files_menu как
элемент меню. Это значит, что вход отобразится в виде переданной ему надписи "Open recent", а затем будет стоять стрелка вправо. При выборе входа
появится выпадающее меню со списком последних открытых файлов, и пользователь сможет из него выбрать необходимый файл.
Наконец, существует еще множество способов добавления нового входа меню - в данном случае все реализовано максимально просто. Более подробную
информацию можно найти в документации Qt, в описании класса QMenuData
.
Теперь, после создания выпадающих меню file_menu, edit_menu
и view_menu
, мы должны включить "Help"-меню. Мы можем
сделать это аналогично, но класс KApplication
предоставляет красивый и быстрый метод для этого:
help_menu = kapp->getHelpMenu(true, i18n("KScribble\n" VERSION ));
Это все, что мы должны сделать, чтобы получить меню помощи, которое содержит вход для вызова системы помощи с горячей клавишей
, окно "about" ("о программе...")
для приложения и аналогичное окно для KDE (которое может быть запрещено вызовом getHelpMenu(false,...);
). Содержимое наших окон
"about" определяется с использованием макроса i18n()
. VERSION использует макрос, определенный для номера версии проекта
в файле config.h
, поэтому мы не должны изменять это каждый раз вручную при выпуске нового релиза. Вы можете спокойно добавить в окне "about" любую
информацию о себе - ваше имя, email-адрес, copyright и др.
Теперь мы должны только вставить выпадающие меню в панель меню. Поскольку KTMainWindow
уже создал панель меню для нас, мы только
вставляем их, вызывая menuBar()->insertItem();
.
Все, что осталось сделать - соединить входы меню с методами, которые должны выполняться по их выбору. Следовательно, мы соединяем каждое выпадающее меню по
сигналу activated( int )
с методом commandCallback( int )
, который содержит конструкцию switch
, вызывающую соответствующие
методы для входов меню. Дополнительно, мы соединяем выпадающие меню по их сигналу highlighted( int )
для вывода помощи в строке статуса
по каждому входу. Когда бы пользователь ни перевел указатель мыши или фокус ввода клавиатуры на вход меню, строка статуса выведет соответствующую подсказку.
После того, как мы завершили с панелью меню, мы начнем разбираться с панелью инструментов. Это мы сделаем в следующей секции. Заметьте, что экземпляр
KTMainWindow
может иметь только одну видимую панель меню; таким образом, если вы хотите создать несколько панелей меню,
вы должны это делать раздельно с использованием экземпляров KMenuBar
, и установить одну из них в соответствующем методе KTMainWindow
как текущую
панель меню. См. документацию по классу KMenuBar
для получения более детальной информации; о том, как расширить возможности, также см.
Конфигурирование панелей меню и панелей инструментов.
Создание панелей инструментов даже проще, чем панелей меню. KTMainWindow
предоставляет готовую панель инструментов, которая создается автоматически при вызове конструктора,
а вы можете легко создать еще несколько. На панель инструментов нужно только добавить кнопки для выполнения тех функций, которые вы желаете реализовать:
toolBar()->insertButton(Icon("filenew.xpm"), ID_FILE_NEW, true, i18n("New File") );
Это добавляет прижатую к левому краю кнопку с иконкой "filenew.xpm" и соответствующим идентификатором на панель инструментов. Третий параметр определяет,
разрешена или запрещена кнопка; по умолчанию мы устанавливаем его равным true
, потому что наш метод disableCommand()
в конце
конструктора сделает необходимые нам установки автоматически для входов меню и панелей инструментов. Наконец, последний параметр используется для
так называемых "всплывающих подсказок" ("Quick-Tip") - когда пользователь располагает указатель мыши над кнопкой так, что она подсвечивается,
появляется окошко с сообщением, содержание которого можно определить этим параметром.
Наконец, все кнопки панели инструментов соединяются с нашим методом commandCallback()
по сигналу clicked()
. По сигналу
pressed()
, мы предоставляем пользователю возможность получить соответствующую подсказку в строке статуса.
Дополнительная информация:
Поскольку панели инструментов создаются на основе класса KToolBar
, вы можете посмотреть соответствующую документацию.
С помощью KToolBar
можно реализовать множество вещей, необходимых для панели инструментов, например задержка перед выпадением меню, если по вашей кнопке вызывается выпадающее меню;
кнопки типа "выпадающий список". По умолчанию панель инструментов заполняет всю ширину
окна, что весьма удобно и красиво, если используется только одна панель. Когда их больше, вы должны продумать
их размеры, так как другие панели могут быть показаны в той же строке, что и первая, под панелью меню. Мы обсудим некоторые
вопросы разработки и расширения возможностей панелей инструментов в разделе
Конфигурирование панелей меню и панелей инструментов.
Строка статуса, как и панели, предоставляется экземпляром KTMainWindow
, поэтому мы должны только вставить в нее свои составляющие.
По умолчанию, сгенерированное приложение содержит только один элемент для отображения подсказки в строке статуса. Для многих приложений
этого может быть не достаточно. Поэтому вы должны добавить необходимые элементы, например, координаты и т.п.
Кроме того, приложение может иметь только одну активную строку статуса, как и панель меню. Если вы хотите создать их несколько, вы должны
создать их по отдельности и установить текущую строку вызовом соответствующего метода KTMainWindow
. В строку статуса также можно
вставлять видимые элементы, которые могут быть использованы для красивого отображения индикаторов прогресса, как это делает KDevelop. За дальнейшей информацией обращайтесь к описанию класса
KStatusBar
в документации по классам.
Добравшись до метода initKeyAccel()
, мы уже создали стандартные объекты главного окна приложения - панель меню,
панели инструментов и строку статуса. Неужели мы не создали ни одной горячей клавиши, с помощью которых опытный пользователь, который желает
работать с клавиатурой, будет иметь быстрый доступ к определенным командам, использующимся наиболее часто в процессе работы с нашей программой. Чтобы сделать это, мы
можем использовать встроенные горячие клавиши, созданные при разработке, например, меню, но KDE предоставляет хорошее
решение для создания и поддержания пользовательских горячих клавиш. Множество пользователей хотят, чтобы они были конфигурируемыми, с одной стороны,
а с другой стороны, стандартные горячие клавиши должны быть одинаковыми для различных приложений. Центр управления KDE
предоставляет конфигурирование стандартных горячих клавиш
глобально, используя класс KAccel
. Кроме того,
библиотеки KDE содержат элемент, который позволяет пользователю легко конфигурировать специфические для приложения горячие клавиши
локально.
Поскольку наше приложение использует только меню, выполняющие стандартные действия, такие как "New" или "Exit", они устанавливаются с помощью метода
initKeyAccel()
. Стандартные действия должны быть только связаны с соответствующей комбинацией клавиш. Мы должны добавить их,
вначале указав наименование стандартного действия, а затем выполняемой функции. Поскольку все наши горячие клавиши
определены в меню, мы должны изменить их для входов выпадающих меню. Затем мы вызываем
readSettings()
, который считывает текущие установки корневого окна KDE, содержащего конфигурацию стандартных
горячих клавиш, потом установки для них, определенные в конфигурационном файле приложения.
Когда мы продвинемся дальше в разработке нашего примера, мы поговорим также о том, как конфигурировать специфические
для приложения горячие клавиши через диалоговое окно,
см.
Конфигурирование панелей меню и панелей инструментов об этой части процесса разработки.
Следующие два вызова функций-членов, initDocument()
и initView()
, завершают построение той части окна приложения, которую
предполагается представить пользователю: интерфейс для работы с данными, которые должно обрабатывать приложение; это еще одна причина,
по которой наше приложение состоит из трех классов, *App
, *View
и *Doc
.
Чтобы понять пользу такой структуры, немного отвлечемся от кода и ознакомимся с теорией, а затем снова
вернемся к программе, чтобы увидеть, как KDevelop поддерживает теоретическую модель.
Вообще говоря, все, что было сказано о нашем приложении, это что нам необходим экземпляр приложения, содержащий главное окно. Это окно имеет возможность предоставить пользователю базисный интерфейс - оно содержит меню, панель инструментов и строку статуса, а также механизм обработки ввода пользователя. Кроме того, оно содержит область, описанную как "просмотр" ("view"). В общем, назначение этой области - показывать данные, которыми сможет манипулировать пользователь, например, часть большого текстового файла. Хотя текстовый файл, вероятно, больше, чем наша область может показать на экране, она предоставляет возможность пользователю перейти к той части документа, которую он хочет видеть (это и есть "просмотр"). Здесь же пользователь может редактировать файл. Чтобы дать программисту возможность наилучшим образом разделить части приложения при реализации кода, была разработана модель Документ-Просмотр. Хотя она и не является стандартом, она предлагает способ, как приложение должно работать:
Возвращаясь к нашему примеру обработки текстового файла - наша модель должна работать так: Документ считывает содержимое файла и предоставляет методы для изменения данных и для сохранения файла. Объект Просмотр обрабатывает события, которые генерирует пользователь с помощью клавиатуры и мыши, и использует методы объекта Документ для обработки данных.
Наконец, управляющий объект отвечает за взаимодействие с пользователем, предоставляя ему доступ к объектам Документ и Просмотр, а также интерфейс для выдачи команд на открытие и закрытие документа. Кроме того, некоторые методы объекта Просмотр могут активироваться командами, поступающими от горячих клавиш, меню и панелей инструментов.
Эта модель Документ-Просмотр имеет некоторые преимущества - она разделяет программный код более объектно-ориентированно, и, таким образом, предоставляет большую гибкость, например, один и тот же объект Документ может отображаться в нескольких объектах Просмотр одновременно; это может происходить как в различных окнах, так и в рамках одного окна, разделяя его рабочую область на несколько элементов Просмотр.
Если вы работали в MS-Windows, у вас может быть некоторый опыт такой работы - MFC предоставляет модель документа, готовую к использованию. Для KDE и Qt приложений дело обстоит несколько по иному. Qt - мощный инструмент, который предоставляет наиболее необходимые классы и видимые элементы. Но он не заботится о реализации модели Документ-Просмотр, и поскольку KDE унаследована от Qt, нет никаких оснований вводить в ней такую модель. Есть какой-то смысл в том, что традиционное X-приложение не работает с многодокументным интерфейсом (MDI, Multiple Document Interface). Каждое главное окно отвечает за содержащиеся в нем данные и, таким образом, уменьшает потребность в модели Документ-Просмотр, поскольку все методы работы с документом реализованы в видимом элементе. Единственное исключение на текущий момент - проект KOffice, который предоставляет полный набор офисных приложений, таких как текстовый процессор, электронные таблицы и т.д. Технически это реализовано внесением двух изменений в традиционный способ использования Qt и KDE:
Но, поскольку сейчас KDevelop ориентирован на использование текущих библиотек KDE 1.1.x и Qt 1.4x, мы не можем использовать данную модель по определению - она появится в более поздних версиях KDE 2, которая, как предполагается, будет содержать два основных изменения по отношению к текущей -
Таким образом, в настоящее время разработчик приложений может либо реализовать все необходимые методы объекта Документ в объекте Просмотр, или попытаться воспроизвести модель Документ-Просмотр самостоятельно. KDevelop поддерживает такую реализацию, предоставляя все необходимые классы и основные методы, которые обычно используются в модели Документ-Просмотр в шаблонах приложений для Qt и KDE.
Возвращаясь к коду, сейчас вы понимаете цели использования двух методов, которые мы упоминали в начале данного раздела:
функции initDocument()
и initView()
. initDocument()
создает объект документа, который представляет
данные окна приложения и инициализирует базисные атрибуты, такие как бит модификации, отображающий наличие изменений в текущих данных.
Затем метод initView()
создает видимый элемент *View
, связанный с документом, и
вызывает метод setView()
KTMainWindow
, чтобы сообщить окну *App
, что необходимо использовать элемент *View
как Просмотр.
Для разработчика необходимо знать, что в процессе разработки он должен:
QWidget
, в объекте *View
, чтобы реализовать
способы управления данными.paintEvent()
класса QWidget
в объекте *View
для перерисовки (repaint()) области просмотра после изменения,*View
,*Doc
считыванию и записи файлов,*Doc
, который логически представляет данные документа
в памяти.
Теперь, после того, как мы создали все элементы, инициируемые экземпляром KTMainWindow
нашего приложения, мы должны
установить определенные атрибуты, значения которых влияют на внешний вид нашего приложения. Для этого мы вызываем readOptions()
, которые
считывает все значения и вызывает методы, устанавливающие соответствующие атрибуты. Библиотека KDE-Core содержит класс KConfig
, который предоставляет удобный
способ записи значений в конфигурационный файл, а также считывания их из файла снова. Поскольку каждый экземпляр KApplication
создает по умолчанию свой ресурсный файл, мы должны только получить доступ к этому файлу и создать наши значения.
KConfig
предоставляет нам объект файла.
А мы используем класс KConfigBase
для чтения и записи всех входов. Тогда как запись выполнить очень просто с помощью
метода writeEntry()
, чтение зависит от типа атрибута, который мы хотим инициализировать. Вообще говоря, все входы в конфигурационном файле
состоят из имени значения и значения. Значения, взаимосвязанные по смыслу, могут быть объединены в группы.
Поэтому мы должны задать имя группы до того, как обратимся к значению. Группа должна быть определена только однажды для чтения набора атрибутов,
входящих в нее. Давайте посмотрим, что мы хотим читать:
1 void KScribbleApp::readOptions()
2 {
3
4 config->setGroup("General Options");
5
6 // bar status settings
7 bool bViewToolbar = config->readBoolEntry("Show Toolbar", true);
8 view_menu->setItemChecked(ID_VIEW_TOOLBAR, bViewToolbar);
9 if(!bViewToolbar)
10 enableToolBar(KToolBar::Hide);
11
12 bool bViewStatusbar = config->readBoolEntry("Show Statusbar", true);
13 view_menu->setItemChecked(ID_VIEW_STATUSBAR, bViewStatusbar);
14 if(!bViewStatusbar)
15 enableStatusBar(KStatusBar::Hide);
16
17 // bar position settings
18 KMenuBar::menuPosition menu_bar_pos;
19 menu_bar_pos=(KMenuBar::menuPosition)config->readNumEntry("MenuBar Position", KMenuBar::Top);
20
21 KToolBar::BarPosition tool_bar_pos;
22 tool_bar_pos=(KToolBar::BarPosition)config->readNumEntry("ToolBar Position", KToolBar::Top);
23
24 menuBar()->setMenuBarPos(menu_bar_pos);
25 toolBar()->setBarPos(tool_bar_pos);
26
27 // initialize the recent file list
28 recent_files.setAutoDelete(TRUE);
29 config->readListEntry("Recent Files",recent_files);
30
31 uint i;
32 for ( i =0 ; i < recent_files.count(); i++){
33 recent_files_menu->insertItem(recent_files.at(i));
34 }
35
36 QSize size=config->readSizeEntry("Geometry");
37 if(!size.isEmpty())
38 resize(size);
39 }
Как мы уже видели в одном из предыдущих примеров первое действие, которое делает наш конструктор:
config=kapp->getConfig();
что устанавливает указатель config
типа KConfig
на конфигурацию приложения. Поэтому мы не должны заботиться о расположении
конфигурационного файла. Этот файл, согласно стандарту файловой системы KDE (KDE File System Standard, KDE FSS), располагается в
$HOME/.kde/share/config/
; подробнее файловую систему KDE мы рассмотрим позднее, когда будем обсуждать расположение файлов в дистрибутиве приложения.
Поскольку конфигурационный файл располагается в домашнем каталоге пользователя, каждый пользователь имеет свои настройки внешнего вида приложения.
Это не касается значений, определенных в общесистемном конфигурационном файле, который при необходимости может быть создан и инсталлирован
программистом в каталоге KDE.
Хотя это бывает полезно в некоторых случаях, мы должны избегать зависимости нашего приложения от существования
значения параметра в конфигурационном файле. Поэтому все методы чтения значений параметров, предоставляемые KConfigBase
, позволяют добавить значение по умолчанию, которое будет использовано,
если не будет найдено в файле. Другой момент, важный для программиста, это то, что конфигурационный файл представляет собой обычный текстовый файл,
поэтому необходимо обратить внимание:
Теперь, когда мы знаем теорию, начнем анализировать код. Как уже говорилось, мы должны использовать только наш указатель на конфигурацию для доступа к значениям.
В строке 4 мы делаем текущей группу "General Options". Это означает, что входящие в нее значения устанавливают какие-то общие атрибуты
приложения. Затем мы считываем значения для панели инструментов и строки статуса - они должны быть сохранены приложением при закрытии,
чтобы восстановить их при перезапуске программы. Поскольку панели могут быть либо включены, либо выключены, мы используем значения типа boolean и
вызываем метод readBoolEntry()
. Процесс идентичен для обеих панелей, поэтому мы рассмотрим только строки 7-10, чтобы понять,
что происходит с панелью инструментов. Во-первых, мы читаем значение во временную переменную bViewToolbar
в строке 7. Имя атрибута
в конфигурационном файле "Show Toolbar", и, если такое значение отсутствует (например, при первом запуске приложения),
задаем значение по умолчанию true
. Потом мы устанавливаем пометку для пункта меню, отвечающего за разрешение/запрещение показа панели инструментов:
мы вызываем setItemChecked()
для входа меню ID_VIEW_TOOLBAR
с нашим атрибутом. Наконец, мы говорим панели инструментов
установить требуемое значение. По умолчанию панель видима, поэтому мы должны что-то делать только в том случае, если bViewToolbar
равно false
.
Вызывая enableToolBar()
(строка 10), мы скрываем панель, если она запрещена.
Теперь мы должны прочитать положение панели. Поскольку пользователь может изменить положение панели, переместив ее с помощью мыши
в другое место, это должно быть, как и отображение панели, также сохранено. Посмотрев на классы KToolBar
и
KMenuBar
, мы видим, что положение панели может быть:
enum BarPosition {Top, Left, Bottom, Right, Floating, Flat}
Поскольку это значение записывается в числовом виде, мы должны считывать его с помощью метода readNumEntry()
и преобразовывать в значение положения.
С помощью setMenuBarPos()
и setBarPos()
говорим панелям, где отображаться.
Сейчас вы, возможно, вспомните, что наше меню "File" содержит подменю со списком последних открытых файлов. Имена файлов занесены в список
строк, который сохраняется при закрытии приложения и сейчас должен быть считан для восстановления меню. Во-первых, мы инициализируем список
с занесенными в него значениями строк, используя readListEntry()
. Затем в цикле for-
мы создаем входы меню для каждого элемента списка.
Наконец, мы должны позаботиться о размерах окна нашего приложения. Мы считываем его расположение в переменную QSize
, содержащую x
и y значения для ширины и высоты окна. Поскольку окно инициализируется KTMainWindow
, мы не должны заботиться
о значениях по умолчанию, и только используем метод resize()
, если значение не пусто.
Нам еще осталось объяснить в создании приложения запрет команд, которые не должны быть доступны, если не удовлетворяются определенные условия. Это сохранение файла и операции, которые используют буфер обмена. В течение жизни приложения мы должны об этом позаботиться несколько раз, но это достаточно просто, так как шаблон дает нам только два метода для разрешения/запрещения команд меню и кнопок панели инструментов.
В предыдущем разделе мы рассматривали только то, что происходит при вызове конструктора экземпляра нашего KScribbleApp
, готовящего для нас главное окно.
После возвращения в функцию main()
мы должны вызвать метод show()
для того, чтобы отобразить окно. Чем отличаются
KApplication
и QApplication
здесь, так это тем, что мы используем KTMainWindow
как экземпляр нашего
главного элемента (main widget), но мы не устанавливали его с помощью setMainWidget()
. Это делает KTMainWindow
самостоятельно,
и мы не должны заботиться об этом.
Единственное, что осталось - это обработать аргументы командной строки. Если int argc
> 1,
то пользователь вызвал приложение в виде kscribble имя_файла_для_открытия
. Поэтому мы просим наше приложение
открыть файл имя_файла_для_открытия
, вызывая метод openDocumentFile()
с аргументом, содержащим имя файла.
Последняя строка в main()
выполняет уже известную нам работу: она запускает на выполнение экземпляр приложения, и программа входит в
главный цикл событий.
Ранее, в разделе
Функция main(), мы решили рассмотреть только обычный запуск приложения,
и не анализировали ветку if( app.isRestored() )
. Следующий материал является введением в менеджмент сессий, и объясняет,
как приложения используют этот механизм.
Как мы уже говорили, функция main()
проверяет, запущено ли приложение пользователем или менеджером сессий. Менеджер сессий отвечает за
сохранение текущего состояния всех открытых окон приложения на рабочем столе и должен восстановить их, когда пользователь войдет в систему в следующий раз,
что значит, что приложение запускается не пользователем, а автоматически. Часть кода, которая выполняется:
6 if (app.isRestored())
7 {
8 RESTORE(KScribbleApp);
9 }
В разделе
Функция main() мы начинали с того, что проверяли метод вызова,
запрашивая app.isRestored()
. Затем выполняется строка 8. Она выглядит как простой оператор, но на самом деле это результат выполнения сложного
процесса, за которым мы проследим в этом разделе.
RESTORE() сам по себе - макрос, предоставляемый KTMainWindow
. Он содержит следующий код:
if (app.isRestored()){
int n = 1;
while (KTMainWindow::canBeRestored(n)){
(new KScribbleApp)->restore(n);
n++;
}
}
Это должно восстанавливать все окна приложения класса KScribbleApp
, создавая экземпляры и вызывая restore()
для
новых окон. Необходимо учесть, что, если ваше приложение использует несколько различных элементов, унаследованных от KTMainWindow
,
вы должны расширить макрос и определять тип верхнего элемента, используя KTMainWindow::classNameOfToplevel(n)
вместо класса
KScribbleApp
.
Метод restore()
затем считывает часть файла сессии, которая содержит информацию об окне. Так как KTMainWindow
запоминает все это для нас, мы не должны заботиться ни о чем больше. Только необходимо сделать так, чтобы специфичная для нашего экземпляра приложения информация
была доступна KScribbleApp
. Обычно это будет временный файл, созданный для занесения в него документа,
или другая инициализация, которая нам необходима. Для предоставления этой информации по восстановлению мы должны только переопределить два виртуальных метода
KTMainWindow
, saveProperties()
и readProperties()
.
Информация, которую мы хотим сохранить в конце сессии, - был ли модифицирован текущий файл и имя файла.
Если файл был модифицирован, мы должны создать временный файл и сохранить модифицированный документ в него.
В начале следующей сессии эта информация используется для восстановления содержимого документа:
void KScribbleApp::readProperties(KConfig*)
{
QString filename = config->readEntry("filename","");
bool modified = config->readBoolEntry("modified",false);
if( modified ){
bool b_canRecover;
QString tempname = kapp->checkRecoverFile(filename,b_canRecover);
if(b_canRecover){
doc->openDocument(tempname);
doc->setModified();
QFileInfo info(filename);
doc->pathName(info.absFilePath());
doc->title(info.fileName());
QFile::remove(tempname);
}
}
else if(!filename.isEmpty()){
doc->openDocument(filename);
}
setCaption(kapp->appName()+": "+doc->getTitle());
}
Здесь строка kapp->checkRecoverFile()
выглядит несколько странно, так как b_canRecover
не инициализирован. Это делается методом checkRecoverFile()
,
который устанавливает true
, если есть файл для восстановления. Поскольку мы сохраняем документ в файл для восстановления только в том случае, если он был изменен,
мы устанавливаем бит "модифицирован" непосредственно, чтобы показать, что информация не была сохранена в надлежащий файл. Также мы должны позаботиться,
чтобы файл для восстановления имел имя, отличное от имени открытого оригинала. Таким образом, мы должны переустановить имя и путь
для старого файла. Наконец, мы извлекли всю информацию для восстановления, какую хотели, и можем удалить временный файл.
Подведение итогов:
В этой главе вы узнали, как запускается приложение при нормальном старте и при восстановлении менеджером сессий. Мы прошли через весь код, разобравшись, как создаются части видимого интерфейса приложения, как инициализируются атрибуты по значениям, сохраненным в конфигурационном файле. Сейчас вы можете запустить приложение, чтобы проверить его функции и посмотреть, как реагирует окно программы на события.
Кроме исходного кода, проект KDevelop содержит множество других частей, интересных для разработчика. Это:
Кроме API-документации, эти элементы проекта будут инсталлированы вместе с бинарными файлами приложения.
Чтобы проект был как можно более открытым, вы должны согласовать эти его части с функциями приложения. Во-первых, вы должны отредактировать иконки.
Это даст вашему приложению уникальный идентификатор, с помощью которого пользователь сможет узнать ваше приложение в окне
менеджера меню. Файл .kdelnk - это файл, который добавляет ваше приложение в kpanel
в меню Applications
.
Он должен быть отредактирован - необходимо установить путь для инсталляции, что будет обсуждаться далее в этой книге. Наконец, документация, которую вы
предоставите пользователю, пишется в SGML. Это делает очень простым создание нескольких конечных файлов различных форматов из одного исходника. По умолчанию,
KDevelop предлагает создать HTML файлы из этого исходника, для KDE-проектов это делается автоматически с использованием ksgml2html
,
что добавляет классический KDE-вид документации. В последующих главах мы увидим, как редактировать файл SGML,
и что мы предоставим конечному пользователю для инсталляции.
Наконец, API-документация (Application Programming Interface) позволит вам и другим разработчикам быстро понять код
и использовать классы, не угадывая, для чего они предназначены. Мы научимся создавать API-документацию позднее.
На текущий момент достаточно знать, что документация генерируется программой KDoc
, которая обрабатывает файлы заголовков и создает
HTML документ, поэтому все подлежащие включению в документацию комментарии должны располагаться в заголовках.
Next Previous Table of Contents