# 3.3. Структура скелета исходного кода

Чтобы понять и использовать концепцию того, как работает приложение KDE, в первую очередь мы должны очень внимательно изучить структуру скелета исходного кода, сгенерированную KAppWizarg. Как мы уже видели, у нас есть набор файлов заголовков и кода, которые реализуют исходный код приложения, и делают его готовым к выполнению. Таким образом, простейший путь объяснить код - пройти строка за строкой код так, как он обрабатывается в процессе выполнения программы до входа в главный цикл событий и перехода в режим ожидания ввода пользователя. Затем мы рассмотрим, как реализуются функции взаимодействия с пользователем и как некоторые вещи работают. Это, наверное, лучший путь объяснить, как работают элементы окна (framework), и, поскольку это стандартно для большинства приложений KDE, такое объяснение позволит нам читать исходный код других проектов; кроме того, мы узнаем, где что необходимо изменить, чтобы заставить свое приложение работать так, как от него требуется.

## 3.3.1. Функция `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&{;&};`; затем мы вернемся и поясним функционирование менеджмента сессий.

## 3.3.2. Запуск приложения пользователем

В ветке `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` и заполняет рабочую область окна; обычно этот объект выглядит как область редактирования текста.

### 3.3.2.1. Конструктор

Давайте взглянем на код конструктора и рассмотрим, как создается экземпляр объекта:

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

Затем, все части приложения, которые необходимы, созданы вызовом соответствующих функций-членов, которые специфичны для нашего приложения.

- initMenuBar(): создает панель меню,

- initToolBar(): создает панель инструментов ,

- initStatusBar(): создает строку статуса,

- initKeyAccel(): устанавливает все клавиши быстрого доступа(accelerators) для нашего приложения согласно глобальной конфигурации, определенной приложением,

- initDocument(): создает объект документа для окна приложения,

- initView(): создает главный элемент, заполняющий наше окно,

- readOptions(): считывает все специфичные для приложения установки из конфигурационного файла и инициализирует оставшиеся части приложения, такие как список последних файлов, положение панелей и размеры окна.

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

### 3.3.2.2. Панель меню

