This adds a shortcuts configuration dialog for Quassel without KDE integration.
/***************************************************************************
- * 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 *
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
/***************************************************************************
- * 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 *
//! 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.
#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),
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)));
#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"));
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
+}
/********************************************************************************************************/
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);
--- /dev/null
+/***************************************************************************
+ * 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;
+}
--- /dev/null
+/***************************************************************************
+ * 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
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)
--- /dev/null
+/***************************************************************************
+ * 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);
+ }
+ }
+}
--- /dev/null
+/***************************************************************************
+ * 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
--- /dev/null
+
+/***************************************************************************
+ * 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();
+}
--- /dev/null
+/***************************************************************************
+ * 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
--- /dev/null
+<?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>