18.3. Управление сеансами.

В момент завершения сеанса X11, некоторые оконные менеджеры выдают запрос на подтверждение завершения сеанса. Если мы подтверждаем завершение сессии, то приложения, которые работали в этот момент, будут автоматически запущены в начале следующего сеанса, с теми же экранными координатами и, в идеале, в том же самом состоянии.

Компонент X11, который управляет сохранением и восстановлением сеанса называется менеджер сеанса (или, если хотите, менеджер сессии). Чтобы добавить в Qt-приложение возможность сохранения своего состояния, в момент завершения сессии, необходимо перекрыть метод QApplication::saveState(), в котором выполнять сохранение всех необходимых параметров.

Рисунок 18.6. Окно запроса подтверждения завершения сеанса в KDE.


Операционные системы Windows 2000/XP (и некоторые Unix-системы) предлагают иной механизм сохранения сеансов, который носит название гибернация (hibernation). Когда пользователь переводит систему в режим гибернации, то она просто скидывает дамп памяти на диск и на следующем запуске загружает его. В этом случае приложениям ничего не надо делать, им даже нет необходимости что либо "знать" о гибернации.

Момент завершения работы может быть перехвачен приложением, для этого надо перекрыть метод QApplication::commitData(). Это позволит сохранить любые несохраненные данные и запросить что либо у пользователя, если в этом возникнет необходимость. Это поведение реализуется одинаково на обеих платформах: X11 и Windows.

Мы исследуем поведение приложения, которое может взаимодействовать с менеджером сеанса, на примере программы "Крестики-нолики". Сначала рассмотрим функцию main():

int main(int argc, char *argv[]) { Application app(argc, argv); TicTacToe tic(0, "tic"); app.setTicTacToe(&tic); tic.show(); return app.exec(); } Здесь создается экземпляр класса Application, производный от класса QApplication. Этот класс перекрывает методы предка -- commitData() и saveState().

Затем создается виджет TicTacToe и выводится на экран. Виджету TicTacToe было присаоено имя "tic". Если вы хотите обеспечить взаимодействие программы с менеджером сеанса, то всем виджетам верхнего уровня должны быть присвоены уникальные имена.

Рисунок 18.7. Внешний вид приложения "Крестики-нолики".


Ниже приводится определение класса Application: class Application : public QApplication { Q_OBJECT public: Application(int &argc, char *argv[]); void setTicTacToe(TicTacToe *tic); void commitData(QSessionManager &sessionManager); void saveState(QSessionManager &sessionManager); private: TicTacToe *ticTacToe; }; Класс Application хранит указатель на виджет TicTacToe в приватной переменной. void Application::saveState(QSessionManager &sessionManager) { QString fileName = ticTacToe->saveState(); QStringList discardCommand; discardCommand << "rm" << fileName; sessionManager.setDiscardCommand(discardCommand); } На платформе X11, менеджер сеансов вызывает функцию saveState(), для сохранения состояния приложения. Она доступна и на других платформах, но никогда не вызывается. Аргумент типа QSessionManager позволяет взаимодействовать с менеджером сеансов.

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

rm file где file -- это имя файла, в котором сохраняется информация о текущем состоянии, а rm -- это стандартная команда Unix, выполняющая удаление файлов.

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

appname -session id_key где appname берется из argv[0], id -- идентификатор сессии, поставляемый самим менеджером. Менеджер сеансов гарантирует уникальность идентификатора для каждого экземпляра приложения. И наконец key -- дополнительная информация, содержащая время сохранения состояния приложения. По различным причинам, функция saveState() может вызываться несколько раз, на протяжении одной сессии, таким образом пара id и key гарантируют уникальность каждого из сохраненных состояний.

Из-за ограничений, существующих в менеджерах сеансов, путь к исполняемому файлу приложения должен быть прописан в переменной PATH. В данном конкретном случае, если вы пожелаете испытать приложение "Крестики-нолики", вы должны переписать исполняемый файл программы в каталог, скажем, /usr/bin, и запустить ее командой tictactoe.

Для простых приложений, таких как "Крестики-нолики", состояние может быть сохранено в виде аргумента командной строки, которая перезапускает приложение в начале следующей сессии, например:

tictactoe -state OX-XO-X-O В этом случае отпадает необходимость сохранения информации в отдельный файл и установки команды удаления файла. void Application::commitData(QSessionManager &sessionManager) { if (ticTacToe->gameInProgress() && sessionManager.allowsInteraction()) { int ret = QMessageBox::warning(ticTacToe, tr("Tic-Tac-Toe"), tr("The game hasn t finished.\n" "Do you really want to quit?"), QMessageBox::Yes | QMessageBox::Default, QMessageBox::No | QMessageBox::Escape); if (ret == QMessageBox::Yes) sessionManager.release(); else sessionManager.cancel(); } } Функция commitData() вызывается в момент завершения сеанса. Здесь выводится запрос на подтверждение завершения приложения, чтобы предотвратить потерю данных. По-умолчанию она закрывает все виджеты верхнего уровня, точно так же, как и в случае завершения приложения нажатием на кнопку "X", в заголовке окна. В Главе 3 мы уже демонстрировали, как перекрыть метод closeEvent() и вывести запрос на подтверждение.

