2 Copyright (C) 2012, 2013 by Glad Deschrijver <glad.deschrijver@gmail.com>
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License as
6 published by the Free Software Foundation; either version 2 of
7 the License or (at your option) version 3 or any later version
8 accepted by the membership of KDE e.V. (or its successor approved
9 by the membership of KDE e.V.), which shall act as a proxy
10 defined in Section 14 of version 3 of the license.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "ShortcutConfigWidget.h"
23 #ifndef QT_NO_SHORTCUT
26 #include <QMessageBox>
29 #include "ShortcutHandler.h"
30 #include "Common/SettingsCategoryGuard.h"
31 #include "UiUtils/IconLoader.h"
36 ShortcutConfigWidget::ShortcutConfigWidget(QWidget
*parent
)
38 , m_shortcutsShouldBeRestored(false)
41 setWindowTitle(tr("Configure Shortcuts") + QLatin1String(" - ") + trUtf8("Trojitá"));
43 ui
.shortcutTreeWidget
->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents
);
44 ui
.shortcutTreeWidget
->header()->setSectionResizeMode(1, QHeaderView::Stretch
);
45 ui
.shortcutTreeWidget
->setUniformRowHeights(true); // all rows have the same height
46 ui
.shortcutTreeWidget
->installEventFilter(this);
48 ui
.gridLayout
->setColumnStretch(2, 1); // make sure the buttons are not too large
49 ui
.gridLayout
->setContentsMargins(0, 0, 0, 0);
51 setFocusProxy(ui
.shortcutTreeWidget
);
53 connect(ui
.searchLineEdit
, &QLineEdit::textChanged
, this, &ShortcutConfigWidget::searchItems
);
54 connect(ui
.clearPushButton
, &QAbstractButton::clicked
, this, &ShortcutConfigWidget::clearShortcut
);
55 connect(ui
.useDefaultPushButton
, &QAbstractButton::clicked
, this, &ShortcutConfigWidget::restoreDefaultShortcut
);
58 ShortcutConfigWidget::~ShortcutConfigWidget()
62 QPushButton
*ShortcutConfigWidget::clearButton()
64 return ui
.clearPushButton
;
67 QPushButton
*ShortcutConfigWidget::useDefaultButton()
69 return ui
.useDefaultPushButton
;
72 /***************************************************************************/
74 void ShortcutConfigWidget::setExclusivityGroups(const QList
<QStringList
> &groups
)
76 // this function must be called after all actions are added
77 // we set a unique ID for each exclusivity group and we set the data of each toplevel item to the ID of the group to which it belongs
78 // the ID must be a negative number, since eventFilter() assumes that the ID is either a negative number or the index of the corresponding action in m_actions
79 m_exclusivityGroups
= groups
;
80 const int topLevelItemCount
= ui
.shortcutTreeWidget
->topLevelItemCount();
81 for (int i
= 0; i
< topLevelItemCount
; ++i
) {
82 QTreeWidgetItem
*topLevelItem
= ui
.shortcutTreeWidget
->topLevelItem(i
);
83 const QString parentId
= topLevelItem
->text(0);
84 for (int j
= 0; j
< m_exclusivityGroups
.size(); ++j
)
85 if (m_exclusivityGroups
.at(j
).contains(parentId
))
86 topLevelItem
->setData(1, Qt::UserRole
, -j
-1);
90 void ShortcutConfigWidget::addItem(const QString
&actionName
, const QString
&text
, const QString
&shortcut
, const QIcon
&icon
, const QString
&parentId
)
92 // search correct toplevel item
93 int topLevelItemNumber
= -1;
94 const int topLevelItemCount
= ui
.shortcutTreeWidget
->topLevelItemCount();
95 for (int i
= 0; i
< topLevelItemCount
; ++i
) {
96 if (ui
.shortcutTreeWidget
->topLevelItem(i
)->text(0) == parentId
) {
97 topLevelItemNumber
= i
;
101 if (topLevelItemNumber
< 0) { // toplevel item with name parentId doesn't exist yet, so create
102 QTreeWidgetItem
*item
= new QTreeWidgetItem(ui
.shortcutTreeWidget
);
103 item
->setText(0, parentId
);
104 topLevelItemNumber
= topLevelItemCount
;
105 item
->setSizeHint(1, QSize(0, 1.5 * qApp
->fontMetrics().height())); // since the second column is stretchable, it doesn't matter that the width of the size hint is set to 0
106 item
->setData(1, Qt::UserRole
, QString());
110 QString textWithoutAccelerator
= text
;
111 QTreeWidgetItem
*item
= new QTreeWidgetItem(ui
.shortcutTreeWidget
->topLevelItem(topLevelItemNumber
));
112 item
->setText(0, textWithoutAccelerator
.remove(QLatin1Char('&')));
113 item
->setIcon(0, icon
);
114 item
->setText(1, shortcut
);
115 item
->setData(1, Qt::UserRole
, actionName
); // store objectName of the current action
118 void ShortcutConfigWidget::setActionDescriptions(const QHash
<QString
, ActionDescription
> &actionDescriptions
)
120 m_actionDescriptions
= actionDescriptions
;
121 ui
.shortcutTreeWidget
->scrollToItem(ui
.shortcutTreeWidget
->invisibleRootItem()->child(0));
122 ui
.shortcutTreeWidget
->clear();
123 for (QHash
<QString
, ActionDescription
>::const_iterator it
= m_actionDescriptions
.constBegin(); it
!= m_actionDescriptions
.constEnd(); ++it
) {
124 ActionDescription actionDescription
= it
.value();
125 addItem(it
.key(), actionDescription
.text
, actionDescription
.shortcut
, UiUtils::loadIcon(actionDescription
.iconName
), actionDescription
.parentId
);
127 ui
.shortcutTreeWidget
->expandAll();
130 /***************************************************************************/
132 void ShortcutConfigWidget::searchItems(const QString
&text
)
134 const int topLevelItemCount
= ui
.shortcutTreeWidget
->topLevelItemCount();
135 for (int i
= 0; i
< topLevelItemCount
; ++i
) {
136 QTreeWidgetItem
*topLevelItem
= ui
.shortcutTreeWidget
->topLevelItem(i
);
137 const int childCount
= topLevelItem
->childCount();
138 for (int j
= 0; j
< childCount
; ++j
) {
139 QTreeWidgetItem
*childItem
= topLevelItem
->child(j
);
140 if (childItem
->text(0).contains(text
, Qt::CaseInsensitive
) || childItem
->text(1).contains(text
, Qt::CaseInsensitive
))
141 childItem
->setHidden(false);
143 childItem
->setHidden(true);
148 /***************************************************************************/
150 bool ShortcutConfigWidget::eventFilter(QObject
*obj
, QEvent
*event
)
153 if (event
->type() != QEvent::KeyPress
)
156 QKeyEvent
*keyEvent
= static_cast<QKeyEvent
*>(event
);
158 // don't allow modifiers to be handled as single keys and skip CapsLock and NumLock
159 if (keyEvent
->key() == Qt::Key_Control
|| keyEvent
->key() == Qt::Key_Shift
160 || keyEvent
->key() == Qt::Key_Meta
|| keyEvent
->key() == Qt::Key_Alt
161 || keyEvent
->key() == Qt::Key_Super_L
|| keyEvent
->key() == Qt::Key_AltGr
162 || keyEvent
->key() == Qt::Key_CapsLock
|| keyEvent
->key() == Qt::Key_NumLock
)
164 // skip some particular keys (note that Qt::Key_Up and friends are used to navigate the list, in order to avoid that they interfere with the shortcut changing, we skip them)
165 if (keyEvent
->modifiers() == Qt::NoModifier
166 && (keyEvent
->key() == Qt::Key_Up
|| keyEvent
->key() == Qt::Key_Down
167 || keyEvent
->key() == Qt::Key_Left
|| keyEvent
->key() == Qt::Key_Right
168 || keyEvent
->key() == Qt::Key_PageUp
|| keyEvent
->key() == Qt::Key_PageDown
169 || keyEvent
->key() == Qt::Key_Tab
|| keyEvent
->key() == Qt::Key_Backtab
170 || keyEvent
->key() == Qt::Key_Return
|| keyEvent
->key() == Qt::Key_Escape
))
172 if (keyEvent
->key() == Qt::Key_Backtab
) // the above doesn't catch "Shift+Tab"
175 // create string representation of the new shortcut
176 if (keyEvent
->modifiers() & Qt::ControlModifier
)
177 keySequence
+= tr("Ctrl+", "Shortcut modifier");
178 if (keyEvent
->modifiers() & Qt::AltModifier
)
179 keySequence
+= tr("Alt+", "Shortcut modifier");
180 if (keyEvent
->modifiers() & Qt::ShiftModifier
)
181 keySequence
+= tr("Shift+", "Shortcut modifier");
182 keySequence
+= QKeySequence(keyEvent
->key()).toString(QKeySequence::PortableText
);
184 // replace shortcut in the list (but not yet for real, this is done when the user accepts the dialog)
185 QTreeWidgetItem
*currentItem
= ui
.shortcutTreeWidget
->currentItem();
186 if (!currentItem
) // this is the case when ui.shortcutTreeWidget is empty
188 const QString actionName
= currentItem
->data(1, Qt::UserRole
).toString();
189 if (actionName
.isEmpty()) // this is the case when a toplevel item is selected
191 // test whether the new shortcut is already defined for another action, if yes then ask the user to set an empty shortcut for the old action
192 const QVariant parentId
= currentItem
->parent()->data(1, Qt::UserRole
);
193 const int topLevelItemCount
= ui
.shortcutTreeWidget
->topLevelItemCount();
194 for (int i
= 0; i
< topLevelItemCount
; ++i
) {
195 QTreeWidgetItem
*topLevelItem
= ui
.shortcutTreeWidget
->topLevelItem(i
);
196 if (topLevelItem
->data(1, Qt::UserRole
) != parentId
) // only deal with actions in the same exclusivity group
198 const int childCount
= topLevelItem
->childCount();
199 for (int j
= 0; j
< childCount
; ++j
) {
200 QTreeWidgetItem
*childItem
= topLevelItem
->child(j
);
201 if (keySequence
== childItem
->text(1) && childItem
->data(1, Qt::UserRole
).toString() != actionName
) {
202 QMessageBox::StandardButton result
= QMessageBox::warning(this,
203 tr("Shortcut Conflicts") + QLatin1String(" - ") + trUtf8("Trojitá"),
204 tr("<p>The \"%1\" shortcut is ambiguous with the following shortcut:</p>"
205 "<p>%2</p><p>Do you want to assign an empty shortcut to this action?</p>")
206 .arg(keySequence
).arg(childItem
->text(0)),
207 QMessageBox::Ok
| QMessageBox::Cancel
, QMessageBox::Ok
);
208 if (result
== QMessageBox::Ok
)
209 childItem
->setText(1, QString());
215 // finally we can set the new shortcut
216 currentItem
->setText(1, keySequence
);
220 void ShortcutConfigWidget::clearShortcut()
222 QTreeWidgetItem
*currentItem
= ui
.shortcutTreeWidget
->currentItem();
223 const QString actionName
= currentItem
->data(1, Qt::UserRole
).toString();
224 if (actionName
.isEmpty())
226 currentItem
->setText(1, QString());
229 void ShortcutConfigWidget::restoreDefaultShortcut()
231 QTreeWidgetItem
*currentItem
= ui
.shortcutTreeWidget
->currentItem();
232 const QString actionName
= currentItem
->data(1, Qt::UserRole
).toString();
233 if (actionName
.isEmpty())
235 currentItem
->setText(1, m_actionDescriptions
[actionName
].defaultShortcut
);
238 /***************************************************************************/
240 void ShortcutConfigWidget::accept()
242 // set shortcuts for real (but they are not yet saved in the settings on disk, this is done in writeSettings())
243 const int topLevelItemCount
= ui
.shortcutTreeWidget
->topLevelItemCount();
244 for (int i
= 0; i
< topLevelItemCount
; ++i
) {
245 QTreeWidgetItem
*topLevelItem
= ui
.shortcutTreeWidget
->topLevelItem(i
);
246 const int childCount
= topLevelItem
->childCount();
247 for (int j
= 0; j
< childCount
; ++j
) {
248 QTreeWidgetItem
*childItem
= topLevelItem
->child(j
);
249 const QString actionName
= childItem
->data(1, Qt::UserRole
).toString();
250 m_actionDescriptions
[actionName
].shortcut
= childItem
->text(1);
254 Q_EMIT(shortcutsChanged(m_actionDescriptions
));
257 void ShortcutConfigWidget::reject()
259 if (!m_shortcutsShouldBeRestored
)
262 // restore unsaved shortcuts in the tree widget
263 const int topLevelItemCount
= ui
.shortcutTreeWidget
->topLevelItemCount();
264 for (int i
= 0; i
< topLevelItemCount
; ++i
) {
265 QTreeWidgetItem
*topLevelItem
= ui
.shortcutTreeWidget
->topLevelItem(i
);
266 const int childCount
= topLevelItem
->childCount();
267 for (int j
= 0; j
< childCount
; ++j
) {
268 QTreeWidgetItem
*childItem
= topLevelItem
->child(j
);
269 const QString actionName
= childItem
->data(1, Qt::UserRole
).toString();
270 childItem
->setText(1, m_actionDescriptions
[actionName
].shortcut
);
273 m_shortcutsShouldBeRestored
= false;
276 void ShortcutConfigWidget::showEvent(QShowEvent
*event
)
279 reject(); // restore unsaved shortcuts in the tree widget before the configuration dialog is shown again
280 m_shortcutsShouldBeRestored
= true;
281 ui
.shortcutTreeWidget
->sortByColumn(0, Qt::AscendingOrder
);
284 /***************************************************************************/
287 * \see ShortcutHandler::readSettings()
289 void ShortcutConfigWidget::writeSettings()
291 QSettings
*settings
= ShortcutHandler::instance()->settingsObject();
292 Q_ASSERT_X(settings
, "ShortcutHandler", "no QSettings object found: a settings object should first be set using setSettingsObject() and then readSettings() should be called when initializing your program; note that this QSettings object should exist during the entire lifetime of the ShortcutHandler object and therefore not be deleted first");
293 Common::SettingsCategoryGuard
guard(settings
, QLatin1String("ShortcutHandler"));
294 settings
->remove(QString());
295 settings
->beginWriteArray(QLatin1String("Shortcuts"));
297 for (QHash
<QString
, ActionDescription
>::const_iterator it
= m_actionDescriptions
.constBegin(); it
!= m_actionDescriptions
.constEnd(); ++it
) {
298 ActionDescription actionDescription
= it
.value();
299 const QString shortcut
= actionDescription
.shortcut
;
300 if (shortcut
!= actionDescription
.defaultShortcut
) {
301 settings
->setArrayIndex(index
);
302 settings
->setValue(QLatin1String("Action"), it
.key());
303 settings
->setValue(QLatin1String("Shortcut"), actionDescription
.shortcut
);
307 settings
->endArray();
312 #endif // QT_NO_SHORTCUT