Merge "persistent color scheme selection"
[trojita.git] / src / Gui / ShortcutHandler / ShortcutConfigWidget.cpp
blobae84595aff5711f4e514a2646edbc9ac2f6db656
1 /*
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
25 #include <QKeyEvent>
26 #include <QMessageBox>
27 #include <QSettings>
29 #include "ShortcutHandler.h"
30 #include "Common/SettingsCategoryGuard.h"
31 #include "UiUtils/IconLoader.h"
33 namespace Gui
36 ShortcutConfigWidget::ShortcutConfigWidget(QWidget *parent)
37 : QWidget(parent)
38 , m_shortcutsShouldBeRestored(false)
40 ui.setupUi(this);
41 setWindowTitle(tr("Configure Shortcuts") + QLatin1String(" - ") + tr("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;
98 break;
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());
109 // create item
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);
142 else
143 childItem->setHidden(true);
148 /***************************************************************************/
150 bool ShortcutConfigWidget::eventFilter(QObject *obj, QEvent *event)
152 Q_UNUSED(obj);
153 if (event->type() != QEvent::KeyPress)
154 return false;
156 QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
157 QString keySequence;
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)
163 return false;
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))
171 return false;
172 if (keyEvent->key() == Qt::Key_Backtab) // the above doesn't catch "Shift+Tab"
173 return false;
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
187 return false;
188 const QString actionName = currentItem->data(1, Qt::UserRole).toString();
189 if (actionName.isEmpty()) // this is the case when a toplevel item is selected
190 return false;
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
197 continue;
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(" - ") + tr("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, childItem->text(0)),
207 QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok);
208 if (result == QMessageBox::Ok)
209 childItem->setText(1, QString());
210 else
211 return false;
215 // finally we can set the new shortcut
216 currentItem->setText(1, keySequence);
217 return true;
220 void ShortcutConfigWidget::clearShortcut()
222 QTreeWidgetItem *currentItem = ui.shortcutTreeWidget->currentItem();
223 const QString actionName = currentItem->data(1, Qt::UserRole).toString();
224 if (actionName.isEmpty())
225 return;
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())
234 return;
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);
253 writeSettings();
254 Q_EMIT(shortcutsChanged(m_actionDescriptions));
257 void ShortcutConfigWidget::reject()
259 if (!m_shortcutsShouldBeRestored)
260 return;
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)
278 Q_UNUSED(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, QStringLiteral("ShortcutHandler"));
294 settings->remove(QString());
295 settings->beginWriteArray(QStringLiteral("Shortcuts"));
296 int index = 0;
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(QStringLiteral("Action"), it.key());
303 settings->setValue(QStringLiteral("Shortcut"), actionDescription.shortcut);
304 ++index;
307 settings->endArray();
310 } // namespace Gui
312 #endif // QT_NO_SHORTCUT