File indexing completed on 2024-06-23 05:20:47
0001 /* 0002 Copyright (C) 2012, 2013 by Glad Deschrijver <glad.deschrijver@gmail.com> 0003 0004 This program is free software; you can redistribute it and/or 0005 modify it under the terms of the GNU General Public License as 0006 published by the Free Software Foundation; either version 2 of 0007 the License or (at your option) version 3 or any later version 0008 accepted by the membership of KDE e.V. (or its successor approved 0009 by the membership of KDE e.V.), which shall act as a proxy 0010 defined in Section 14 of version 3 of the license. 0011 0012 This program is distributed in the hope that it will be useful, 0013 but WITHOUT ANY WARRANTY; without even the implied warranty of 0014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0015 GNU General Public License for more details. 0016 0017 You should have received a copy of the GNU General Public License 0018 along with this program. If not, see <http://www.gnu.org/licenses/>. 0019 */ 0020 0021 #include "ShortcutConfigWidget.h" 0022 0023 #ifndef QT_NO_SHORTCUT 0024 0025 #include <QKeyEvent> 0026 #include <QMessageBox> 0027 #include <QSettings> 0028 0029 #include "ShortcutHandler.h" 0030 #include "Common/SettingsCategoryGuard.h" 0031 #include "UiUtils/IconLoader.h" 0032 0033 namespace Gui 0034 { 0035 0036 ShortcutConfigWidget::ShortcutConfigWidget(QWidget *parent) 0037 : QWidget(parent) 0038 , m_shortcutsShouldBeRestored(false) 0039 { 0040 ui.setupUi(this); 0041 setWindowTitle(tr("Configure Shortcuts") + QLatin1String(" - ") + tr("Trojitá")); 0042 0043 ui.shortcutTreeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); 0044 ui.shortcutTreeWidget->header()->setSectionResizeMode(1, QHeaderView::Stretch); 0045 ui.shortcutTreeWidget->setUniformRowHeights(true); // all rows have the same height 0046 ui.shortcutTreeWidget->installEventFilter(this); 0047 0048 ui.gridLayout->setColumnStretch(2, 1); // make sure the buttons are not too large 0049 ui.gridLayout->setContentsMargins(0, 0, 0, 0); 0050 0051 setFocusProxy(ui.shortcutTreeWidget); 0052 0053 connect(ui.searchLineEdit, &QLineEdit::textChanged, this, &ShortcutConfigWidget::searchItems); 0054 connect(ui.clearPushButton, &QAbstractButton::clicked, this, &ShortcutConfigWidget::clearShortcut); 0055 connect(ui.useDefaultPushButton, &QAbstractButton::clicked, this, &ShortcutConfigWidget::restoreDefaultShortcut); 0056 } 0057 0058 ShortcutConfigWidget::~ShortcutConfigWidget() 0059 { 0060 } 0061 0062 QPushButton *ShortcutConfigWidget::clearButton() 0063 { 0064 return ui.clearPushButton; 0065 } 0066 0067 QPushButton *ShortcutConfigWidget::useDefaultButton() 0068 { 0069 return ui.useDefaultPushButton; 0070 } 0071 0072 /***************************************************************************/ 0073 0074 void ShortcutConfigWidget::setExclusivityGroups(const QList<QStringList> &groups) 0075 { 0076 // this function must be called after all actions are added 0077 // 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 0078 // 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 0079 m_exclusivityGroups = groups; 0080 const int topLevelItemCount = ui.shortcutTreeWidget->topLevelItemCount(); 0081 for (int i = 0; i < topLevelItemCount; ++i) { 0082 QTreeWidgetItem *topLevelItem = ui.shortcutTreeWidget->topLevelItem(i); 0083 const QString parentId = topLevelItem->text(0); 0084 for (int j = 0; j < m_exclusivityGroups.size(); ++j) 0085 if (m_exclusivityGroups.at(j).contains(parentId)) 0086 topLevelItem->setData(1, Qt::UserRole, -j-1); 0087 } 0088 } 0089 0090 void ShortcutConfigWidget::addItem(const QString &actionName, const QString &text, const QString &shortcut, const QIcon &icon, const QString &parentId) 0091 { 0092 // search correct toplevel item 0093 int topLevelItemNumber = -1; 0094 const int topLevelItemCount = ui.shortcutTreeWidget->topLevelItemCount(); 0095 for (int i = 0; i < topLevelItemCount; ++i) { 0096 if (ui.shortcutTreeWidget->topLevelItem(i)->text(0) == parentId) { 0097 topLevelItemNumber = i; 0098 break; 0099 } 0100 } 0101 if (topLevelItemNumber < 0) { // toplevel item with name parentId doesn't exist yet, so create 0102 QTreeWidgetItem *item = new QTreeWidgetItem(ui.shortcutTreeWidget); 0103 item->setText(0, parentId); 0104 topLevelItemNumber = topLevelItemCount; 0105 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 0106 item->setData(1, Qt::UserRole, QString()); 0107 } 0108 0109 // create item 0110 QString textWithoutAccelerator = text; 0111 QTreeWidgetItem *item = new QTreeWidgetItem(ui.shortcutTreeWidget->topLevelItem(topLevelItemNumber)); 0112 item->setText(0, textWithoutAccelerator.remove(QLatin1Char('&'))); 0113 item->setIcon(0, icon); 0114 item->setText(1, shortcut); 0115 item->setData(1, Qt::UserRole, actionName); // store objectName of the current action 0116 } 0117 0118 void ShortcutConfigWidget::setActionDescriptions(const QHash<QString, ActionDescription> &actionDescriptions) 0119 { 0120 m_actionDescriptions = actionDescriptions; 0121 ui.shortcutTreeWidget->scrollToItem(ui.shortcutTreeWidget->invisibleRootItem()->child(0)); 0122 ui.shortcutTreeWidget->clear(); 0123 for (QHash<QString, ActionDescription>::const_iterator it = m_actionDescriptions.constBegin(); it != m_actionDescriptions.constEnd(); ++it) { 0124 ActionDescription actionDescription = it.value(); 0125 addItem(it.key(), actionDescription.text, actionDescription.shortcut, UiUtils::loadIcon(actionDescription.iconName), actionDescription.parentId); 0126 } 0127 ui.shortcutTreeWidget->expandAll(); 0128 } 0129 0130 /***************************************************************************/ 0131 0132 void ShortcutConfigWidget::searchItems(const QString &text) 0133 { 0134 const int topLevelItemCount = ui.shortcutTreeWidget->topLevelItemCount(); 0135 for (int i = 0; i < topLevelItemCount; ++i) { 0136 QTreeWidgetItem *topLevelItem = ui.shortcutTreeWidget->topLevelItem(i); 0137 const int childCount = topLevelItem->childCount(); 0138 for (int j = 0; j < childCount; ++j) { 0139 QTreeWidgetItem *childItem = topLevelItem->child(j); 0140 if (childItem->text(0).contains(text, Qt::CaseInsensitive) || childItem->text(1).contains(text, Qt::CaseInsensitive)) 0141 childItem->setHidden(false); 0142 else 0143 childItem->setHidden(true); 0144 } 0145 } 0146 } 0147 0148 /***************************************************************************/ 0149 0150 bool ShortcutConfigWidget::eventFilter(QObject *obj, QEvent *event) 0151 { 0152 Q_UNUSED(obj); 0153 if (event->type() != QEvent::KeyPress) 0154 return false; 0155 0156 QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); 0157 QString keySequence; 0158 // don't allow modifiers to be handled as single keys and skip CapsLock and NumLock 0159 if (keyEvent->key() == Qt::Key_Control || keyEvent->key() == Qt::Key_Shift 0160 || keyEvent->key() == Qt::Key_Meta || keyEvent->key() == Qt::Key_Alt 0161 || keyEvent->key() == Qt::Key_Super_L || keyEvent->key() == Qt::Key_AltGr 0162 || keyEvent->key() == Qt::Key_CapsLock || keyEvent->key() == Qt::Key_NumLock) 0163 return false; 0164 // 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) 0165 if (keyEvent->modifiers() == Qt::NoModifier 0166 && (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down 0167 || keyEvent->key() == Qt::Key_Left || keyEvent->key() == Qt::Key_Right 0168 || keyEvent->key() == Qt::Key_PageUp || keyEvent->key() == Qt::Key_PageDown 0169 || keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Backtab 0170 || keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Escape)) 0171 return false; 0172 if (keyEvent->key() == Qt::Key_Backtab) // the above doesn't catch "Shift+Tab" 0173 return false; 0174 0175 // create string representation of the new shortcut 0176 if (keyEvent->modifiers() & Qt::ControlModifier) 0177 keySequence += tr("Ctrl+", "Shortcut modifier"); 0178 if (keyEvent->modifiers() & Qt::AltModifier) 0179 keySequence += tr("Alt+", "Shortcut modifier"); 0180 if (keyEvent->modifiers() & Qt::ShiftModifier) 0181 keySequence += tr("Shift+", "Shortcut modifier"); 0182 keySequence += QKeySequence(keyEvent->key()).toString(QKeySequence::PortableText); 0183 0184 // replace shortcut in the list (but not yet for real, this is done when the user accepts the dialog) 0185 QTreeWidgetItem *currentItem = ui.shortcutTreeWidget->currentItem(); 0186 if (!currentItem) // this is the case when ui.shortcutTreeWidget is empty 0187 return false; 0188 const QString actionName = currentItem->data(1, Qt::UserRole).toString(); 0189 if (actionName.isEmpty()) // this is the case when a toplevel item is selected 0190 return false; 0191 // 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 0192 const QVariant parentId = currentItem->parent()->data(1, Qt::UserRole); 0193 const int topLevelItemCount = ui.shortcutTreeWidget->topLevelItemCount(); 0194 for (int i = 0; i < topLevelItemCount; ++i) { 0195 QTreeWidgetItem *topLevelItem = ui.shortcutTreeWidget->topLevelItem(i); 0196 if (topLevelItem->data(1, Qt::UserRole) != parentId) // only deal with actions in the same exclusivity group 0197 continue; 0198 const int childCount = topLevelItem->childCount(); 0199 for (int j = 0; j < childCount; ++j) { 0200 QTreeWidgetItem *childItem = topLevelItem->child(j); 0201 if (keySequence == childItem->text(1) && childItem->data(1, Qt::UserRole).toString() != actionName) { 0202 QMessageBox::StandardButton result = QMessageBox::warning(this, 0203 tr("Shortcut Conflicts") + QLatin1String(" - ") + tr("Trojitá"), 0204 tr("<p>The \"%1\" shortcut is ambiguous with the following shortcut:</p>" 0205 "<p>%2</p><p>Do you want to assign an empty shortcut to this action?</p>") 0206 .arg(keySequence, childItem->text(0)), 0207 QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok); 0208 if (result == QMessageBox::Ok) 0209 childItem->setText(1, QString()); 0210 else 0211 return false; 0212 } 0213 } 0214 } 0215 // finally we can set the new shortcut 0216 currentItem->setText(1, keySequence); 0217 return true; 0218 } 0219 0220 void ShortcutConfigWidget::clearShortcut() 0221 { 0222 QTreeWidgetItem *currentItem = ui.shortcutTreeWidget->currentItem(); 0223 const QString actionName = currentItem->data(1, Qt::UserRole).toString(); 0224 if (actionName.isEmpty()) 0225 return; 0226 currentItem->setText(1, QString()); 0227 } 0228 0229 void ShortcutConfigWidget::restoreDefaultShortcut() 0230 { 0231 QTreeWidgetItem *currentItem = ui.shortcutTreeWidget->currentItem(); 0232 const QString actionName = currentItem->data(1, Qt::UserRole).toString(); 0233 if (actionName.isEmpty()) 0234 return; 0235 currentItem->setText(1, m_actionDescriptions[actionName].defaultShortcut); 0236 } 0237 0238 /***************************************************************************/ 0239 0240 void ShortcutConfigWidget::accept() 0241 { 0242 // set shortcuts for real (but they are not yet saved in the settings on disk, this is done in writeSettings()) 0243 const int topLevelItemCount = ui.shortcutTreeWidget->topLevelItemCount(); 0244 for (int i = 0; i < topLevelItemCount; ++i) { 0245 QTreeWidgetItem *topLevelItem = ui.shortcutTreeWidget->topLevelItem(i); 0246 const int childCount = topLevelItem->childCount(); 0247 for (int j = 0; j < childCount; ++j) { 0248 QTreeWidgetItem *childItem = topLevelItem->child(j); 0249 const QString actionName = childItem->data(1, Qt::UserRole).toString(); 0250 m_actionDescriptions[actionName].shortcut = childItem->text(1); 0251 } 0252 } 0253 writeSettings(); 0254 Q_EMIT(shortcutsChanged(m_actionDescriptions)); 0255 } 0256 0257 void ShortcutConfigWidget::reject() 0258 { 0259 if (!m_shortcutsShouldBeRestored) 0260 return; 0261 0262 // restore unsaved shortcuts in the tree widget 0263 const int topLevelItemCount = ui.shortcutTreeWidget->topLevelItemCount(); 0264 for (int i = 0; i < topLevelItemCount; ++i) { 0265 QTreeWidgetItem *topLevelItem = ui.shortcutTreeWidget->topLevelItem(i); 0266 const int childCount = topLevelItem->childCount(); 0267 for (int j = 0; j < childCount; ++j) { 0268 QTreeWidgetItem *childItem = topLevelItem->child(j); 0269 const QString actionName = childItem->data(1, Qt::UserRole).toString(); 0270 childItem->setText(1, m_actionDescriptions[actionName].shortcut); 0271 } 0272 } 0273 m_shortcutsShouldBeRestored = false; 0274 } 0275 0276 void ShortcutConfigWidget::showEvent(QShowEvent *event) 0277 { 0278 Q_UNUSED(event); 0279 reject(); // restore unsaved shortcuts in the tree widget before the configuration dialog is shown again 0280 m_shortcutsShouldBeRestored = true; 0281 ui.shortcutTreeWidget->sortByColumn(0, Qt::AscendingOrder); 0282 } 0283 0284 /***************************************************************************/ 0285 0286 /** 0287 * \see ShortcutHandler::readSettings() 0288 */ 0289 void ShortcutConfigWidget::writeSettings() 0290 { 0291 QSettings *settings = ShortcutHandler::instance()->settingsObject(); 0292 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"); 0293 Common::SettingsCategoryGuard guard(settings, QStringLiteral("ShortcutHandler")); 0294 settings->remove(QString()); 0295 settings->beginWriteArray(QStringLiteral("Shortcuts")); 0296 int index = 0; 0297 for (QHash<QString, ActionDescription>::const_iterator it = m_actionDescriptions.constBegin(); it != m_actionDescriptions.constEnd(); ++it) { 0298 ActionDescription actionDescription = it.value(); 0299 const QString shortcut = actionDescription.shortcut; 0300 if (shortcut != actionDescription.defaultShortcut) { 0301 settings->setArrayIndex(index); 0302 settings->setValue(QStringLiteral("Action"), it.key()); 0303 settings->setValue(QStringLiteral("Shortcut"), actionDescription.shortcut); 0304 ++index; 0305 } 0306 } 0307 settings->endArray(); 0308 } 0309 0310 } // namespace Gui 0311 0312 #endif // QT_NO_SHORTCUT