Allow configuration of shortcuts for platforms other than KDE
authorManuel Nickschas <sputnick@quassel-irc.org>
Mon, 31 May 2010 17:28:25 +0000 (19:28 +0200)
committerManuel Nickschas <sputnick@quassel-irc.org>
Mon, 31 May 2010 17:30:17 +0000 (19:30 +0200)
This adds a shortcuts configuration dialog for Quassel without KDE integration.

12 files changed:
src/common/util.cpp
src/common/util.h
src/qtui/mainwin.cpp
src/qtui/mainwin.h
src/qtui/settingspages/keysequencewidget.cpp [new file with mode: 0644]
src/qtui/settingspages/keysequencewidget.h [new file with mode: 0644]
src/qtui/settingspages/settingspages.inc
src/qtui/settingspages/shortcutsmodel.cpp [new file with mode: 0644]
src/qtui/settingspages/shortcutsmodel.h [new file with mode: 0644]
src/qtui/settingspages/shortcutssettingspage.cpp [new file with mode: 0644]
src/qtui/settingspages/shortcutssettingspage.h [new file with mode: 0644]
src/qtui/settingspages/shortcutssettingspage.ui [new file with mode: 0644]

index 8b9231e..14435bd 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-09 by the Quassel Project                          *
+ *   Copyright (C) 2005-2010 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
@@ -60,6 +60,22 @@ QString stripFormatCodes(QString str) {
   return str;
 }
 
+QString stripAcceleratorMarkers(const QString &label_) {
+  QString label = label_;
+  int p = 0;
+  forever {
+    p = label.indexOf('&', p);
+    if(p < 0 || p + 1 >= label.length())
+      break;
+
+    if(label.at(p + 1).isLetterOrNumber() || label.at(p + 1) == '&')
+      label.remove(p, 1);
+
+    ++p;
+  }
+  return label;
+}
+
 QString decodeString(const QByteArray &input, QTextCodec *codec) {
   // First, we check if it's utf8. It is very improbable to encounter a string that looks like
   // valid utf8, but in fact is not. This means that if the input string passes as valid utf8, it
index 0e205fd..aee3499 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005/06 by the Quassel Project                          *
+ *   Copyright (C) 2005-2010 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
@@ -36,6 +36,9 @@ bool isChannelName(QString str);
 //! Strip mIRC format codes
 QString stripFormatCodes(QString);
 
+//! Remove accelerator markers (&) from the string
+QString stripAcceleratorMarkers(const QString &);
+
 QString secondsToString(int timeInSeconds);
 
 //! Take a string and decode it using the specified text codec, recognizing utf8.
index 598befd..878168e 100644 (file)
 #include "settingspages/notificationssettingspage.h"
 #include "settingspages/topicwidgetsettingspage.h"
 
+#ifndef HAVE_KDE
+#  include "settingspages/shortcutssettingspage.h"
+#endif
+
 MainWin::MainWin(QWidget *parent)
 #ifdef HAVE_KDE
   : KMainWindow(parent),
@@ -339,6 +343,8 @@ void MainWin::setupActions() {
                                                 0, 0))->setCheckable(true);
 
   // Settings
+  coll->addAction("ConfigureShortcuts", new Action(SmallIcon("configure-shortcuts"), tr("Configure &Shortcuts..."), coll,
+                                                  this, SLOT(showShortcutsDlg())));
   coll->addAction("ConfigureQuassel", new Action(SmallIcon("configure"), tr("&Configure Quassel..."), coll,
                                                   this, SLOT(showSettingsDlg()), QKeySequence(Qt::Key_F7)));
 
@@ -408,6 +414,8 @@ void MainWin::setupMenus() {
 #ifdef HAVE_KDE
   _settingsMenu->addAction(KStandardAction::configureNotifications(this, SLOT(showNotificationsDlg()), this));
   _settingsMenu->addAction(KStandardAction::keyBindings(this, SLOT(showShortcutsDlg()), this));
+#else
+  _settingsMenu->addAction(coll->action("ConfigureShortcuts"));
 #endif
   _settingsMenu->addAction(coll->action("ConfigureQuassel"));
 
@@ -974,11 +982,14 @@ void MainWin::showAboutDlg() {
   AboutDlg(this).exec();
 }
 
-#ifdef HAVE_KDE
 void MainWin::showShortcutsDlg() {
+#ifdef HAVE_KDE
   KShortcutsDialog::configure(QtUi::actionCollection("General"), KShortcutsEditor::LetterShortcutsDisallowed);
-}
+#else
+  SettingsPageDlg dlg(new ShortcutsSettingsPage(QtUi::actionCollections(), this), this);
+  dlg.exec();
 #endif
+}
 
 /********************************************************************************************************/
 
index 53548bf..f9130bd 100644 (file)
@@ -114,9 +114,8 @@ class MainWin
     void showSettingsDlg();
     void showNotificationsDlg();
     void showIgnoreList(QString newRule = QString());
-#ifdef HAVE_KDE
     void showShortcutsDlg();
-#endif
+
     void handleCoreConnectionError(const QString &errorMsg);
     void userAuthenticationRequired(CoreAccount *, bool *valid, const QString &errorMessage);
     void handleNoSslInClient(bool *accepted);