Как показано выше, панель меню 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` для получения более детальной информации; о том, как расширить возможности, также см. Конфигурирование панелей меню и панелей инструментов.

### 3.3.2.3. Панель инструментов

Создание панелей инструментов даже проще, чем панелей меню. `KTMainWindow` предоставляет готовую панель инструментов, которая создается автоматически при вызове конструктора, а вы можете легко создать еще несколько. На панель инструментов нужно только добавить кнопки для выполнения тех функций, которые вы желаете реализовать:

     toolBar()->insertButton(Icon("filenew.xpm"), ID_FILE_NEW, true, i18n("New File") );

Это добавляет прижатую к левому краю кнопку с иконкой "filenew.xpm" и соответствующим идентификатором на панель инструментов. Третий параметр определяет, разрешена или запрещена кнопка; по умолчанию мы устанавливаем его равным `true`, потому что наш метод `disableCommand()` в конце конструктора сделает необходимые нам установки автоматически для входов меню и панелей инструментов. Наконец, последний параметр используется для так называемых "всплывающих подсказок" ("Quick-Tip") - когда пользователь располагает указатель мыши над кнопкой так, что она подсвечивается, появляется окошко с сообщением, содержание которого можно определить этим параметром.

Наконец, все кнопки панели инструментов соединяются с нашим методом `commandCallback()` по сигналу `clicked()`. По сигналу `pressed()`, мы предоставляем пользователю возможность получить соответствующую подсказку в строке статуса.

Дополнительная информация:

Поскольку панели инструментов создаются на основе класса `KToolBar`, вы можете посмотреть соответствующую документацию. С помощью `KToolBar` можно реализовать множество вещей, необходимых для панели инструментов, например задержка перед выпадением меню, если по вашей кнопке вызывается выпадающее меню; кнопки типа "выпадающий список". По умолчанию панель инструментов заполняет всю ширину окна, что весьма удобно и красиво, если используется только одна панель. Когда их больше, вы должны продумать их размеры, так как другие панели могут быть показаны в той же строке, что и первая, под панелью меню. Мы обсудим некоторые вопросы разработки и расширения возможностей панелей инструментов в разделе Конфигурирование панелей меню и панелей инструментов.

### 3.3.2.4. Строка статуса

Строка статуса, как и панели, предоставляется экземпляром `KTMainWindow`, поэтому мы должны только вставить в нее свои составляющие. По умолчанию, сгенерированное приложение содержит только один элемент для отображения подсказки в строке статуса. Для многих приложений этого может быть не достаточно. Поэтому вы должны добавить необходимые элементы, например, координаты и т.п.

Кроме того, приложение может иметь только одну активную строку статуса, как и панель меню. Если вы хотите создать их несколько, вы должны создать их по отдельности и установить текущую строку вызовом соответствующего метода `KTMainWindow`. В строку статуса также можно вставлять видимые элементы, которые могут быть использованы для красивого отображения индикаторов прогресса, как это делает KDevelop. За дальнейшей информацией обращайтесь к описанию класса `KStatusBar` в документации по классам.

### 3.3.2.5. Горячие клавиши (keyboard accelerator)

Добравшись до метода `initKeyAccel()`, мы уже создали стандартные объекты главного окна приложения - панель меню, панели инструментов и строку статуса. Неужели мы не создали ни одной горячей клавиши, с помощью которых опытный пользователь, который желает работать с клавиатурой, будет иметь быстрый доступ к определенным командам, использующимся наиболее часто в процессе работы с нашей программой. Чтобы сделать это, мы можем использовать встроенные горячие клавиши, созданные при разработке, например, меню, но KDE предоставляет хорошее решение для создания и поддержания пользовательских горячих клавиш. Множество пользователей хотят, чтобы они были конфигурируемыми, с одной стороны, а с другой стороны, стандартные горячие клавиши должны быть одинаковыми для различных приложений. Центр управления KDE предоставляет конфигурирование стандартных горячих клавиш глобально, используя класс `KAccel`. Кроме того, библиотеки KDE содержат элемент, который позволяет пользователю легко конфигурировать специфические для приложения горячие клавиши локально. Поскольку наше приложение использует только меню, выполняющие стандартные действия, такие как "New" или "Exit", они устанавливаются с помощью метода `initKeyAccel()`. Стандартные действия должны быть только связаны с соответствующей комбинацией клавиш. Мы должны добавить их, вначале указав наименование стандартного действия, а затем выполняемой функции. Поскольку все наши горячие клавиши определены в меню, мы должны изменить их для входов выпадающих меню. Затем мы вызываем `readSettings()`, который считывает текущие установки корневого окна KDE, содержащего конфигурацию стандартных горячих клавиш, потом установки для них, определенные в конфигурационном файле приложения. Когда мы продвинемся дальше в разработке нашего примера, мы поговорим также о том, как конфигурировать специфические для приложения горячие клавиши через диалоговое окно, см. Конфигурирование панелей меню и панелей инструментов об этой части процесса разработки.

### 3.3.2.6. Модель Документ-Просмотр (Document-View Model)

Следующие два вызова функций-членов, `initDocument()` и `initView()`, завершают построение той части окна приложения, которую предполагается представить пользователю: интерфейс для работы с данными, которые должно обрабатывать приложение; это еще одна причина, по которой наше приложение состоит из трех классов, `*App`, `*View` и `*Doc`. Чтобы понять пользу такой структуры, немного отвлечемся от кода и ознакомимся с теорией, а затем снова вернемся к программе, чтобы увидеть, как KDevelop поддерживает теоретическую модель.

Вообще говоря, все, что было сказано о нашем приложении, это что нам необходим экземпляр приложения, содержащий главное окно. Это окно имеет возможность предоставить пользователю базисный интерфейс - оно содержит меню, панель инструментов и строку статуса, а также механизм обработки ввода пользователя. Кроме того, оно содержит область, описанную как "просмотр" ("view"). В общем, назначение этой области - показывать данные, которыми сможет манипулировать пользователь, например, часть большого текстового файла. Хотя текстовый файл, вероятно, больше, чем наша область может показать на экране, она предоставляет возможность пользователю перейти к той части документа, которую он хочет видеть (это и есть "просмотр"). Здесь же пользователь может редактировать файл. Чтобы дать программисту возможность наилучшим образом разделить части приложения при реализации кода, была разработана модель Документ-Просмотр. Хотя она и не является стандартом, она предлагает способ, как приложение должно работать:

- Приложение содержит управляющий объект (controller object),

- объект Просмотр, отображающий данные, с которыми работает пользователь,

- и объект Документ, который в действительности содержит обрабатываемые данные.

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

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

Эта модель Документ-Просмотр имеет некоторые преимущества - она разделяет программный код более объектно-ориентированно, и, таким образом, предоставляет большую гибкость, например, один и тот же объект Документ может отображаться в нескольких объектах Просмотр одновременно; это может происходить как в различных окнах, так и в рамках одного окна, разделяя его рабочую область на несколько элементов Просмотр.

Если вы работали в MS-Windows, у вас может быть некоторый опыт такой работы - MFC предоставляет модель документа, готовую к использованию. Для KDE и Qt приложений дело обстоит несколько по иному. Qt - мощный инструмент, который предоставляет наиболее необходимые классы и видимые элементы. Но он не заботится о реализации модели Документ-Просмотр, и поскольку KDE унаследована от Qt , нет никаких оснований вводить в ней такую модель. Есть какой-то смысл в том, что традиционное X-приложение не работает с многодокументным интерфейсом (MDI, Multiple Document Interface). Каждое главное окно отвечает за содержащиеся в нем данные и, таким образом, уменьшает потребность в модели Документ-Просмотр, поскольку все методы работы с документом реализованы в видимом элементе. Единственное исключение на текущий момент - проект KOffice, который предоставляет полный набор офисных приложений, таких как текстовый процессор, электронные таблицы и т.д. Технически это реализовано внесением двух изменений в традиционный способ использования Qt и KDE:

- KOffice использует KOM и бесплатную (free) реализацию MICO из CORBA для межобъектного взаимодействия,

- приложения KOffice используют модель Документ-Просмотр, чтобы позволить всем приложениям работать с объектами данных KOffice.

Но, поскольку сейчас KDevelop ориентирован на использование текущих библиотек KDE 1.1.x и Qt 1.4x, мы не можем использовать данную модель по определению - она появится в более поздних версиях KDE 2, которая, как предполагается, будет содержать два основных изменения по отношению к текущей -

1. MDI-интерфейс для KTMainWindow

2. KOM библиотеки, которые реализуют модель Документ-Просмотр.

Таким образом, в настоящее время разработчик приложений может либо реализовать все необходимые методы объекта Документ в объекте Просмотр, или попытаться воспроизвести модель Документ-Просмотр самостоятельно. KDevelop поддерживает такую реализацию, предоставляя все необходимые классы и основные методы, которые обычно используются в модели Документ-Просмотр в шаблонах приложений для Qt и KDE.

Возвращаясь к коду, сейчас вы понимаете цели использования двух методов, которые мы упоминали в начале данного раздела: функции `initDocument()` и `initView()`. `initDocument()` создает объект документа, который представляет данные окна приложения и инициализирует базисные атрибуты, такие как бит модификации, отображающий наличие изменений в текущих данных. Затем метод `initView()` создает видимый элемент `*View`, связанный с документом, и вызывает метод `setView()` `KTMainWindow`, чтобы сообщить окну `*App`, что необходимо использовать элемент `*View` как Просмотр.

Для разработчика необходимо знать, что в процессе разработки он должен:

- переопределить виртуальные методы для обработки событий мыши и клавиатуры, предоставляемые `QWidget`, в объекте `*View`, чтобы реализовать способы управления данными.

- переопределить `paintEvent()` класса `QWidget` в объекте `*View` для перерисовки (repaint()) области просмотра после изменения,

- доработать реализацию печати документа в методе печати объекта `*View`,

- научить объект `*Doc` считыванию и записи файлов,

- добавить реализацию структуры данных документа к объекту `*Doc`, который логически представляет данные документа в памяти.

- добавить все методы, которые должны быть доступны пользователю через горячие клавиши, меню и панели инструментов.

### 3.3.2.7. Конфигурирование приложения

Теперь, после того, как мы создали все элементы, инициируемые экземпляром `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()`, если значение не пусто.

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

### 3.3.2.8. Выполнение

В предыдущем разделе мы рассматривали только то, что происходит при вызове конструктора экземпляра нашего `KScribbleApp`, готовящего для нас главное окно. После возвращения в функцию `main()` мы должны вызвать метод `show()` для того, чтобы отобразить окно. Чем отличаются `KApplication` и `QApplication` здесь, так это тем, что мы используем `KTMainWindow` как экземпляр нашего главного элемента (main widget), но мы не устанавливали его с помощью `setMainWidget()`. Это делает `KTMainWindow` самостоятельно, и мы не должны заботиться об этом.

Единственное, что осталось - это обработать аргументы командной строки. Если `int argc` > 1, то пользователь вызвал приложение в виде `kscribble имя&_;файла&_;для&_;открытия`. Поэтому мы просим наше приложение открыть файл `имя&_;файла&_;для&_;открытия`, вызывая метод `openDocumentFile()` с аргументом, содержащим имя файла.

Последняя строка в `main()` выполняет уже известную нам работу: она запускает на выполнение экземпляр приложения, и программа входит в главный цикл событий.

Ранее, в разделе Функция main(), мы решили рассмотреть только обычный запуск приложения, и не анализировали ветку `if( app.isRestored() )`. Следующий материал является введением в менеджмент сессий, и объясняет, как приложения используют этот механизм.

## 3.3.3. Введение в Менеджмент сессий (Session Management)

Как мы уже говорили, функция `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`, если есть файл для восстановления. Поскольку мы сохраняем документ в файл для восстановления только в том случае, если он был изменен, мы устанавливаем бит "модифицирован" непосредственно, чтобы показать, что информация не была сохранена в надлежащий файл. Также мы должны позаботиться, чтобы файл для восстановления имел имя, отличное от имени открытого оригинала. Таким образом, мы должны переустановить имя и путь для старого файла. Наконец, мы извлекли всю информацию для восстановления, какую хотели, и можем удалить временный файл.

Подведение итогов:

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