File indexing completed on 2024-12-22 05:17:14
0001 /* 0002 * This file is part of the KDE wacomtablet project. For copyright 0003 * information and license terms see the AUTHORS and COPYING files 0004 * in the top-level directory of this distribution. 0005 * 0006 * This program is free software; you can redistribute it and/or 0007 * modify it under the terms of the GNU General Public License as 0008 * published by the Free Software Foundation; either version 2 of 0009 * the License, or (at your option) any later version. 0010 * 0011 * This program is distributed in the hope that it will be useful, 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0014 * GNU General Public License for more details. 0015 * 0016 * You should have received a copy of the GNU General Public License 0017 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0018 */ 0019 0020 #include "buttonshortcut.h" 0021 0022 #include <QKeySequence> 0023 #include <QRegularExpression> 0024 0025 #include <KGlobalAccel> 0026 #include <KLocalizedString> 0027 0028 using namespace Wacom; 0029 0030 /* 0031 * When mapping multiple keys to the same name, the last entry 0032 * will be the default one. This is because QMap.find() returns 0033 * the most recently added value as default. 0034 * 0035 * Example: 0036 * 0037 * super_l = meta 0038 * super = meta <-- default entry when searching for "meta" 0039 * 0040 */ 0041 const char *ButtonShortcut::CONVERT_KEY_MAP_DATA[][2] = { 0042 // clang-format off 0043 {"alt_l", "alt"}, 0044 {"alt_r", "alt"}, 0045 {"alt", "alt"}, // "alt" default 0046 {"ampersand", "&"}, 0047 {"apostrophe", "'"}, 0048 {"asciicircum", "^"}, 0049 {"asciitilde", "~"}, 0050 {"asterisk", "*"}, 0051 {"at", "@"}, 0052 {"backslash", "\\"}, 0053 {"bar", "|"}, 0054 {"braceleft", "{"}, 0055 {"braceright", "}"}, 0056 {"bracketleft", "["}, 0057 {"bracketright", "]"}, 0058 {"colon", ":"}, 0059 {"comma", ","}, 0060 {"ctrl_l", "ctrl"}, 0061 {"ctrl_r", "ctrl"}, 0062 {"ctrl", "ctrl"}, // "ctrl" default 0063 {"dollar", "$"}, 0064 {"equal", "="}, 0065 {"exclam", "!"}, 0066 {"greater", ">"}, 0067 {"less", "<"}, 0068 {"pgdn", "pgdown"}, 0069 {"numbersign", "#"}, 0070 {"period", "."}, 0071 {"plus", "+"}, 0072 {"minus", "-"}, 0073 {"parenleft", "("}, 0074 {"parenright", ")"}, 0075 {"percent", "%"}, 0076 {"question", "?"}, 0077 {"quotedbl", "\""}, 0078 {"quoteleft", "`"}, 0079 {"semicolon", ";"}, 0080 {"slash", "/"}, 0081 {"super_l", "meta"}, 0082 {"super_r", "meta"}, 0083 {"super", "meta"}, // "super" default 0084 {"underscore", "_"}, 0085 {nullptr, nullptr}, 0086 // clang-format on 0087 }; 0088 0089 QString buttonNumberToDescription(int buttonNr) 0090 { 0091 switch (buttonNr) { 0092 case 1: 0093 return i18nc("Tablet button triggers a left mouse button click.", "Left Mouse Button Click"); 0094 case 2: 0095 return i18nc("Tablet button triggers a middle mouse button click.", "Middle Mouse Button Click"); 0096 case 3: 0097 return i18nc("Tablet button triggers a right mouse button click.", "Right Mouse Button Click"); 0098 case 4: 0099 return i18nc("Tablet button triggers mouse wheel up.", "Mouse Wheel Up"); 0100 case 5: 0101 return i18nc("Tablet button triggers mouse wheel down.", "Mouse Wheel Down"); 0102 case 6: 0103 return i18nc("Tablet button triggers mouse wheel left.", "Mouse Wheel Left"); 0104 case 7: 0105 return i18nc("Tablet button triggers mouse wheel right.", "Mouse Wheel Right"); 0106 default: 0107 return i18nc("Tablet button triggers a click of mouse button with number #", "Mouse Button %1 Click", buttonNr); 0108 } 0109 } 0110 0111 namespace Wacom 0112 { 0113 class ButtonShortcutPrivate 0114 { 0115 public: 0116 ButtonShortcutPrivate() 0117 { 0118 type = ButtonShortcut::ShortcutType::NONE; 0119 button = 0; 0120 } 0121 0122 ButtonShortcut::ShortcutType type; 0123 QString sequence; //!< The key or modifier sequence. 0124 int button; //!< The button number. 0125 }; 0126 } 0127 0128 ButtonShortcut::ButtonShortcut() 0129 : d_ptr(new ButtonShortcutPrivate) 0130 { 0131 } 0132 0133 ButtonShortcut::ButtonShortcut(const ButtonShortcut &that) 0134 : d_ptr(new ButtonShortcutPrivate) 0135 { 0136 operator=(that); 0137 } 0138 0139 ButtonShortcut::ButtonShortcut(const QString &shortcut) 0140 : d_ptr(new ButtonShortcutPrivate) 0141 { 0142 set(shortcut); 0143 } 0144 0145 ButtonShortcut::ButtonShortcut(int buttonNumber) 0146 : d_ptr(new ButtonShortcutPrivate) 0147 { 0148 setButton(buttonNumber); 0149 } 0150 0151 ButtonShortcut::~ButtonShortcut() 0152 { 0153 delete this->d_ptr; 0154 } 0155 0156 ButtonShortcut &ButtonShortcut::operator=(const ButtonShortcut &that) 0157 { 0158 Q_D(ButtonShortcut); 0159 0160 *d = *(that.d_ptr); 0161 return *this; 0162 } 0163 0164 ButtonShortcut &ButtonShortcut::operator=(const QString &shortcut) 0165 { 0166 set(shortcut); 0167 return *this; 0168 } 0169 0170 bool ButtonShortcut::operator==(const ButtonShortcut &that) const 0171 { 0172 Q_D(const ButtonShortcut); 0173 0174 if (d->type != that.d_ptr->type) { 0175 return false; 0176 } 0177 0178 if (d->button != that.d_ptr->button) { 0179 return false; 0180 } 0181 0182 if (d->sequence.compare(that.d_ptr->sequence, Qt::CaseInsensitive) != 0) { 0183 return false; 0184 } 0185 0186 return true; 0187 } 0188 0189 bool ButtonShortcut::operator!=(const ButtonShortcut &that) const 0190 { 0191 return (!operator==(that)); 0192 } 0193 0194 void ButtonShortcut::clear() 0195 { 0196 Q_D(ButtonShortcut); 0197 0198 d->type = ShortcutType::NONE; 0199 d->button = 0; 0200 d->sequence.clear(); 0201 } 0202 0203 int ButtonShortcut::getButton() const 0204 { 0205 Q_D(const ButtonShortcut); 0206 return d->button; 0207 } 0208 0209 ButtonShortcut::ShortcutType ButtonShortcut::getType() const 0210 { 0211 Q_D(const ButtonShortcut); 0212 return d->type; 0213 } 0214 0215 bool ButtonShortcut::isButton() const 0216 { 0217 return (getType() == ShortcutType::BUTTON); 0218 } 0219 0220 bool ButtonShortcut::isKeystroke() const 0221 { 0222 return (getType() == ShortcutType::KEYSTROKE); 0223 } 0224 0225 bool ButtonShortcut::isModifier() const 0226 { 0227 return (getType() == ShortcutType::MODIFIER); 0228 } 0229 0230 bool ButtonShortcut::isSet() const 0231 { 0232 return (getType() != ShortcutType::NONE); 0233 } 0234 0235 bool ButtonShortcut::setButton(int buttonNumber) 0236 { 0237 Q_D(ButtonShortcut); 0238 0239 clear(); 0240 0241 if (buttonNumber > 0 && buttonNumber <= 32) { 0242 d->type = ShortcutType::BUTTON; 0243 d->button = buttonNumber; 0244 return true; 0245 } 0246 0247 return false; 0248 } 0249 0250 bool ButtonShortcut::set(const QString &sequence) 0251 { 0252 clear(); 0253 0254 QString seq = sequence.trimmed(); 0255 0256 if (seq.isEmpty()) { 0257 return true; 0258 } 0259 0260 static const QRegularExpression modifierRx(QLatin1String("^(?:key )?(?:\\s*\\+?(?:alt|ctrl|meta|shift|super))+$"), 0261 QRegularExpression::CaseInsensitiveOption); 0262 static const QRegularExpression buttonRx(QLatin1String("^(?:button\\s+)?\\+?\\d+$"), QRegularExpression::CaseInsensitiveOption); 0263 0264 if (seq.contains(buttonRx)) { 0265 // this is a button 0266 return setButtonSequence(seq); 0267 0268 } else if (seq.contains(modifierRx)) { 0269 // this is a modifier sequence 0270 return setModifierSequence(seq); 0271 } 0272 0273 // this is probably a key sequence 0274 return setKeySequence(seq); 0275 } 0276 0277 const QString ButtonShortcut::toDisplayString() const 0278 { 0279 Q_D(const ButtonShortcut); 0280 0281 QList<KGlobalShortcutInfo> globalShortcutList; 0282 QString displayString; 0283 int buttonNr = getButton(); 0284 0285 switch (d->type) { 0286 case ShortcutType::BUTTON: 0287 displayString = buttonNumberToDescription(buttonNr); 0288 break; 0289 0290 case ShortcutType::MODIFIER: 0291 displayString = d->sequence; 0292 convertKeySequenceToQKeySequenceFormat(displayString); 0293 break; 0294 0295 case ShortcutType::KEYSTROKE: 0296 displayString = d->sequence; 0297 convertKeySequenceToQKeySequenceFormat(displayString); 0298 0299 // check if a global shortcut is assigned to this sequence 0300 globalShortcutList = KGlobalAccel::globalShortcutsByKey(QKeySequence(displayString)); 0301 0302 if (!globalShortcutList.isEmpty()) { 0303 displayString = globalShortcutList.at(0).uniqueName(); 0304 } 0305 break; 0306 0307 case ShortcutType::NONE: 0308 break; 0309 } 0310 0311 return displayString; 0312 } 0313 0314 const QString ButtonShortcut::toQKeySequenceString() const 0315 { 0316 Q_D(const ButtonShortcut); 0317 0318 QString keySequence; 0319 0320 if (d->type == ShortcutType::KEYSTROKE) { 0321 keySequence = d->sequence; 0322 convertKeySequenceToQKeySequenceFormat(keySequence); 0323 } 0324 0325 return keySequence; 0326 } 0327 0328 const QString ButtonShortcut::toString() const 0329 { 0330 Q_D(const ButtonShortcut); 0331 0332 QString shortcutString = QLatin1String("0"); 0333 0334 switch (d->type) { 0335 case ShortcutType::BUTTON: 0336 shortcutString = QString::number(d->button); 0337 break; 0338 0339 case ShortcutType::MODIFIER: 0340 case ShortcutType::KEYSTROKE: 0341 shortcutString = QString::fromLatin1("key %2").arg(d->sequence); 0342 break; 0343 0344 case ShortcutType::NONE: 0345 break; 0346 } 0347 0348 return shortcutString.toLower(); 0349 } 0350 0351 bool ButtonShortcut::convertKey(QString &key, bool fromStorage) const 0352 { 0353 QMap<QString, QString>::ConstIterator iter; 0354 QMap<QString, QString>::ConstIterator iterEnd; 0355 0356 if (fromStorage) { 0357 iter = getConvertFromStorageMap().find(key.toLower()); 0358 iterEnd = getConvertFromStorageMap().constEnd(); 0359 } else { 0360 iter = getConvertToStorageMap().find(key.toLower()); 0361 iterEnd = getConvertToStorageMap().constEnd(); 0362 } 0363 0364 if (iter == iterEnd) { 0365 return false; 0366 } 0367 0368 key = iter.value(); 0369 0370 return true; 0371 } 0372 0373 void ButtonShortcut::convertToNormalizedKeySequence(QString &sequence, bool fromStorage) const 0374 { 0375 normalizeKeySequence(sequence); 0376 0377 static const QRegularExpression rx(QStringLiteral("\\s+")); 0378 QStringList keyList = sequence.split(rx, Qt::SkipEmptyParts); 0379 bool isFirstKey = true; 0380 0381 sequence.clear(); 0382 0383 for (QStringList::iterator iter = keyList.begin(); iter != keyList.end(); ++iter) { 0384 convertKey(*iter, fromStorage); 0385 prettifyKey(*iter); 0386 0387 if (isFirstKey) { 0388 sequence.append(*iter); 0389 isFirstKey = false; 0390 } else { 0391 sequence.append(QString::fromLatin1(" %1").arg(*iter)); 0392 } 0393 } 0394 } 0395 0396 void ButtonShortcut::convertKeySequenceToStorageFormat(QString &sequence) const 0397 { 0398 convertToNormalizedKeySequence(sequence, false); 0399 } 0400 0401 void ButtonShortcut::convertKeySequenceToQKeySequenceFormat(QString &sequence) const 0402 { 0403 convertToNormalizedKeySequence(sequence, true); 0404 sequence.replace(QLatin1String(" "), QLatin1String("+")); 0405 } 0406 0407 const QMap<QString, QString> &ButtonShortcut::getConvertFromStorageMap() 0408 { 0409 static QMap<QString, QString> map = initConversionMap(true); 0410 return map; 0411 } 0412 0413 const QMap<QString, QString> &ButtonShortcut::getConvertToStorageMap() 0414 { 0415 static QMap<QString, QString> map = initConversionMap(false); 0416 return map; 0417 } 0418 0419 QMap<QString, QString> ButtonShortcut::initConversionMap(bool fromStorageMap) 0420 { 0421 QMap<QString, QString> map; 0422 0423 for (int i = 0;; ++i) { 0424 if (CONVERT_KEY_MAP_DATA[i][0] == nullptr || CONVERT_KEY_MAP_DATA[i][1] == nullptr) { 0425 return map; 0426 } 0427 0428 if (fromStorageMap) { 0429 map.insert(QLatin1String(CONVERT_KEY_MAP_DATA[i][0]), QLatin1String(CONVERT_KEY_MAP_DATA[i][1])); 0430 } else { 0431 map.insert(QLatin1String(CONVERT_KEY_MAP_DATA[i][1]), QLatin1String(CONVERT_KEY_MAP_DATA[i][0])); 0432 } 0433 } 0434 0435 return map; 0436 } 0437 0438 void ButtonShortcut::normalizeKeySequence(QString &sequence) const 0439 { 0440 // When setting a shortcut like "ctrl+x", xsetwacom will convert it to "key +ctrl +x -x" 0441 // therefore we just truncate the string on the first "-key" we find. 0442 static const QRegularExpression minusKeyRx(QLatin1String("(^|\\s)-\\S")); 0443 0444 const QRegularExpressionMatch minusKeyRxMatch = minusKeyRx.match(sequence); 0445 0446 if (minusKeyRxMatch.hasMatch()) { 0447 sequence = sequence.left(minusKeyRxMatch.capturedStart()); 0448 } 0449 0450 // cleanup leading "key " identifier from xsetwacom sequences 0451 static const QRegularExpression leadingKey(QStringLiteral("^\\s*key\\s+"), QRegularExpression::CaseInsensitiveOption); 0452 sequence.remove(leadingKey); 0453 0454 // Remove all '+' prefixes from keys. 0455 // This will convert shortcuts like "+ctrl +alt" to "ctrl alt", but not 0456 // shortcuts like "ctrl +" which is required to keep compatibility to older 0457 // (buggy) configuration files. 0458 static const QRegularExpression plusPrefixes(QStringLiteral("(^|\\s)\\+(\\S)"), QRegularExpression::CaseInsensitiveOption); 0459 sequence.replace(plusPrefixes, QLatin1String("\\1\\2")); 0460 0461 // Cleanup plus signs between keys. 0462 // This will convert shortcuts like "ctrl+alt+shift" or "Ctrl++" to "ctrl alt shift" or "Ctrl +". 0463 static const QRegularExpression cleanupPlus(QStringLiteral("(\\S)\\+(\\S)"), QRegularExpression::CaseInsensitiveOption); 0464 sequence.replace(cleanupPlus, QLatin1String("\\1 \\2")); 0465 0466 // replace multiple whitespaces with one 0467 static const QRegularExpression whitespaces(QStringLiteral("\\s{2,}"), QRegularExpression::CaseInsensitiveOption); 0468 sequence.replace(whitespaces, QLatin1String(" ")); 0469 0470 // trim the string 0471 sequence = sequence.trimmed(); 0472 } 0473 0474 void ButtonShortcut::prettifyKey(QString &key) const 0475 { 0476 if (!key.isEmpty()) { 0477 key = key.toLower(); 0478 key[0] = key.at(0).toUpper(); 0479 } 0480 } 0481 0482 bool ButtonShortcut::setButtonSequence(const QString &buttonSequence) 0483 { 0484 QString buttonNumber = buttonSequence; 0485 static const QRegularExpression rx(QStringLiteral("^\\s*button\\s+"), QRegularExpression::CaseInsensitiveOption); 0486 buttonNumber.remove(rx); 0487 0488 bool ok = false; 0489 int button = buttonNumber.toInt(&ok); 0490 0491 if (!ok) { 0492 return false; 0493 } 0494 0495 return setButton(button); 0496 } 0497 0498 bool ButtonShortcut::setKeySequence(QString sequence) 0499 { 0500 Q_D(ButtonShortcut); 0501 0502 clear(); 0503 0504 // Check if this keysequence is valid by converting it to a QKeySequence and then back 0505 // again. If both sequences are equal the sequence should be valid. This is not very 0506 // effective but it leaves us with something KDE can handle and it makes sure a key 0507 // is always converted to its default key name if there are multiple mappings. 0508 // TODO improve this 0509 QString convertedSequence = sequence; 0510 convertKeySequenceToQKeySequenceFormat(convertedSequence); 0511 0512 QKeySequence qkeySequence(convertedSequence); 0513 convertedSequence = qkeySequence.toString(); 0514 0515 convertKeySequenceToStorageFormat(convertedSequence); 0516 convertKeySequenceToStorageFormat(sequence); 0517 0518 // we do not allow invalid sequences to be set 0519 // the sequences have to be equal and only one sequence is allowed 0520 if (sequence.compare(convertedSequence, Qt::CaseInsensitive) != 0 || qkeySequence.count() != 1) { 0521 return false; 0522 } 0523 0524 d->type = ShortcutType::KEYSTROKE; 0525 d->sequence = sequence; 0526 0527 return true; 0528 } 0529 0530 bool ButtonShortcut::setModifierSequence(QString sequence) 0531 { 0532 Q_D(ButtonShortcut); 0533 0534 clear(); 0535 convertKeySequenceToStorageFormat(sequence); 0536 0537 d->type = ShortcutType::MODIFIER; 0538 d->sequence = sequence; 0539 0540 return true; 0541 }