diff --git a/src/qtui/settingspages/keysequencewidget.cpp b/src/qtui/settingspages/keysequencewidget.cpp
new file mode 100644 (file)
index 0000000..4673a18
--- /dev/null
@@ -0,0 +1,376 @@
+/***************************************************************************
+ *   Copyright (C) 2010 by the Quassel Project                             *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   This class has been inspired by KDE's KKeySequenceWidget and uses     *
+ *   some code snippets of its implementation, part of kdelibs.            *
+ *   The original file is                                                  *
+ *       Copyright (C) 1998 Mark Donohoe <donohoe@kde.org>                 *
+ *       Copyright (C) 2001 Ellis Whitehead <ellis@kde.org>                *
+ *       Copyright (C) 2007 Andreas Hartmetz <ahartmetz@gmail.com>         *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+ ***************************************************************************/
+
+#include <QApplication>
+#include <QDebug>
+#include <QKeyEvent>
+#include <QHBoxLayout>
+#include <QMessageBox>
+#include <QToolButton>
+
+#include "action.h"
+#include "actioncollection.h"
+#include "iconloader.h"
+#include "keysequencewidget.h"
+
+KeySequenceButton::KeySequenceButton(KeySequenceWidget *d_, QWidget *parent)
+  : QPushButton(parent),
+  d(d_)
+{
+
+}
+
+bool KeySequenceButton::event(QEvent *e) {
+  if(d->isRecording() && e->type() == QEvent::KeyPress) {
+    keyPressEvent(static_cast<QKeyEvent *>(e));
+    return true;
+  }
+
+  // The shortcut 'alt+c' ( or any other dialog local action shortcut )
+  // ended the recording and triggered the action associated with the
+  // action. In case of 'alt+c' ending the dialog.  It seems that those
+  // ShortcutOverride events get sent even if grabKeyboard() is active.
+  if(d->isRecording() && e->type() == QEvent::ShortcutOverride) {
+    e->accept();
+    return true;
+  }
+
+  return QPushButton::event(e);
+}
+
+void KeySequenceButton::keyPressEvent(QKeyEvent *e) {
+  int keyQt = e->key();
+  if(keyQt == -1) {
+    // Qt sometimes returns garbage keycodes, I observed -1, if it doesn't know a key.
+    // We cannot do anything useful with those (several keys have -1, indistinguishable)
+    // and QKeySequence.toString() will also yield a garbage string.
+    QMessageBox::information(this,
+                             tr("The key you just pressed is not supported by Qt."),
+                             tr("Unsupported Key"));
+    return d->cancelRecording();
+  }
+
+  uint newModifiers = e->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META);
+
+  //don't have the return or space key appear as first key of the sequence when they
+  //were pressed to start editing - catch and them and imitate their effect
+  if(!d->isRecording() && ((keyQt == Qt::Key_Return || keyQt == Qt::Key_Space))) {
+    d->startRecording();
+    d->_modifierKeys = newModifiers;
+    d->updateShortcutDisplay();
+    return;
+  }
+
+  // We get events even if recording isn't active.
+  if(!d->isRecording())
+    return QPushButton::keyPressEvent(e);
+
+  e->accept();
+  d->_modifierKeys = newModifiers;
+
+  switch(keyQt) {
+  case Qt::Key_AltGr: //or else we get unicode salad
+    return;
+  case Qt::Key_Shift:
+  case Qt::Key_Control:
+  case Qt::Key_Alt:
+  case Qt::Key_Meta:
+  case Qt::Key_Menu: //unused (yes, but why?)
+    d->updateShortcutDisplay();
+    break;
+
+  default:
+    if(!(d->_modifierKeys & ~Qt::SHIFT)) {
+      // It's the first key and no modifier pressed. Check if this is
+      // allowed
+      if(!d->isOkWhenModifierless(keyQt))
+        return;
+    }
+
+    // We now have a valid key press.
+    if(keyQt) {
+      if((keyQt == Qt::Key_Backtab) && (d->_modifierKeys & Qt::SHIFT)) {
+        keyQt = Qt::Key_Tab | d->_modifierKeys;
+      }
+      else if(d->isShiftAsModifierAllowed(keyQt)) {
+        keyQt |= d->_modifierKeys;
+      } else
+        keyQt |= (d->_modifierKeys & ~Qt::SHIFT);
+
+      d->_keySequence = QKeySequence(keyQt);
+      d->doneRecording();
+    }
+  }
+}
+
+void KeySequenceButton::keyReleaseEvent(QKeyEvent *e) {
+  if(e->key() == -1) {
+    // ignore garbage, see keyPressEvent()
+    return;
+  }
+
+  if(!d->isRecording())
+    return QPushButton::keyReleaseEvent(e);
+
+  e->accept();
+
+  uint newModifiers = e->modifiers() & (Qt::SHIFT | Qt::CTRL | Qt::ALT | Qt::META);
+
+  // if a modifier that belongs to the shortcut was released...
+  if((newModifiers & d->_modifierKeys) < d->_modifierKeys) {
+    d->_modifierKeys = newModifiers;
+    d->updateShortcutDisplay();
+  }
+}
+
+/******************************************************************************/
+
+KeySequenceWidget::KeySequenceWidget(QWidget *parent)
+  : QWidget(parent),
+  _shortcutsModel(0),
+  _isRecording(false),
+  _modifierKeys(0)
+{
+  QHBoxLayout *layout = new QHBoxLayout(this);
+  layout->setMargin(0);
+
+  _keyButton = new KeySequenceButton(this, this);
+  _keyButton->setFocusPolicy(Qt::StrongFocus);
+  _keyButton->setIcon(SmallIcon("configure"));
+  _keyButton->setToolTip(tr("Click on the button, then enter the shortcut like you would in the program.\nExample for Ctrl+a: hold the Ctrl key and press a."));
+  layout->addWidget(_keyButton);
+
+  _clearButton = new QToolButton(this);
+  layout->addWidget(_clearButton);
+
+  if(qApp->isLeftToRight())
+    _clearButton->setIcon(SmallIcon("edit-clear-locationbar-rtl"));
+  else
+    _clearButton->setIcon(SmallIcon("edit-clear-locationbar-ltr"));
+
+  setLayout(layout);
+
+  connect(_keyButton, SIGNAL(clicked()), SLOT(startRecording()));
+  connect(_keyButton, SIGNAL(clicked()), SIGNAL(clicked()));
+  connect(_clearButton, SIGNAL(clicked()), SLOT(clear()));
+  connect(_clearButton, SIGNAL(clicked()), SIGNAL(clicked()));
+}
+
+void KeySequenceWidget::setModel(ShortcutsModel *model) {
+  Q_ASSERT(!_shortcutsModel);
+  _shortcutsModel = model;
+}
+
+bool KeySequenceWidget::isOkWhenModifierless(int keyQt) const {
+  //this whole function is a hack, but especially the first line of code
+  if(QKeySequence(keyQt).toString().length() == 1)
+    return false;
+
+  switch(keyQt) {
+  case Qt::Key_Return:
+  case Qt::Key_Space:
+  case Qt::Key_Tab:
+  case Qt::Key_Backtab: //does this ever happen?
+  case Qt::Key_Backspace:
+  case Qt::Key_Delete:
+    return false;
+  default:
+    return true;
+  }
+}
+
+bool KeySequenceWidget::isShiftAsModifierAllowed(int keyQt) const {
+  // Shift only works as a modifier with certain keys. It's not possible
+  // to enter the SHIFT+5 key sequence for me because this is handled as
+  // '%' by qt on my keyboard.
+  // The working keys are all hardcoded here :-(
+  if(keyQt >= Qt::Key_F1 && keyQt <= Qt::Key_F35)
+    return true;
+
+  if(QChar(keyQt).isLetter())
+    return true;
+
+  switch(keyQt) {
+  case Qt::Key_Return:
+  case Qt::Key_Space:
+  case Qt::Key_Backspace:
+  case Qt::Key_Escape:
+  case Qt::Key_Print:
+  case Qt::Key_ScrollLock:
+  case Qt::Key_Pause:
+  case Qt::Key_PageUp:
+  case Qt::Key_PageDown:
+  case Qt::Key_Insert:
+  case Qt::Key_Delete:
+  case Qt::Key_Home:
+  case Qt::Key_End:
+  case Qt::Key_Up:
+  case Qt::Key_Down:
+  case Qt::Key_Left:
+  case Qt::Key_Right:
+    return true;
+
+  default:
+    return false;
+  }
+}
+
+void KeySequenceWidget::updateShortcutDisplay() {
+  // make translators happy
+  static QString metaKey = tr("Meta", "Meta key");
+  static QString altKey = tr("Alt", "Alt key");
+  static QString ctrlKey = tr("Ctrl", "Ctrl key");
+  static QString shiftKey = tr("Shift", "Shift key");
+
+  QString s = _keySequence.toString(QKeySequence::NativeText);
+  s.replace('&', QLatin1String("&&"));
+
+  if(_isRecording) {
+    if(_modifierKeys) {
+      if(_modifierKeys & Qt::META)  s += metaKey + '+';
+#if defined(Q_WS_MAC)
+      if(_modifierKeys & Qt::ALT)   s += altKey + '+';
+      if(_modifierKeys & Qt::CTRL)  s += ctrlKey + '+';
+#elif defined(Q_WS_X11)
+      if(_modifierKeys & Qt::CTRL)  s += ctrlKey + '+';
+      if(_modifierKeys & Qt::ALT)   s += altKey + '+';
+#endif
+      if(_modifierKeys & Qt::SHIFT) s += shiftKey + '+';
+
+    } else {
+      s = tr("Input", "What the user inputs now will be taken as the new shortcut");
+    }
+    // make it clear that input is still going on
+    s.append(" ...");
+  }
+
+  if(s.isEmpty()) {
+    s = tr("None", "No shortcut defined");
+  }
+
+  s.prepend(' ');
+  s.append(' ');
+  _keyButton->setText(s);
+}
+
+void KeySequenceWidget::startRecording() {
+  _modifierKeys = 0;
+  _oldKeySequence = _keySequence;
+  _keySequence = QKeySequence();
+  _conflictingIndex = QModelIndex();
+  _isRecording = true;
+  _keyButton->grabKeyboard();
+
+  if(!QWidget::keyboardGrabber()) {
+    qWarning() << "Failed to grab the keyboard! Most likely qt's nograb option is active";
+  }
+
+  _keyButton->setDown(true);
+  updateShortcutDisplay();
+}
+
+
+void KeySequenceWidget::doneRecording() {
+  bool wasRecording = _isRecording;
+  _isRecording = false;
+  _keyButton->releaseKeyboard();
+  _keyButton->setDown(false);
+
+  if(!wasRecording || _keySequence == _oldKeySequence) {
+    // The sequence hasn't changed
+    updateShortcutDisplay();
+    return;
+  }
+
+  if(!isKeySequenceAvailable(_keySequence)) {
+    _keySequence = _oldKeySequence;
+  } else if(wasRecording) {
+    emit keySequenceChanged(_keySequence, _conflictingIndex);
+  }
+  updateShortcutDisplay();
+}
+
+void KeySequenceWidget::cancelRecording() {
+  _keySequence = _oldKeySequence;
+  doneRecording();
+}
+
+void KeySequenceWidget::setKeySequence(const QKeySequence &seq) {
+  // oldKeySequence holds the key sequence before recording started, if setKeySequence()
+  // is called while not recording then set oldKeySequence to the existing sequence so
+  // that the keySequenceChanged() signal is emitted if the new and previous key
+  // sequences are different
+  if(!isRecording())
+    _oldKeySequence = _keySequence;
+
+  _keySequence = seq;
+  _clearButton->setVisible(!_keySequence.isEmpty());
+  doneRecording();
+}
+
+void KeySequenceWidget::clear() {
+  setKeySequence(QKeySequence());
+  // setKeySequence() won't emit a signal when we're not recording
+  emit keySequenceChanged(QKeySequence());
+}
+
+bool KeySequenceWidget::isKeySequenceAvailable(const QKeySequence &seq) {
+  if(seq.isEmpty())
+    return true;
+
+  // We need to access the root model, not the filtered one
+  for(int cat = 0; cat < _shortcutsModel->rowCount(); cat++) {
+    QModelIndex catIdx = _shortcutsModel->index(cat, 0);
+    for(int r = 0; r < _shortcutsModel->rowCount(catIdx); r++) {
+      QModelIndex actIdx = _shortcutsModel->index(r, 0, catIdx);
+      Q_ASSERT(actIdx.isValid());
+      if(actIdx.data(ShortcutsModel::ActiveShortcutRole).value<QKeySequence>() != seq)
+        continue;
+
+      if(!actIdx.data(ShortcutsModel::IsConfigurableRole).toBool()) {
+        QMessageBox::warning(this, tr("Shortcut Conflict"),
+                             tr("The \"%1\" shortcut is already in use, and cannot be configured.\nPlease choose another one.").arg(seq.toString()),
+                             QMessageBox::Ok);
+        return false;
+      }
+
+      QMessageBox box(QMessageBox::Warning, tr("Shortcut Conflict"),
+                      (tr("The \"%1\" shortcut is ambiguous with the shortcut for the following action:")
+                       + "<br><ul><li>%2</li></ul><br>"
+                       + tr("Do you want to reassign this shortcut to the selected action?")
+                       ).arg(seq.toString(), actIdx.data().toString()),
+                      QMessageBox::Cancel, this);
+      box.addButton(tr("Reassign"), QMessageBox::AcceptRole);
+      if(box.exec() == QMessageBox::Cancel)
+        return false;
+
+      _conflictingIndex = actIdx;
+      return true;
+    }
+  }
+  return true;
+}
diff --git a/src/qtui/settingspages/keysequencewidget.h b/src/qtui/settingspages/keysequencewidget.h
new file mode 100644 (file)
index 0000000..82d14dc
--- /dev/null
@@ -0,0 +1,107 @@
+/***************************************************************************
+ *   Copyright (C) 2010 by the Quassel Project                             *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   This class has been inspired by KDE's KKeySequenceWidget and uses     *
+ *   some code snippets of its implementation, part of kdelibs.            *
+ *   The original file is                                                  *
+ *       Copyright (C) 1998 Mark Donohoe <donohoe@kde.org>                 *
+ *       Copyright (C) 2001 Ellis Whitehead <ellis@kde.org>                *
+ *       Copyright (C) 2007 Andreas Hartmetz <ahartmetz@gmail.com>         *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+ ***************************************************************************/
+
+#ifndef KEYSEQUENCEWIDGET_H
+#define KEYSEQUENCEWIDGET_H
+
+#include <QKeySequence>
+#include <QPushButton>
+#include <QSet>
+#include <QWidget>
+
+#include "shortcutsmodel.h"
+
+class Action;
+class ActionCollection;
+class KeySequenceButton;
+class QToolButton;
+
+class KeySequenceWidget : public QWidget {
+  Q_OBJECT
+public:
+  KeySequenceWidget(QWidget *parent = 0);
+
+  void setModel(ShortcutsModel *model);
+
+public slots:
+  void setKeySequence(const QKeySequence &seq);
+
+signals:
+  /**
+   * This signal is emitted when the current key sequence has changed by user input
+   * \param seq         The key sequence the user has chosen
+   * \param conflicting The index of an action that needs to have its shortcut removed. The user has already been
+   *                    asked to agree (if he declines, this signal won't be emitted at all).
+   */
+  void keySequenceChanged(const QKeySequence &seq, const QModelIndex &conflicting = QModelIndex());
+
+  void clicked();
+
+private slots:
+  void updateShortcutDisplay();
+  void startRecording();
+  void cancelRecording();
+  void clear();
+
+private:
+  inline bool isRecording() const { return _isRecording; }
+  void doneRecording();
+
+  bool isOkWhenModifierless(int keyQt) const;
+  bool isShiftAsModifierAllowed(int keyQt) const;
+  bool isKeySequenceAvailable(const QKeySequence &seq);
+
+  ShortcutsModel *_shortcutsModel;
+  bool _isRecording;
+  QKeySequence _keySequence, _oldKeySequence;
+  uint _modifierKeys;
+  QModelIndex _conflictingIndex;
+
+  KeySequenceButton *_keyButton;
+  QToolButton *_clearButton;
+
+  friend class KeySequenceButton;
+};
+
+
+/*****************************************************************************/
+
+class KeySequenceButton : public QPushButton {
+  Q_OBJECT
+public:
+  explicit KeySequenceButton(KeySequenceWidget *d, QWidget *parent = 0);
+
+protected:
+  virtual bool event(QEvent *event);
+  virtual void keyPressEvent(QKeyEvent *event);
+  virtual void keyReleaseEvent(QKeyEvent *event);
+
+private:
+  KeySequenceWidget *d;
+};
+
+#endif // KEYSEQUENCEWIDGET_H
index 1efe3e2..1659271 100644 (file)
@@ -9,3 +9,9 @@ set(SP_SOURCES aliasesmodel.cpp identityeditwidget.cpp ignorelistmodel.cpp notif
 set(SP_HEADERS aliasesmodel.h identityeditwidget.h ignorelistmodel.h notificationssettingspage.h previewbufferview.h)
 set(SP_FORMS buffervieweditdlg.ui coreaccounteditdlg.ui createidentitydlg.ui identityeditwidget.ui ignorelisteditdlg.ui saveidentitiesdlg.ui 
              networkadddlg.ui networkeditdlg.ui nickeditdlg.ui servereditdlg.ui)
+
+if(NOT HAVE_KDE)
+  set(SETTINGSPAGES ${SETTINGSPAGES} shortcuts)
+  set(SP_SOURCES ${SP_SOURCES} keysequencewidget.cpp shortcutsmodel.cpp)
+  set(SP_HEADERS ${SP_HEADERS} keysequencewidget.h shortcutsmodel.h)
+endif(NOT HAVE_KDE)
diff --git a/src/qtui/settingspages/shortcutsmodel.cpp b/src/qtui/settingspages/shortcutsmodel.cpp
new file mode 100644 (file)
index 0000000..f9dcbe2
--- /dev/null
@@ -0,0 +1,232 @@
+/***************************************************************************
+ *   Copyright (C) 2010 by the Quassel Project                             *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) version 3.                                           *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+ ***************************************************************************/
+
+#include "shortcutsmodel.h"
+
+#include "action.h"
+#include "actioncollection.h"
+#include "util.h"
+
+ShortcutsModel::ShortcutsModel(const QHash<QString, ActionCollection *> &actionCollections, QObject *parent)
+  : QAbstractItemModel(parent),
+  _changedCount(0)
+{
+  for(int r = 0; r < actionCollections.values().count(); r++) {
+    ActionCollection *coll = actionCollections.values().at(r);
+    Item *item = new Item();
+    item->row = r;
+    item->collection = coll;
+    for(int i = 0; i < coll->actions().count(); i++) {
+      Action *action = qobject_cast<Action *>(coll->actions().at(i));
+      if(!action)
+        continue;
+      Item *actionItem = new Item();
+      actionItem->parentItem = item;
+      actionItem->row = i;
+      actionItem->collection = coll;
+      actionItem->action = action;
+      actionItem->shortcut = action->shortcut();
+      item->actionItems.append(actionItem);
+    }
+    _categoryItems.append(item);
+  }
+}
+
+ShortcutsModel::~ShortcutsModel() {
+  qDeleteAll(_categoryItems);
+}
+
+QModelIndex ShortcutsModel::parent(const QModelIndex &child) const {
+  if(!child.isValid())
+    return QModelIndex();
+
+  Item *item = static_cast<Item *>(child.internalPointer());
+  Q_ASSERT(item);
+
+  if(!item->parentItem)
+    return QModelIndex();
+
+  return createIndex(item->parentItem->row, 0, item->parentItem);
+}
+
+QModelIndex ShortcutsModel::index(int row, int column, const QModelIndex &parent) const {
+
+  if(parent.isValid())
+    return createIndex(row, column, static_cast<Item *>(parent.internalPointer())->actionItems.at(row));
+
+  // top level category item
+  return createIndex(row, column, _categoryItems.at(row));
+}
+
+int ShortcutsModel::columnCount(const QModelIndex &parent) const {
+  return 2;
+  if(!parent.isValid())
+    return 2;
+
+  Item *item = static_cast<Item *>(parent.internalPointer());
+  Q_ASSERT(item);
+
+  if(!item->parentItem)
+    return 2;
+
+  return 2;
+}
+
+int ShortcutsModel::rowCount(const QModelIndex &parent) const {
+  if(!parent.isValid())
+    return _categoryItems.count();
+
+  Item *item = static_cast<Item *>(parent.internalPointer());
+  Q_ASSERT(item);
+
+  if(!item->parentItem)
+    return item->actionItems.count();
+
+  return 0;
+}
+
+QVariant ShortcutsModel::headerData(int section, Qt::Orientation orientation, int role) const {
+  if(orientation != Qt::Horizontal || role != Qt::DisplayRole)
+    return QVariant();
+  switch(section) {
+  case 0:
+    return tr("Action");
+  case 1:
+    return tr("Shortcut");
+  default:
+    return QVariant();
+  }
+}
+
+QVariant ShortcutsModel::data(const QModelIndex &index, int role) const {
+  if(!index.isValid())
+    return QVariant();
+
+  Item *item = static_cast<Item *>(index.internalPointer());
+  Q_ASSERT(item);
+
+  if(!item->parentItem) {
+    if(index.column() != 0)
+      return QVariant();
+    switch(role) {
+    case Qt::DisplayRole:
+      return item->collection->property("Category");
+    default:
+      return QVariant();
+    }
+  }
+
+  Action *action = qobject_cast<Action *>(item->action);
+  Q_ASSERT(action);
+
+  switch(role) {
+  case Qt::DisplayRole:
+    switch(index.column()) {
+    case 0:
+      return stripAcceleratorMarkers(action->text());
+    case 1:
+      return item->shortcut.toString();
+    default:
+      return QVariant();
+    }
+
+  case Qt::DecorationRole:
+    if(index.column() == 0)
+      return action->icon();
+    return QVariant();
+
+  case ActionRole:
+    return QVariant::fromValue<QObject *>(action);
+
+  case DefaultShortcutRole:
+    return action->shortcut(Action::DefaultShortcut);
+  case ActiveShortcutRole:
+    return item->shortcut;
+
+  case IsConfigurableRole:
+    return action->isShortcutConfigurable();
+
+  default:
+    return QVariant();
+  }
+}
+
+bool ShortcutsModel::setData(const QModelIndex &index, const QVariant &value, int role) {
+  if(role != ActiveShortcutRole)
+    return false;
+
+  if(!index.parent().isValid())
+    return false;
+
+  Item *item = static_cast<Item *>(index.internalPointer());
+  Q_ASSERT(item);
+
+  QKeySequence newSeq = value.value<QKeySequence>();
+  QKeySequence oldSeq = item->shortcut;
+  QKeySequence storedSeq = item->action->shortcut(Action::ActiveShortcut);
+
+  item->shortcut = newSeq;
+  emit dataChanged(index, index.sibling(index.row(), 1));
+
+  if(oldSeq == storedSeq && newSeq != storedSeq) {
+    if(++_changedCount == 1)
+      emit hasChanged(true);
+  } else if(oldSeq != storedSeq && newSeq == storedSeq) {
+    if(--_changedCount == 0)
+      emit hasChanged(false);
+  }
+
+  return true;
+}
+
+void ShortcutsModel::load() {
+  foreach(Item *catItem, _categoryItems) {
+    foreach(Item *actItem, catItem->actionItems) {
+      actItem->shortcut = actItem->action->shortcut(Action::ActiveShortcut);
+    }
+  }
+  emit dataChanged(index(0, 1), index(rowCount()-1, 1));
+  if(_changedCount != 0) {
+    _changedCount = 0;
+    emit hasChanged(false);
+  }
+}
+
+void ShortcutsModel::commit() {
+  foreach(Item *catItem, _categoryItems) {
+    foreach(Item *actItem, catItem->actionItems) {
+      actItem->action->setShortcut(actItem->shortcut, Action::ActiveShortcut);
+    }
+  }
+  if(_changedCount != 0) {
+    _changedCount = 0;
+    emit hasChanged(false);
+  }
+}
+
+void ShortcutsModel::defaults() {
+  for(int cat = 0; cat < rowCount(); cat++) {
+    QModelIndex catidx = index(cat, 0);
+    for(int act = 0; act < rowCount(catidx); act++) {
+      QModelIndex actidx = index(act, 1, catidx);
+      setData(actidx, actidx.data(DefaultShortcutRole), ActiveShortcutRole);
+    }
+  }
+}
diff --git a/src/qtui/settingspages/shortcutsmodel.h b/src/qtui/settingspages/shortcutsmodel.h
new file mode 100644 (file)
index 0000000..a52e073
--- /dev/null
@@ -0,0 +1,96 @@
+/***************************************************************************
+ *   Copyright (C) 2010 by the Quassel Project                             *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) version 3.                                           *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+ ***************************************************************************/
+
+#ifndef SHORTCUTSMODEL_H
+#define SHORTCUTSMODEL_H
+
+#include <QAbstractItemModel>
+#include <QKeySequence>
+
+class Action;
+class ActionCollection;
+
+//! Model that exposes the actions from one or more ActionCollections
+/** This model takes one or more ActionCollections and exposes their actions as model items.
+ *  Note that the ShortcutsModel will not react to changes in the ActionCollection (e.g. adding,
+ *  removing actions), because it is supposed to be used after all actions being defined.
+ */
+class ShortcutsModel : public QAbstractItemModel {
+  Q_OBJECT
+public:
+  enum Role {
+    ActionRole = Qt::UserRole,
+    DefaultShortcutRole,
+    ActiveShortcutRole,
+    IsConfigurableRole
+  };
+
+  ShortcutsModel(const QHash<QString, ActionCollection *> &actionCollections, QObject *parent = 0);
+  ~ShortcutsModel();
+
+  QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
+  QModelIndex parent(const QModelIndex &child) const;
+  int columnCount(const QModelIndex &parent = QModelIndex()) const;
+  int rowCount(const QModelIndex &parent = QModelIndex()) const;
+  QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+  QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+  bool setData(const QModelIndex &index, const QVariant &value, int role = ActiveShortcutRole);
+
+public slots:
+  //! Load shortcuts from the ActionCollections
+  /** Note that this will not rebuild the internal structure of the model, as we assume the
+   *  ActionCollections to be static during the lifetime of the settingspage. This will merely
+   *  re-read the shortcuts currently set in Quassel.
+   */
+  void load();
+
+  //! Load default shortcuts from the ActionCollections
+  /** Note that this will not rebuild the internal structure of the model, as we assume the
+   *  ActionCollections to be static during the lifetime of the settingspage. This will update
+   *  the model's state from the ActionCollections' defaults.
+   */
+  void defaults();
+
+  //! Commit the model changes to the ActionCollections
+  void commit();
+
+  inline bool hasChanged() const { return _changedCount; }
+
+signals:
+  //! Reflects the difference between model contents and the ActionCollections we loaded this from
+  void hasChanged(bool changed);
+
+private:
+  struct Item {
+    inline Item() { parentItem = 0; collection = 0; action = 0; }
+    inline ~Item() { qDeleteAll(actionItems); }
+    int row;
+    Item *parentItem;
+    ActionCollection *collection;
+    Action *action;
+    QKeySequence shortcut;
+    QList<Item *> actionItems;
+  };
+
+  QList<Item *> _categoryItems;
+  int _changedCount;
+};
+
+#endif // SHORTCUTSMODEL_H
diff --git a/src/qtui/settingspages/shortcutssettingspage.cpp b/src/qtui/settingspages/shortcutssettingspage.cpp
new file mode 100644 (file)
index 0000000..2317a7b
--- /dev/null
@@ -0,0 +1,154 @@
+
+/***************************************************************************
+ *   Copyright (C) 2010 by the Quassel Project                             *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) version 3.                                           *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+ ***************************************************************************/
+
+#include <QTimer>
+
+#include "shortcutssettingspage.h"
+
+#include "action.h"
+#include "actioncollection.h"
+#include "qtui.h"
+#include "shortcutsmodel.h"
+#include "util.h"
+
+ShortcutsFilter::ShortcutsFilter(QObject *parent) : QSortFilterProxyModel(parent) {
+  setDynamicSortFilter(true);
+}
+
+void ShortcutsFilter::setFilterString(const QString &filterString) {
+  _filterString = filterString;
+  invalidateFilter();
+}
+
+bool ShortcutsFilter::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const {
+  if(!source_parent.isValid())
+    return true;
+
+  QModelIndex index = source_parent.model()->index(source_row, 0, source_parent);
+  Q_ASSERT(index.isValid());
+  if(!qobject_cast<Action *>(index.data(ShortcutsModel::ActionRole).value<QObject *>())->isShortcutConfigurable())
+    return false;
+
+  for(int col = 0; col < source_parent.model()->columnCount(source_parent); col++) {
+    if(source_parent.model()->index(source_row, col, source_parent).data().toString().contains(_filterString, Qt::CaseInsensitive))
+      return true;
+  }
+  return false;
+}
+
+/****************************************************************************/
+
+ShortcutsSettingsPage::ShortcutsSettingsPage(const QHash<QString, ActionCollection *> &actionCollections, QWidget *parent)
+  : SettingsPage(tr("Interface"), tr("Shortcuts"), parent),
+  _shortcutsModel(new ShortcutsModel(actionCollections, this)),
+  _shortcutsFilter(new ShortcutsFilter(this))
+{
+  ui.setupUi(this);
+
+  _shortcutsFilter->setSourceModel(_shortcutsModel);
+  ui.shortcutsView->setModel(_shortcutsFilter);
+  ui.shortcutsView->expandAll();
+  ui.shortcutsView->resizeColumnToContents(0);
+  ui.shortcutsView->sortByColumn(0, Qt::AscendingOrder);
+
+  ui.keySequenceWidget->setModel(_shortcutsModel);
+  connect(ui.keySequenceWidget, SIGNAL(keySequenceChanged(QKeySequence,QModelIndex)), SLOT(keySequenceChanged(QKeySequence,QModelIndex)));
+
+  connect(ui.shortcutsView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), SLOT(setWidgetStates()));
+
+  setWidgetStates();
+
+  connect(ui.useDefault, SIGNAL(clicked(bool)), SLOT(toggledCustomOrDefault()));
+  connect(ui.useCustom, SIGNAL(clicked(bool)), SLOT(toggledCustomOrDefault()));
+
+  connect(_shortcutsModel, SIGNAL(hasChanged(bool)), SLOT(setChangedState(bool)));
+
+  // fugly, but directly setting it from the ctor doesn't seem to work
+  QTimer::singleShot(0, ui.searchEdit, SLOT(setFocus()));
+}
+
+void ShortcutsSettingsPage::setWidgetStates() {
+  if(ui.shortcutsView->currentIndex().isValid() && ui.shortcutsView->currentIndex().parent().isValid()) {
+    QKeySequence active = ui.shortcutsView->currentIndex().data(ShortcutsModel::ActiveShortcutRole).value<QKeySequence>();
+    QKeySequence def = ui.shortcutsView->currentIndex().data(ShortcutsModel::DefaultShortcutRole).value<QKeySequence>();
+    ui.defaultShortcut->setText(def.isEmpty()? tr("None") : def.toString());
+    ui.actionBox->setEnabled(true);
+    if(active == def) {
+      ui.useDefault->setChecked(true);
+      ui.keySequenceWidget->setKeySequence(QKeySequence());
+    } else {
+      ui.useCustom->setChecked(true);
+      ui.keySequenceWidget->setKeySequence(active);
+    }
+  } else {
+    ui.defaultShortcut->setText(tr("None"));
+    ui.actionBox->setEnabled(false);
+    ui.useDefault->setChecked(true);
+    ui.keySequenceWidget->setKeySequence(QKeySequence());
+  }
+}
+
+void ShortcutsSettingsPage::on_searchEdit_textChanged(const QString &text) {
+  _shortcutsFilter->setFilterString(text);
+}
+
+void ShortcutsSettingsPage::keySequenceChanged(const QKeySequence &seq, const QModelIndex &conflicting) {
+  if(conflicting.isValid())
+    _shortcutsModel->setData(conflicting, QKeySequence(), ShortcutsModel::ActiveShortcutRole);
+
+  QModelIndex rowIdx = _shortcutsFilter->mapToSource(ui.shortcutsView->currentIndex());
+  Q_ASSERT(rowIdx.isValid());
+  _shortcutsModel->setData(rowIdx, seq, ShortcutsModel::ActiveShortcutRole);
+  setWidgetStates();
+}
+
+void ShortcutsSettingsPage::toggledCustomOrDefault() {
+  if(!ui.shortcutsView->currentIndex().isValid())
+    return;
+
+  QModelIndex index = _shortcutsFilter->mapToSource(ui.shortcutsView->currentIndex());
+  Q_ASSERT(index.isValid());
+
+  if(ui.useDefault->isChecked()) {
+    _shortcutsModel->setData(index, index.data(ShortcutsModel::DefaultShortcutRole));
+  } else {
+    _shortcutsModel->setData(index, QKeySequence());
+  }
+  setWidgetStates();
+}
+
+void ShortcutsSettingsPage::save() {
+  _shortcutsModel->commit();
+  QtUi::saveShortcuts();
+  SettingsPage::save();
+}
+
+void ShortcutsSettingsPage::load() {
+  _shortcutsModel->load();
+
+  SettingsPage::load();
+}
+
+void ShortcutsSettingsPage::defaults() {
+  _shortcutsModel->defaults();
+
+  SettingsPage::defaults();
+}
diff --git a/src/qtui/settingspages/shortcutssettingspage.h b/src/qtui/settingspages/shortcutssettingspage.h
new file mode 100644 (file)
index 0000000..bd3413d
--- /dev/null
@@ -0,0 +1,72 @@
+/***************************************************************************
+ *   Copyright (C) 2010 by the Quassel Project                             *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) version 3.                                           *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+ ***************************************************************************/
+
+#ifndef SHORTCUTSSETTINGSPAGE_H
+#define SHORTCUTSSETTINGSPAGE_H
+
+#include <QSortFilterProxyModel>
+
+#include "settingspage.h"
+
+#include "ui_shortcutssettingspage.h"
+
+class ActionCollection;
+class ShortcutsModel;
+
+class ShortcutsFilter : public QSortFilterProxyModel {
+  Q_OBJECT
+public:
+  ShortcutsFilter(QObject *parent = 0);
+
+public slots:
+  void setFilterString(const QString &filterString);
+
+protected:
+  virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
+
+private:
+  QString _filterString;
+};
+
+class ShortcutsSettingsPage : public SettingsPage {
+  Q_OBJECT
+public:
+  ShortcutsSettingsPage(const QHash<QString, ActionCollection *> &actionCollections, QWidget *parent = 0);
+
+  inline bool hasDefaults() const { return true; }
+
+public slots:
+  void save();
+  void load();
+  void defaults();
+
+private slots:
+  void on_searchEdit_textChanged(const QString &text);
+  void keySequenceChanged(const QKeySequence &seq, const QModelIndex &conflicting);
+  void setWidgetStates();
+  void toggledCustomOrDefault();
+
+private:
+  Ui::ShortcutsSettingsPage ui;
+  ShortcutsModel *_shortcutsModel;
+  ShortcutsFilter *_shortcutsFilter;
+};
+
+#endif // SHORTCUTSSETTINGSPAGE_H
diff --git a/src/qtui/settingspages/shortcutssettingspage.ui b/src/qtui/settingspages/shortcutssettingspage.ui
new file mode 100644 (file)
index 0000000..ead27c7
--- /dev/null
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ShortcutsSettingsPage</class>
+ <widget class="QWidget" name="ShortcutsSettingsPage">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>497</width>
+    <height>481</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QLabel" name="label_2">
+       <property name="text">
+        <string>Search:</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLineEdit" name="searchEdit"/>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QTreeView" name="shortcutsView">
+     <property name="editTriggers">
+      <set>QAbstractItemView::NoEditTriggers</set>
+     </property>
+     <property name="showDropIndicator" stdset="0">
+      <bool>false</bool>
+     </property>
+     <property name="alternatingRowColors">
+      <bool>true</bool>
+     </property>
+     <property name="sortingEnabled">
+      <bool>true</bool>
+     </property>
+     <property name="animated">
+      <bool>false</bool>
+     </property>
+     <property name="allColumnsShowFocus">
+      <bool>true</bool>
+     </property>
+     <attribute name="headerCascadingSectionResizes">
+      <bool>true</bool>
+     </attribute>
+     <attribute name="headerStretchLastSection">
+      <bool>true</bool>
+     </attribute>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="actionBox">
+     <property name="title">
+      <string>Shortcut for Selected Action</string>
+     </property>
+     <layout class="QGridLayout" name="gridLayout">
+      <item row="0" column="0">
+       <widget class="QRadioButton" name="useDefault">
+        <property name="text">
+         <string>Default:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="1">
+       <widget class="QLabel" name="defaultShortcut">
+        <property name="text">
+         <string>None</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="0">
+       <widget class="QRadioButton" name="useCustom">
+        <property name="text">
+         <string>Custom:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="1">
+       <widget class="KeySequenceWidget" name="keySequenceWidget" native="true"/>
+      </item>
+      <item row="1" column="2">
+       <spacer name="horizontalSpacer">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>346</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+     </layout>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>KeySequenceWidget</class>
+   <extends>QWidget</extends>
+   <header>keysequencewidget.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>searchEdit</tabstop>
+  <tabstop>shortcutsView</tabstop>
+  <tabstop>useDefault</tabstop>
+  <tabstop>useCustom</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>