Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Qt User customizable hotkeys

Tags:

c++

qt

qt4

I'm trying to design a Qt GUI application with user customize-able hotkeys. The main issue I'm running into is how to synchronize the hotkeys across the application since a particular hotkey (for example, copy) may be used by multiple widgets/components.

My current strategy is to use a reference class which holds a list of QKeySequence objects for each different hotkey. Each widget would have to have a way to reference this master list and have custom implementations of low-level the keyPressEvent which would compare inputted keys vs. the hotkeys. I don't particularly like this strategy, though, as it requires significant re-implimentation in each widget and feels like I'm trying to re-invent the wheel.

I also tried using QAction objects which can hold QKeySequence shortcuts internally, then use these to trigger higher-level events which I can handle using slots & signals. However, the main issue I have here is how to manage which slots signals get routed to.

For example, say I have 2 open widgets which can both receive a copy action signal. I can connect a slot for both of these to the same signal and take advantage of the single update point for shortcuts, but then things get messy since only the active widget should act on the copy signal, not both widgets. I can re-implement the focusOutEvent and focusInEvent handlers to connect/disconnect slots manually, but this also seems to run into the same issue above where I'm trying to re-invent the wheel and doing more work than is necessary.

Is there an easier way around this problem?

like image 862
helloworld922 Avatar asked Nov 17 '12 22:11

helloworld922


1 Answers

I don't think there is a particularly easy/non-tedious solution to this problem, but when I needed to add user-customizable hotkeys to my application, here is how I did it:

1) Start with your application that has hard-coded key shortcuts, e.g. code like this:

QMenu * editMenu = new QMenu;
QAction * copyItem = menu->addAction(tr("Copy"), this, SLOT(CopyData()));
copyItem->setShortcut(tr("Ctrl+C"));

2) Create a GetKeySequence() function that looks something like this:

static QHash<QString, QKeySequence> _usersKeyPreferences;
static bool _usersKeyPreferencesLoaded = false;

QKeySequence GetKeySequence(const QString & keySequence, const QString & contextStr)
{
   if (_usersKeyPreferencesLoaded == false)
   {
      // Oops, time to load in the user's saved custom-key settings from a file somewhere
      _usersKeyPreferences = LoadUsersKeyPreferencesFromFile();
      _usersKeyPreferencesLoaded = true;  // so we'll only try to load the file once
   }
   if (_usersKeyPreferences.contains(contextStr)) 
   {
      return _usersKeyPreferences[contextStr];
   }
   else 
   {
      // No user preference specified?  Okay, fall back to using the 
      // hard-coded default key sequence instead.
      return QKeySequence(qApp->translate(contextStr, keySequence));
   }
}

3) Now the tedious part: grovel over all of your code, and anywhere you've specified a key-sequence explicitly (like in the third line of the code shown for step 1), wrap it with a call to GetKeySequence(), like this:

copyItem->setShortcut(GetKeySequence(tr("Ctrl+C"), tr("Edit_Menu|Copy")));

4) At this point, your program's key-sequences will be customizable; just make sure that the key-settings-file is present on disk before GUI-creation code runs. Here's an excerpt from my program's key-mappings file (which I store as a simple ASCII text file):

Edit_Menu|Copy   = Ctrl+C
Edit_Menu|Cut    = Ctrl+X
Edit_Menu|Paste  = Ctrl+V
[... and so on for all other menu items, etc...]

... of course one downside to this approach is that once the GUI is created, the key-bindings can't be modified "on the fly" (at least, not without a lot of additional coding). My program gets around this simply by closing and then re-creating all windows after the user clicks "Save and Apply" in the Edit Key Bindings dialog.

5) An optional further step (which is some extra work up front but saves time in the long run) is to write a program (or script) that greps all the .cpp files in your program's codebase looking for calls GetKeySequence() in the code. When it finds a GetKeySequence() call, it parses out the two arguments to the call and prints them as a line in a key-bindings file with the default settings. This is useful because you can make this script part of your autobuild, and thereafter you'll never have to remember to manually update the default key-settings-file whenever you add a new menu item (or other key-sequence specifier) to your program.

This worked well for me, anyway. The advantage is that you don't have to refactor your existing program at all; you can just go through it inserting GetKeySequence() as necessary while leaving the larger logic/structure of the program intact.

like image 73
Jeremy Friesner Avatar answered Sep 19 '22 16:09

Jeremy Friesner