В данном примере, мы перекрыли метод commitData() и выводим запрос на подтверждение из него, если менеджер сеанса позволяет это сделать. Когда пользователь щелкает по кнопке Yes, то вызывается функция release(), которая сообщает менеджеру сеанса о том, что он может продолжить процедуру завершения сессии. В противном случае процедура завершения сеанса будет остановлена, вызовом метода cancel().

Рисунок 18.8. Запрос на подтверждение завершения работы программы.


Теперь перейдем к рассмотрению класса TicTacToe: class TicTacToe : public QWidget { Q_OBJECT public: TicTacToe(QWidget *parent = 0, const char *name = 0); QSize sizeHint() const; bool gameInProgress() const; QString saveState() const; protected: void paintEvent(QPaintEvent *event); void mousePressEvent(QMouseEvent *event); private: enum { Empty = '-', Cross = 'X', Nought = 'O' }; void clearBoard(); void restoreState(); QString sessionFileName() const; QRect cellRect(int row, int col) const; int cellWidth() const { return width() / 3; } int cellHeight() const { return height() / 3; } char board[3][3]; int turnNumber; }; Класс TicTacToe порожден от QWidget и перекрывает методы предка: sizeHint(), paintEvent() и mousePressEvent(). Он так же реализует новые методы: gameInProgress() и saveState(), которые используются классом Application. TicTacToe::TicTacToe(QWidget *parent, const char *name) : QWidget(parent, name) { setCaption(tr("Tic-Tac-Toe")); clearBoard(); if (qApp->isSessionRestored()) restoreState(); } В конструкторе производится очистка игрового поля и, если приложение вызвано с ключом -session, то вызывается приватная функция restoreState(). void TicTacToe::clearBoard() { for (int row = 0; row < 3; ++row) { for (int col = 0; col < 3; ++col) { board[row][col] = Empty; } } turnNumber = 0; } Функция clearBoard() выполняет очистку ячеек игрового поля и записывает значение 0 в переменную turnNumber (номер хода). QString TicTacToe::saveState() const { QFile file(sessionFileName()); if (file.open(IO_WriteOnly)) { QTextStream out(&file); for (int row = 0; row < 3; ++row) { for (int col = 0; col < 3; ++col) { out << board[row][col]; } } } return file.name(); } В функции saveState() производится сохранение состояния игрового поля в файл. Формат файла очень прост -- на место крестика записывается символ 'X', на место нолика -- 'O' и на место пустой ячейки -- '-'. QString TicTacToe::sessionFileName() const { return QDir::homeDirPath() + "/.tictactoe_" + qApp->sessionId() + "_" + qApp->sessionKey(); } Функция sessionFileName() возвращает имя файла, которое соответствует текущему идентификатору и ключу сеанса. Эта функция вызывается как из saveState(), так и из restoreState(). void TicTacToe::restoreState() { QFile file(sessionFileName()); if (file.open(IO_ReadOnly)) { QTextStream in(&file); for (int row = 0; row < 3; ++row) { for (int col = 0; col < 3; ++col) { in >> board[row][col]; if (board[row][col] != Empty) ++turnNumber; } } } repaint(); } Функция restoreState() загружвет файл, в котором было сохранено предыдущее состояние приложения и заполняет игровое поле. Номер хода рассчитывается как сумма крестиков и ноликов на игровом поле.

Функция restoreState() вызывается в конструкторе класса TicTacToe, если QApplication::isSessionRestored() возвращает true. В этом случае, функции sessionId() и sessionKey() возвращают те же значения, с которыми было сохранено предыдущее состояние приложения. Отсюда и sessionFileName() вернет имя файла, соответствующее этой сессии.

Отладка взаимодействия с менеджером сеанса может оказаться занятием нудным и трудоемким, поскольку придется неоднократно перезапускать сессию. К счастью, в состав X11 входит утилита xsm. На запуске, эта утилита откроет окно менеджера сеанса и терминал. Приложения, запускаемые из терминала, будут использовать xsm, в качестве менеджера сеанса. После этого мы можем завершать и перезапускать сессии и следить за поведением отлаживаемого приложения. Дополнительные сведения по этой теме вы найдете по адресу: http://doc.trolltech.com/3.2/session.html.