Зрелый интерфейс на Qt: пояснения к коду
Содержание:
1. Код модуля;
2.Пояснения к коду (Вы читаете данный раздел).
В первую очередь упомяну вспомогательные функции. Они не относятся к «горячим клавишам» напрямую, но используются в механизме сохранения и загрузки сочетаний клавиш, связанных с меню.
> bool file_exists (const QString &fileName) - проверяет, существует ли указанный файл по имени fileName.
> QString qstring_load (const QString &fileName, const char *enc) - загружает содержимое текстового файла с именем fileName, в указанной кодировке enc.
> bool qstring_save (const QString &fileName, const QString &data, const char *enc) - сохраняет текстовые данные data в файле по имени fileName в кодировке enc.
> QHash hash_load_keyval (const QString &fname) - возвращает в виде строковой хэш-таблицы содержимое файла fname. Последний имеет обычный ini-подобный формат, где каждая строка представлена как ключ=значение. Следующие две функции понадобятся для виджета - поля ввода, где пользователь сможет задать сочетание клавиш (нажать в поле ввода сочетание, которое отобразится в поле ввода в виде Модификаторы+Клавиша; например, + ):
> QString mod_to_string (Qt::KeyboardModifiers k) - получает в качестве параметра клавиши=модификаторы k и возвращает их названия, т.е. «Alt», «Shift» и тому подобное. Если модификаторов несколько, то они перечисляются, разделенные знаком «плюс».
> QString keycode_to_string (int k) - возвращает буквенное обозначение клавиатурного кода k. По идее, эту функцию можно заменить встроенной в Qt: QKeySequence(k).toString(). Однако она в определенных случаях ведет себя странно.
Теперь о классах:
CShortcutEntry - это потомок QLineEdit; виджет, при помощи которого пользователь может ввести и увидеть сочетание клавиш. В функции keyPressEvent этого класса происходит преобразование кодов модификаторов и клавиш в текстовое представление вида:
CShortcuts - класс, который заведует всем, что относится к «горячим клавишам» и их связи с пунктами меню.
Экземпляр этого класса будет подключаться к экземпляру главного окна, т.к. именно он является владельцем пунктов меню, и с ним, этим окном, мы и будем работать. В описании класса главного окна объявим:
Потом в конструкторе класса главного окна создадим экземпляр CShortcuts, «подключим» к нему виджет главного окна и загрузим из файла настройки горячих клавиш:
shortcuts = new CShortcuts (this); // this --
указатель на главное окно
shortcuts->load_from_file (такое-то имя файла);
Всё, load_from_file берет на себя и загрузку связей пунктов меню с горячими клавишами, и «проход» по пунктам меню с одновременным назначением им этих сочетаний. Имя загруженного файла сохраняется в поле CShortcuts::fname и может быть использовано позже.
Для сохранения текущего набора горячих клавиш в файл используем:
Именно этот файл мы и загружаем в конструкторе главного окна. Разберем подробнее функцию загрузки - CShortcuts::load_from_file. Первые строки очевидны - проверка на существование файла:
Теперь надо загрузить файл с горячими клавишами в хэш-таблицу. Файл имеет простой формат, где каждая строка - «имя пункта меню=сочетание клавиш». Итак, объявим таблицу hash и загрузим в нее файл:
В следующей строке мы получаем список всех «детей» виджета w (это переданное в конструктор класса главное окно), причем являющихся экземплярами класса QAction, то есть пунктами меню:
Начинается прохождение по этому списку (по всем пунктам меню) в цикле: foreach (QAction *ac, a)
Для каждого пункта меню проверяется:
> Содержится ли его название в хэш-таблице.
> Если да, то проверяется, присвоено ли этому пункту меню сочетание клавиш.
> Если нет, то ему задается сочетание клавиш, взятое из хэш-таблицы.
Конечно, может понадобиться и другое поведение функции - например, чтобы сочетание клавиш задавалось в любом случае, даже если пункт уже имеет закрепленное за ним сочетание. Тогда потребуется соответствующим образом исправить код.
Осталось рассмотреть, как дать пользователю возможность самому назначить сочетание клавиш выбранному пункту меню. Здесь всё довольно просто - понадобятся два вид-жета. Первый - уже описанное выше строковое поле ввода CShortcutEntry (CShortcutEntry *ent_shtcut), в котором:
> Пользователь сможет вводить сочетание клавиш.
> При выборе пункта меню в списке, в поле ввода будет отображаться сочетание клавиш, назначенное пункту меню (если сочетание вообще назначено).
Второй виджет - экземпляр QListWidget (QListWidget *lv_menuitems). В него будет выводиться список с названиями пунктов меню. Чтобы получить такой список, достаточно воспользоваться функцией CShortcuts::captions_iterate(), которая заполняет внутренний для CShortcuts список (QStringList captions). Чтобы обновить содержимое QListWidget, надо будет вызывать примерно такой код:
Теперь необходимо связать выбор пункта меню в списке с отображением сочетания клавиш в виджете CShortcutEntry:
Сам обработчик выглядит так:
Здесь видно нахождение пункта меню (указателя на экземпляр QAction) по его названию - точнее, по надписи на пункте меню. За это отвечает функция:
QAction* CShortcuts::find_by_caption (const QString &text);
Существует еще ряд утилитных функций:
> QAction* CShortcuts::find_by_shortcut (const QString &shcut); - найти пункт меню по сочетанию горячих клавиш.
> QKeySequence find_seq_by_caption (const QString &text); - найти сочетание клавиш по надписи пункта меню.
> void set_new_shortcut (const QString &menuitem, const QString &shcut); - задать пункту меню с надписью menuitem новое сочетание клавиш shcut. Параметрами этой функции будут служить выбранный в списке пункт меню и сочетание клавиш из виджета CShortcutEntry. Вызов set_new_shortcut удобно поместить в обработчик нажатия какой-нибудь клавиши (например, «Задать»), по нажатию на которую будет задаваться сочетание клавиш для выбранного пункта меню.
Таким образом, у вас есть всё необходимое для оснащения своей программы возможностью назначать «горячие клавиши» со стороны пользователя. Единственный недостаток приведенного движка - идет привязка к конкретному языку интерфейса. Если вы назначили сочетание клавиш при русском интерфейсе, а потом переключили на английский, то «русские» назначения не будут подхвачены. Но воплощение «универсального» движка усложнило бы задачу - например, можно было бы для пунктов меню генерировать коды и эти коды связывать с клавишами.
1. Код модуля;
2.
В первую очередь упомяну вспомогательные функции. Они не относятся к «горячим клавишам» напрямую, но используются в механизме сохранения и загрузки сочетаний клавиш, связанных с меню.
> bool file_exists (const QString &fileName) - проверяет, существует ли указанный файл по имени fileName.
> QString qstring_load (const QString &fileName, const char *enc) - загружает содержимое текстового файла с именем fileName, в указанной кодировке enc.
> bool qstring_save (const QString &fileName, const QString &data, const char *enc) - сохраняет текстовые данные data в файле по имени fileName в кодировке enc.
> QHash hash_load_keyval (const QString &fname) - возвращает в виде строковой хэш-таблицы содержимое файла fname. Последний имеет обычный ini-подобный формат, где каждая строка представлена как ключ=значение. Следующие две функции понадобятся для виджета - поля ввода, где пользователь сможет задать сочетание клавиш (нажать в поле ввода сочетание, которое отобразится в поле ввода в виде Модификаторы+Клавиша; например,
> QString mod_to_string (Qt::KeyboardModifiers k) - получает в качестве параметра клавиши=модификаторы k и возвращает их названия, т.е. «Alt», «Shift» и тому подобное. Если модификаторов несколько, то они перечисляются, разделенные знаком «плюс».
> QString keycode_to_string (int k) - возвращает буквенное обозначение клавиатурного кода k. По идее, эту функцию можно заменить встроенной в Qt: QKeySequence(k).toString(). Однако она в определенных случаях ведет себя странно.
Теперь о классах:
CShortcutEntry - это потомок QLineEdit; виджет, при помощи которого пользователь может ввести и увидеть сочетание клавиш. В функции keyPressEvent этого класса происходит преобразование кодов модификаторов и клавиш в текстовое представление вида:
<Ctrl> + <Alt> + <A>CShortcuts - класс, который заведует всем, что относится к «горячим клавишам» и их связи с пунктами меню.
Экземпляр этого класса будет подключаться к экземпляру главного окна, т.к. именно он является владельцем пунктов меню, и с ним, этим окном, мы и будем работать. В описании класса главного окна объявим:
CShortcuts *shortcuts;
Потом в конструкторе класса главного окна создадим экземпляр CShortcuts, «подключим» к нему виджет главного окна и загрузим из файла настройки горячих клавиш:
shortcuts = new CShortcuts (this); // this --
указатель на главное окно
shortcuts->load_from_file (такое-то имя файла);
Всё, load_from_file берет на себя и загрузку связей пунктов меню с горячими клавишами, и «проход» по пунктам меню с одновременным назначением им этих сочетаний. Имя загруженного файла сохраняется в поле CShortcuts::fname и может быть использовано позже.
Для сохранения текущего набора горячих клавиш в файл используем:
CShortcuts::save_to_file (const QString &file_name)
Именно этот файл мы и загружаем в конструкторе главного окна. Разберем подробнее функцию загрузки - CShortcuts::load_from_file. Первые строки очевидны - проверка на существование файла:
if (! file_exists (file_name)) return;
Теперь надо загрузить файл с горячими клавишами в хэш-таблицу. Файл имеет простой формат, где каждая строка - «имя пункта меню=сочетание клавиш». Итак, объявим таблицу hash и загрузим в нее файл:
QHash hash = hash_load_keyval (file_name);
В следующей строке мы получаем список всех «детей» виджета w (это переданное в конструктор класса главное окно), причем являющихся экземплярами класса QAction, то есть пунктами меню:
QList a = w->findChildren ();
Начинается прохождение по этому списку (по всем пунктам меню) в цикле: foreach (QAction *ac, a)
if (hash.contains (ac->text()))
{
if (ac->shortcut().isEmpty())
ac->setShortcut (QKeySequence
(hash.value (ac->text())));
}
}
{
if (ac->shortcut().isEmpty())
ac->setShortcut (QKeySequence
(hash.value (ac->text())));
}
}
Для каждого пункта меню проверяется:
> Содержится ли его название в хэш-таблице.
> Если да, то проверяется, присвоено ли этому пункту меню сочетание клавиш.
> Если нет, то ему задается сочетание клавиш, взятое из хэш-таблицы.
Конечно, может понадобиться и другое поведение функции - например, чтобы сочетание клавиш задавалось в любом случае, даже если пункт уже имеет закрепленное за ним сочетание. Тогда потребуется соответствующим образом исправить код.
Осталось рассмотреть, как дать пользователю возможность самому назначить сочетание клавиш выбранному пункту меню. Здесь всё довольно просто - понадобятся два вид-жета. Первый - уже описанное выше строковое поле ввода CShortcutEntry (CShortcutEntry *ent_shtcut), в котором:
> Пользователь сможет вводить сочетание клавиш.
> При выборе пункта меню в списке, в поле ввода будет отображаться сочетание клавиш, назначенное пункту меню (если сочетание вообще назначено).
Второй виджет - экземпляр QListWidget (QListWidget *lv_menuitems). В него будет выводиться список с названиями пунктов меню. Чтобы получить такой список, достаточно воспользоваться функцией CShortcuts::captions_iterate(), которая заполняет внутренний для CShortcuts список (QStringList captions). Чтобы обновить содержимое QListWidget, надо будет вызывать примерно такой код:
shortcuts->captions_iterate();
lv_menuitems->clear() ;
lv_menuitems->addItems (shortcuts->captions);
lv_menuitems->clear() ;
lv_menuitems->addItems (shortcuts->captions);
Теперь необходимо связать выбор пункта меню в списке с отображением сочетания клавиш в виджете CShortcutEntry:
connect (lv_menuitems, SIGNAL(currentItemChanged
(QListWidgetItem *,
QListWidgetItem *)),
this, SLOT(slot_lv_menuitems_currentItemChanged
(QListWidgetItem *, QListWidgetItem *)));
(QListWidgetItem *,
QListWidgetItem *)),
this, SLOT(slot_lv_menuitems_currentItemChanged
(QListWidgetItem *, QListWidgetItem *)));
Сам обработчик выглядит так:
void класс_главного_окна::slot_lv_menuitems_currentItemChanged
(QListWidgetItem *current, QListWidgetItem *previous)
{
QAction *a = shortcuts->find_by_caption (current->text());
if (a)
ent_shtcut->setText (a->shortcut().toString());
}
(QListWidgetItem *current, QListWidgetItem *previous)
{
QAction *a = shortcuts->find_by_caption (current->text());
if (a)
ent_shtcut->setText (a->shortcut().toString());
}
Здесь видно нахождение пункта меню (указателя на экземпляр QAction) по его названию - точнее, по надписи на пункте меню. За это отвечает функция:
QAction* CShortcuts::find_by_caption (const QString &text);
Существует еще ряд утилитных функций:
> QAction* CShortcuts::find_by_shortcut (const QString &shcut); - найти пункт меню по сочетанию горячих клавиш.
> QKeySequence find_seq_by_caption (const QString &text); - найти сочетание клавиш по надписи пункта меню.
> void set_new_shortcut (const QString &menuitem, const QString &shcut); - задать пункту меню с надписью menuitem новое сочетание клавиш shcut. Параметрами этой функции будут служить выбранный в списке пункт меню и сочетание клавиш из виджета CShortcutEntry. Вызов set_new_shortcut удобно поместить в обработчик нажатия какой-нибудь клавиши (например, «Задать»), по нажатию на которую будет задаваться сочетание клавиш для выбранного пункта меню.
Таким образом, у вас есть всё необходимое для оснащения своей программы возможностью назначать «горячие клавиши» со стороны пользователя. Единственный недостаток приведенного движка - идет привязка к конкретному языку интерфейса. Если вы назначили сочетание клавиш при русском интерфейсе, а потом переключили на английский, то «русские» назначения не будут подхвачены. Но воплощение «универсального» движка усложнило бы задачу - например, можно было бы для пунктов меню генерировать коды и эти коды связывать с клавишами.