Warning, file /multimedia/kid3/src/app/qt/shortcutsmodel.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /** 0002 * \file shortcutsmodel.cpp 0003 * Keyboard shortcuts configuration tree model. 0004 * 0005 * \b Project: Kid3 0006 * \author Urs Fleisch 0007 * \date 29 Dec 2011 0008 * 0009 * Copyright (C) 2011-2024 Urs Fleisch 0010 * 0011 * This file is part of Kid3. 0012 * 0013 * Kid3 is free software; you can redistribute it and/or modify 0014 * it under the terms of the GNU General Public License as published by 0015 * the Free Software Foundation; either version 2 of the License, or 0016 * (at your option) any later version. 0017 * 0018 * Kid3 is distributed in the hope that it will be useful, 0019 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0020 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0021 * GNU General Public License for more details. 0022 * 0023 * You should have received a copy of the GNU General Public License 0024 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0025 */ 0026 0027 #include "shortcutsmodel.h" 0028 #include "isettings.h" 0029 #include <QAction> 0030 #include <QFont> 0031 0032 namespace { 0033 0034 constexpr int TopLevelId = -1; 0035 0036 bool isTopLevelItem(const QModelIndex& index) 0037 { 0038 return index.internalId() == static_cast<quintptr>(TopLevelId); 0039 } 0040 0041 } 0042 0043 /** 0044 * Constructor. 0045 * @param parent parent widget 0046 */ 0047 ShortcutsModel::ShortcutsModel(QObject* parent) : QAbstractItemModel(parent) 0048 { 0049 setObjectName(QLatin1String("ShortcutsModel")); 0050 } 0051 0052 /** 0053 * Get item flags for index. 0054 * @param index model index 0055 * @return item flags 0056 */ 0057 Qt::ItemFlags ShortcutsModel::flags(const QModelIndex& index) const 0058 { 0059 Qt::ItemFlags itemFlags = QAbstractItemModel::flags(index); 0060 if (index.isValid() && index.column() == ShortcutColumn) { 0061 itemFlags |= Qt::ItemIsEditable; 0062 } 0063 return itemFlags; 0064 } 0065 0066 /** 0067 * Get group if a model index is a valid index for a group item. 0068 * @param index index to check 0069 * @return group if index is for a group item else 0 0070 */ 0071 const ShortcutsModel::ShortcutGroup* ShortcutsModel::shortcutGroupForIndex( 0072 const QModelIndex& index) const 0073 { 0074 if (index.column() == 0 && 0075 index.row() >= 0 && index.row() < m_shortcutGroups.size() && 0076 isTopLevelItem(index)) { 0077 return &m_shortcutGroups.at(index.row()); 0078 } 0079 return nullptr; 0080 } 0081 0082 /** 0083 * Get data for a given role. 0084 * @param index model index 0085 * @param role item data role 0086 * @return data for role 0087 */ 0088 QVariant ShortcutsModel::data(const QModelIndex& index, int role) const 0089 { 0090 if (index.isValid()) { 0091 if (QModelIndex parentIndex = index.parent(); parentIndex.isValid()) { 0092 if (const ShortcutGroup* group = shortcutGroupForIndex(parentIndex)) { 0093 if (index.row() >= 0 && index.row() < group->size()) { 0094 const ShortcutItem& shortcutItem = group->at(index.row()); 0095 if (index.column() == ActionColumn) { 0096 if (role == Qt::DisplayRole) { 0097 return shortcutItem.actionText(); 0098 } 0099 if (role == Qt::FontRole) { 0100 if (shortcutItem.isCustomShortcutActive()) { 0101 QFont font; 0102 font.setBold(true); 0103 return font; 0104 } 0105 } 0106 } else if (index.column() == ShortcutColumn) { 0107 if (role == Qt::DisplayRole || role == Qt::EditRole) { 0108 QKeySequence keySequence = QKeySequence::fromString( 0109 shortcutItem.activeShortcut(), QKeySequence::PortableText); 0110 return keySequence.toString(QKeySequence::NativeText); 0111 } 0112 if (role == Qt::ToolTipRole) { 0113 return tr("Press F2 or double click to edit cell contents."); 0114 } 0115 } 0116 } 0117 } 0118 } else { 0119 if (const ShortcutGroup* group = shortcutGroupForIndex(index)) { 0120 if (role == Qt::DisplayRole) { 0121 return group->context(); 0122 } 0123 } 0124 } 0125 } 0126 return QVariant(); 0127 } 0128 0129 /** 0130 * Set data for a given role. 0131 * @param index model index 0132 * @param value data value 0133 * @param role item data role 0134 * @return true if successful 0135 */ 0136 bool ShortcutsModel::setData(const QModelIndex& index, const QVariant& value, 0137 int role) 0138 { 0139 if (index.isValid() && index.column() == ShortcutColumn && role == Qt::EditRole) { 0140 if (QModelIndex parentIndex = index.parent(); parentIndex.isValid()) { 0141 if (auto group = 0142 const_cast<ShortcutGroup*>(shortcutGroupForIndex(parentIndex))) { 0143 if (index.row() >= 0 && index.row() < group->size()) { 0144 ShortcutItem si((*group)[index.row()]); 0145 const QString valueString = !value.isNull() 0146 ? value.value<QKeySequence>().toString(QKeySequence::PortableText) 0147 : QString(); 0148 si.setCustomShortcut(valueString); 0149 QString keyString(si.activeShortcut()); 0150 if (!keyString.isEmpty()) { 0151 const auto gs = m_shortcutGroups; 0152 for (const ShortcutGroup& g : gs) { 0153 for (const ShortcutItem& i : g) { 0154 if (i.activeShortcut() == keyString && 0155 si.action() != i.action() && 0156 (si.action()->shortcutContext() != Qt::WidgetShortcut || 0157 i.action()->shortcutContext() != Qt::WidgetShortcut)) { 0158 emit shortcutAlreadyUsed(keyString, g.context(), i.action()); 0159 return false; 0160 } 0161 } 0162 } 0163 } 0164 (*group)[index.row()].setCustomShortcut(valueString); 0165 emit dataChanged(index.sibling(index.row(), ActionColumn), index); 0166 emit shortcutSet(keyString, group->context(), si.action()); 0167 return true; 0168 } 0169 } 0170 } 0171 } 0172 return false; 0173 } 0174 0175 /** 0176 * Get data for header section. 0177 * @param section column or row 0178 * @param orientation horizontal or vertical 0179 * @param role item data role 0180 * @return header data for role 0181 */ 0182 QVariant ShortcutsModel::headerData(int section, Qt::Orientation orientation, 0183 int role) const 0184 { 0185 if (role != Qt::DisplayRole) 0186 return QVariant(); 0187 if (orientation == Qt::Horizontal) { 0188 if (section == ActionColumn) { 0189 return tr("Action"); 0190 } 0191 if (section == ShortcutColumn) { 0192 return tr("Shortcut"); 0193 } 0194 } 0195 return section + 1; 0196 } 0197 0198 /** 0199 * Get number of rows. 0200 * @param parent parent model index 0201 * @return number of rows, if parent is valid number of children 0202 */ 0203 int ShortcutsModel::rowCount(const QModelIndex& parent) const 0204 { 0205 if (parent.isValid()) { 0206 if (const ShortcutGroup* group = shortcutGroupForIndex(parent)) { 0207 return group->size(); 0208 } 0209 return 0; 0210 } 0211 return m_shortcutGroups.size(); 0212 } 0213 0214 /** 0215 * Get number of columns. 0216 * @param parent parent model index 0217 * @return number of columns for children of given @a parent 0218 */ 0219 int ShortcutsModel::columnCount(const QModelIndex& parent) const 0220 { 0221 Q_UNUSED(parent) 0222 return NumColumns; 0223 } 0224 0225 /** 0226 * Get model index of item. 0227 * @param row row of item 0228 * @param column column of item 0229 * @param parent index of parent item 0230 * @return model index of item 0231 */ 0232 QModelIndex ShortcutsModel::index(int row, int column, 0233 const QModelIndex& parent) const 0234 { 0235 if (parent.isValid()) { 0236 if (const ShortcutGroup* group; 0237 (group = shortcutGroupForIndex(parent)) != nullptr && 0238 column >= 0 && column < NumColumns && 0239 row >= 0 && row <= group->size()) { 0240 return createIndex(row, column, parent.row()); 0241 } 0242 } else { 0243 if (column == 0 && 0244 row >= 0 && row < m_shortcutGroups.size()) { 0245 return createIndex(row, column, TopLevelId); 0246 } 0247 } 0248 return QModelIndex(); 0249 } 0250 0251 /** 0252 * Get parent of item. 0253 * @param index model index of item 0254 * @return model index of parent item 0255 */ 0256 QModelIndex ShortcutsModel::parent(const QModelIndex& index) const 0257 { 0258 if (int id = index.internalId(); 0259 id >= 0 && id < m_shortcutGroups.size()) { 0260 return createIndex(id, 0, TopLevelId); 0261 } 0262 return QModelIndex(); 0263 } 0264 0265 /** 0266 * Register an action. 0267 * 0268 * @param action action to be added to model 0269 * @param context context of action 0270 */ 0271 void ShortcutsModel::registerAction(QAction* action, const QString& context) 0272 { 0273 ShortcutItem item(action); 0274 ShortcutGroup group(context); 0275 0276 auto it = m_shortcutGroups.begin(); // clazy:exclude=detaching-member 0277 for (; it != m_shortcutGroups.end(); ++it) { 0278 if (it->context() == group.context()) { 0279 it->append(item); 0280 break; 0281 } 0282 } 0283 if (it == m_shortcutGroups.end()) { 0284 group.append(item); 0285 m_shortcutGroups.append(group); 0286 } 0287 } 0288 0289 /** 0290 * Unregister an action. 0291 * 0292 * @param action action to be removed from model 0293 * @param context context of action 0294 */ 0295 void ShortcutsModel::unregisterAction(const QAction* action, const QString& context) 0296 { 0297 for (auto git = m_shortcutGroups.begin(); git != m_shortcutGroups.end(); ++git) { // clazy:exclude=detaching-member 0298 if (git->context() == context) { 0299 for (auto iit = git->begin(); iit != git->end(); ++iit) { 0300 if (iit->action() == action) { 0301 git->erase(iit); 0302 break; 0303 } 0304 } 0305 if (git->isEmpty()) { 0306 m_shortcutGroups.erase(git); 0307 } 0308 break; 0309 } 0310 } 0311 } 0312 0313 /** 0314 * Get mapping of shortcut names to key sequences. 0315 * @return shortcut map. 0316 */ 0317 QMap<QString, QKeySequence> ShortcutsModel::shortcutsMap() const 0318 { 0319 QMap<QString, QKeySequence> map; 0320 for (auto git = m_shortcutGroups.constBegin(); 0321 git != m_shortcutGroups.constEnd(); 0322 ++git) { 0323 for (auto iit = git->constBegin(); iit != git->constEnd(); ++iit) { 0324 if (const QAction* action = iit->action()) { 0325 if (QString name = action->objectName(); !name.isEmpty()) { 0326 map.insert(name, action->shortcut()); 0327 } 0328 } 0329 } 0330 } 0331 return map; 0332 } 0333 0334 /** 0335 * Assign the shortcuts which have been changed to their actions. 0336 * 0337 * @return true if there was at least one shortcut changed 0338 */ 0339 bool ShortcutsModel::assignChangedShortcuts() 0340 { 0341 bool changed = false; 0342 for (auto git = m_shortcutGroups.begin(); git != m_shortcutGroups.end(); ++git) { // clazy:exclude=detaching-member 0343 for (auto iit = git->begin(); iit != git->end(); ++iit) { 0344 if (iit->isCustomShortcutChanged()) { 0345 iit->assignCustomShortcut(); 0346 changed = true; 0347 } 0348 } 0349 } 0350 return changed; 0351 } 0352 0353 /** 0354 * Forget about all changed shortcuts. 0355 */ 0356 void ShortcutsModel::discardChangedShortcuts() 0357 { 0358 for (auto git = m_shortcutGroups.begin(); git != m_shortcutGroups.end(); ++git) { // clazy:exclude=detaching-member 0359 for (auto iit = git->begin(); iit != git->end(); ++iit) { 0360 iit->revertCustomShortcut(); 0361 } 0362 } 0363 } 0364 0365 /** 0366 * Clear all shortcuts to their default values. 0367 */ 0368 void ShortcutsModel::clearShortcuts() 0369 { 0370 beginResetModel(); 0371 for (auto git = m_shortcutGroups.begin(); git != m_shortcutGroups.end(); ++git) { // clazy:exclude=detaching-member 0372 for (auto iit = git->begin(); iit != git->end(); ++iit) { 0373 iit->clearCustomShortcut(); 0374 } 0375 } 0376 endResetModel(); 0377 } 0378 0379 /** 0380 * Save the shortcuts to a given configuration. 0381 * 0382 * @param config configuration settings 0383 */ 0384 void ShortcutsModel::writeToConfig(ISettings* config) const 0385 { 0386 config->beginGroup(QLatin1String("Shortcuts")); 0387 config->remove(QLatin1String("")); 0388 for (auto git = m_shortcutGroups.constBegin(); 0389 git != m_shortcutGroups.constEnd(); 0390 ++git) { 0391 for (auto iit = git->constBegin(); iit != git->constEnd(); ++iit) { 0392 if (QString actionName(iit->action() ? iit->action()->objectName() 0393 : QLatin1String("")); 0394 !actionName.isEmpty()) { 0395 if (iit->isCustomShortcutActive()) { 0396 config->setValue(actionName, iit->customShortcut()); 0397 } 0398 } else { 0399 qWarning("Action %s does not have an object name", 0400 qPrintable(iit->actionText())); 0401 } 0402 } 0403 } 0404 config->endGroup(); 0405 } 0406 0407 /** 0408 * Read the shortcuts from a given configuration. 0409 * 0410 * @param config configuration settings 0411 */ 0412 void ShortcutsModel::readFromConfig(ISettings* config) 0413 { 0414 config->beginGroup(QLatin1String("Shortcuts")); 0415 for (auto git = m_shortcutGroups.begin(); git != m_shortcutGroups.end(); ++git) { // clazy:exclude=detaching-member 0416 for (auto iit = git->begin(); iit != git->end(); ++iit) { 0417 if (QString actionName(iit->action() ? iit->action()->objectName() 0418 : QLatin1String("")); 0419 !actionName.isEmpty() && config->contains(actionName)) { 0420 QString keyStr(config->value(actionName, QString()).toString()); 0421 // Previous versions stored native text, check if it is such a 0422 // string and try to convert it. 0423 if (QKeySequence::fromString(keyStr, QKeySequence::PortableText) 0424 .toString(QKeySequence::PortableText) != keyStr) { 0425 if (QKeySequence nativeKeySequence = 0426 QKeySequence::fromString(keyStr, QKeySequence::NativeText); 0427 nativeKeySequence.toString(QKeySequence::NativeText) == keyStr) { 0428 QString nativeKeyStr = keyStr; 0429 keyStr = nativeKeySequence.toString(QKeySequence::PortableText); 0430 qWarning("Converting shortcut '%s' to '%s'", 0431 qPrintable(nativeKeyStr), qPrintable(keyStr)); 0432 } 0433 } 0434 iit->setCustomShortcut(keyStr); 0435 iit->assignCustomShortcut(); 0436 } 0437 } 0438 } 0439 config->endGroup(); 0440 } 0441 0442 0443 ShortcutsModel::ShortcutItem::ShortcutItem(QAction* act) 0444 : m_action(act), m_defaultShortcut(m_action->shortcut().toString()) 0445 { 0446 } 0447 0448 void ShortcutsModel::ShortcutItem::setCustomShortcut(const QString& shortcut) 0449 { 0450 m_customShortcut = shortcut != m_defaultShortcut ? shortcut : QString(); 0451 } 0452 0453 void ShortcutsModel::ShortcutItem::revertCustomShortcut() 0454 { 0455 m_customShortcut = m_oldCustomShortcut; 0456 } 0457 0458 void ShortcutsModel::ShortcutItem::clearCustomShortcut() 0459 { 0460 m_customShortcut.clear(); 0461 } 0462 0463 void ShortcutsModel::ShortcutItem::assignCustomShortcut() 0464 { 0465 m_action->setShortcut(QKeySequence(activeShortcut())); 0466 m_oldCustomShortcut = m_customShortcut; 0467 } 0468 0469 QString ShortcutsModel::ShortcutItem::actionText() const 0470 { 0471 return m_action ? m_action->text().remove(QLatin1Char('&')) 0472 : QLatin1String(""); 0473 } 0474 0475 0476 ShortcutsModel::ShortcutGroup::ShortcutGroup(const QString& ctx) 0477 : m_context(ctx) 0478 { 0479 m_context.remove(QLatin1Char('&')); 0480 }