File indexing completed on 2024-12-22 04:14:06
0001 /** 0002 * SPDX-FileCopyrightText: 1997 Nicolas Hadacek <hadacek@kde.org> 0003 * SPDX-FileCopyrightText: 1998 Matthias Ettrich <ettrich@kde.org> 0004 * SPDX-FileCopyrightText: 2001 Ellis Whitehead <ellis@kde.org> 0005 * SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org> 0006 * SPDX-FileCopyrightText: 2007 Roberto Raggi <roberto@kdevelop.org> 0007 * SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com> 0008 * SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz> 0009 * SPDX-FileCopyrightText: 2015 Michael Abrahams <miabraha@gmail.com> 0010 * 0011 * SPDX-License-Identifier: GPL-3.0-or-later 0012 */ 0013 0014 #include "KisShortcutsEditor.h" 0015 #include "KisShortcutsEditor_p.h" 0016 #include <QHeaderView> 0017 #include <QTreeWidget> 0018 #include <QDebug> 0019 #include <QTextTable> 0020 #include <QTextDocument> 0021 #include <QPrinter> 0022 #include <QPrintDialog> 0023 #include <ksharedconfig.h> 0024 #include <KConfigGroup> 0025 #include "kis_action_registry.h" 0026 #include <KisKineticScroller.h> 0027 0028 //--------------------------------------------------------------------- 0029 // KisShortcutsEditorPrivate 0030 //--------------------------------------------------------------------- 0031 0032 namespace { 0033 KisShortcutsEditorItem *itemFromIndex(QTreeWidget *const w, const QModelIndex &index) 0034 { 0035 QTreeWidgetItem *item = static_cast<QTreeWidgetHack *>(w)->itemFromIndex(index); 0036 if (item && item->type() == ActionItem) { 0037 return static_cast<KisShortcutsEditorItem *>(item); 0038 } 0039 return 0; 0040 } 0041 } 0042 0043 0044 KisShortcutsEditorPrivate::KisShortcutsEditorPrivate(KisShortcutsEditor *q) 0045 : q(q), 0046 delegate(0) 0047 {} 0048 0049 void KisShortcutsEditorPrivate::initGUI(KisShortcutsEditor::ActionTypes types, 0050 KisShortcutsEditor::LetterShortcuts allowLetterShortcuts) 0051 { 0052 actionTypes = types; 0053 0054 ui.setupUi(q); 0055 q->layout()->setMargin(0); 0056 ui.searchFilter->searchLine()->setTreeWidget(ui.list); // Plug into search line 0057 ui.list->header()->setSectionResizeMode(QHeaderView::ResizeToContents); 0058 0059 // Create the Delegate. It is responsible for the KKeySequenceWidgets that 0060 // really change the shortcuts. 0061 delegate = new KisShortcutsEditorDelegate( 0062 ui.list, 0063 allowLetterShortcuts == KisShortcutsEditor::LetterShortcutsAllowed); 0064 0065 ui.list->setItemDelegate(delegate); 0066 ui.list->setSelectionBehavior(QAbstractItemView::SelectItems); 0067 ui.list->setSelectionMode(QAbstractItemView::SingleSelection); 0068 //we have our own editing mechanism 0069 ui.list->setEditTriggers(QAbstractItemView::NoEditTriggers); 0070 ui.list->setAlternatingRowColors(true); 0071 0072 QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(ui.list); 0073 if (scroller){ 0074 QObject::connect(scroller, SIGNAL(stateChanged(QScroller::State)), 0075 q, SLOT(slotScrollerStateChanged(QScroller::State))); 0076 } 0077 0078 QObject::connect(delegate, SIGNAL(shortcutChanged(QVariant,QModelIndex)), 0079 q, SLOT(capturedShortcut(QVariant,QModelIndex))); 0080 //hide the editor widget when its item becomes hidden 0081 QObject::connect(ui.searchFilter->searchLine(), SIGNAL(hiddenChanged(QTreeWidgetItem*,bool)), 0082 delegate, SLOT(hiddenBySearchLine(QTreeWidgetItem*,bool))); 0083 //Expand items when searching 0084 QObject::connect(ui.searchFilter->searchLine(), SIGNAL(searchUpdated(QString)), 0085 q, SLOT(searchUpdated(QString))); 0086 0087 ui.searchFilter->setFocus(); 0088 } 0089 0090 void KisShortcutsEditorPrivate::setActionTypes(KisShortcutsEditor::ActionTypes types) 0091 { 0092 if (actionTypes == types) { 0093 return; 0094 } 0095 actionTypes = types; 0096 0097 // show/hide the sections based on new selection 0098 QHeaderView *header = ui.list->header(); 0099 header->showSection(LocalPrimary); 0100 header->showSection(LocalAlternate); 0101 } 0102 0103 bool KisShortcutsEditorPrivate::addAction(QAction *action, QTreeWidgetItem *hier[], hierarchyLevel level) 0104 { 0105 0106 // We cannot handle unnamed actions, even though Qt automatically assigns 0107 // them names prefixed by `unnamed-`. Unfortunately those automatic names 0108 // are not guaranteed to be the same each time, especially not within 0109 // KisParts, so they cannot be reliably saved and loaded. 0110 QString actionName = action->objectName(); 0111 if (actionName.isEmpty() || actionName.startsWith(QStringLiteral("unnamed-"))) { 0112 qCritical() << "Skipping action without name " << action->text() << "," << actionName << "!"; 0113 return false; 0114 } 0115 0116 // Construct the actual treeview items. The work happens here. 0117 // 0118 // Don't feed the editor raw QActions. This code requires that the 0119 // "defaultShortcut" dynamic property be set. 0120 // 0121 // Note: Krita never sets the property "isShortcutConfigurable". 0122 // Perhaps it could be useful. 0123 const QVariant value = action->property("isShortcutConfigurable"); 0124 if (!value.isValid() || value.toBool()) { 0125 new KisShortcutsEditorItem((hier[level]), action); 0126 return true; 0127 } 0128 0129 return false; 0130 } 0131 0132 void KisShortcutsEditorPrivate::allDefault() 0133 { 0134 for (QTreeWidgetItemIterator it(ui.list); (*it); ++it) { 0135 if (!(*it)->parent() || (*it)->type() != ActionItem) { 0136 continue; 0137 } 0138 0139 KisShortcutsEditorItem *item = static_cast<KisShortcutsEditorItem *>(*it); 0140 QAction *act = item->m_action; 0141 0142 QList<QKeySequence> defaultShortcuts = act->property("defaultShortcuts").value<QList<QKeySequence> >(); 0143 if (act->shortcuts() != defaultShortcuts) { 0144 QKeySequence primary = defaultShortcuts.isEmpty() ? QKeySequence() : defaultShortcuts.at(0); 0145 QKeySequence alternate = defaultShortcuts.size() <= 1 ? QKeySequence() : defaultShortcuts.at(1); 0146 changeKeyShortcut(item, LocalPrimary, primary); 0147 changeKeyShortcut(item, LocalAlternate, alternate); 0148 } 0149 } 0150 } 0151 0152 //static 0153 0154 QTreeWidgetItem *KisShortcutsEditorPrivate::findOrMakeItem(QTreeWidgetItem *parent, const QString &name) 0155 { 0156 for (int i = 0; i < parent->childCount(); i++) { 0157 QTreeWidgetItem *child = parent->child(i); 0158 if (child->text(0) == name) { 0159 return child; 0160 } 0161 } 0162 QTreeWidgetItem *ret = new QTreeWidgetItem(parent, NonActionItem); 0163 ret->setText(0, name); 0164 ui.list->expandItem(ret); 0165 ret->setFlags(ret->flags() & ~Qt::ItemIsSelectable); 0166 return ret; 0167 } 0168 0169 //private slot 0170 void KisShortcutsEditorPrivate::capturedShortcut(const QVariant &newShortcut, const QModelIndex &index) 0171 { 0172 //dispatch to the right handler 0173 if (!index.isValid()) { 0174 return; 0175 } 0176 int column = index.column(); 0177 KisShortcutsEditorItem *item = itemFromIndex(ui.list, index); 0178 Q_ASSERT(item); 0179 0180 if (column >= LocalPrimary && column <= Id) { 0181 changeKeyShortcut(item, column, newShortcut.value<QKeySequence>()); 0182 } 0183 } 0184 0185 void KisShortcutsEditorPrivate::changeKeyShortcut(KisShortcutsEditorItem *item, uint column, const QKeySequence &capture) 0186 { 0187 // The keySequence we get is cleared by KisKKeySequenceWidget. No conflicts. 0188 if (capture == item->keySequence(column)) { 0189 return; 0190 } 0191 0192 item->setKeySequence(column, capture); 0193 q->keyChange(); 0194 //force view update 0195 item->setText(column, capture.toString(QKeySequence::NativeText)); 0196 } 0197 0198 0199 void KisShortcutsEditorPrivate::clearConfiguration() 0200 { 0201 for (QTreeWidgetItemIterator it(ui.list); (*it); ++it) { 0202 if (!(*it)->parent()) { 0203 continue; 0204 } 0205 0206 KisShortcutsEditorItem *item = static_cast<KisShortcutsEditorItem *>(*it); 0207 0208 changeKeyShortcut(item, LocalPrimary, QKeySequence()); 0209 changeKeyShortcut(item, LocalAlternate, QKeySequence()); 0210 0211 } 0212 } 0213 0214 /*TODO for the printShortcuts function 0215 Nice to have features (which I'm not sure I can do before may due to 0216 more important things): 0217 0218 - adjust the general page borders, IMHO they're too wide 0219 0220 - add a custom printer options page that allows to filter out all 0221 actions that don't have a shortcut set to reduce this list. IMHO this 0222 should be optional as people might want to simply print all and when 0223 they find a new action that they assign a shortcut they can simply use 0224 a pen to fill out the empty space 0225 0226 - find a way to align the Main/Alternate entries in the shortcuts column without 0227 adding borders. I first did this without a nested table but instead simply 0228 added 3 rows and merged the 3 cells in the Action name and description column, 0229 but unfortunately I didn't find a way to remove the borders between the 6 0230 shortcut cells. 0231 */ 0232 void KisShortcutsEditorPrivate::printShortcuts() const 0233 { 0234 QTreeWidgetItem *root = ui.list->invisibleRootItem(); 0235 QTextDocument doc; 0236 0237 doc.setDefaultFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); 0238 0239 QTextCursor cursor(&doc); 0240 cursor.beginEditBlock(); 0241 QTextCharFormat headerFormat; 0242 headerFormat.setProperty(QTextFormat::FontSizeAdjustment, 3); 0243 headerFormat.setFontWeight(QFont::Bold); 0244 cursor.insertText(i18nc("header for an applications shortcut list", "Shortcuts for %1", 0245 QGuiApplication::applicationDisplayName()), 0246 headerFormat); 0247 QTextCharFormat componentFormat; 0248 componentFormat.setProperty(QTextFormat::FontSizeAdjustment, 2); 0249 componentFormat.setFontWeight(QFont::Bold); 0250 QTextBlockFormat componentBlockFormat = cursor.blockFormat(); 0251 componentBlockFormat.setTopMargin(16); 0252 componentBlockFormat.setBottomMargin(16); 0253 0254 QTextTableFormat tableformat; 0255 tableformat.setHeaderRowCount(1); 0256 tableformat.setCellPadding(4.0); 0257 tableformat.setCellSpacing(0); 0258 tableformat.setBorderStyle(QTextFrameFormat::BorderStyle_Solid); 0259 tableformat.setBorder(0.5); 0260 0261 QList<QPair<QString, ColumnDesignation> > shortcutTitleToColumn; 0262 shortcutTitleToColumn << qMakePair(i18n("Main:"), LocalPrimary); 0263 shortcutTitleToColumn << qMakePair(i18n("Alternate:"), LocalAlternate); 0264 0265 for (int i = 0; i < root->childCount(); i++) { 0266 QTreeWidgetItem *item = root->child(i); 0267 cursor.insertBlock(componentBlockFormat, componentFormat); 0268 cursor.insertText(item->text(0)); 0269 0270 QTextTable *table = cursor.insertTable(1, 3); 0271 table->setFormat(tableformat); 0272 int currow = 0; 0273 0274 QTextTableCell cell = table->cellAt(currow, 0); 0275 QTextCharFormat format = cell.format(); 0276 format.setFontWeight(QFont::Bold); 0277 cell.setFormat(format); 0278 cell.firstCursorPosition().insertText(i18n("Action Name")); 0279 0280 cell = table->cellAt(currow, 1); 0281 cell.setFormat(format); 0282 cell.firstCursorPosition().insertText(i18n("Shortcuts")); 0283 0284 cell = table->cellAt(currow, 2); 0285 cell.setFormat(format); 0286 cell.firstCursorPosition().insertText(i18n("Description")); 0287 currow++; 0288 0289 for (QTreeWidgetItemIterator it(item); *it; ++it) { 0290 if ((*it)->type() != ActionItem) { 0291 continue; 0292 } 0293 0294 KisShortcutsEditorItem *editoritem = static_cast<KisShortcutsEditorItem *>(*it); 0295 table->insertRows(table->rows(), 1); 0296 QVariant data = editoritem->data(Name, Qt::DisplayRole); 0297 table->cellAt(currow, 0).firstCursorPosition().insertText(data.toString()); 0298 0299 QTextTable *shortcutTable = 0; 0300 for (int k = 0; k < shortcutTitleToColumn.count(); k++) { 0301 data = editoritem->data(shortcutTitleToColumn.at(k).second, Qt::DisplayRole); 0302 QString key = data.value<QKeySequence>().toString(); 0303 0304 if (!key.isEmpty()) { 0305 if (!shortcutTable) { 0306 shortcutTable = table->cellAt(currow, 1).firstCursorPosition().insertTable(1, 2); 0307 QTextTableFormat shortcutTableFormat = tableformat; 0308 shortcutTableFormat.setCellSpacing(0.0); 0309 shortcutTableFormat.setHeaderRowCount(0); 0310 shortcutTableFormat.setBorder(0.0); 0311 shortcutTable->setFormat(shortcutTableFormat); 0312 } else { 0313 shortcutTable->insertRows(shortcutTable->rows(), 1); 0314 } 0315 shortcutTable->cellAt(shortcutTable->rows() - 1, 0)\ 0316 .firstCursorPosition().insertText(shortcutTitleToColumn.at(k).first); 0317 shortcutTable->cellAt(shortcutTable->rows() - 1, 1)\ 0318 .firstCursorPosition().insertText(key); 0319 } 0320 } 0321 0322 QAction *action = editoritem->m_action; 0323 cell = table->cellAt(currow, 2); 0324 format = cell.format(); 0325 format.setProperty(QTextFormat::FontSizeAdjustment, -1); 0326 cell.setFormat(format); 0327 cell.firstCursorPosition().insertHtml(action->whatsThis()); 0328 0329 currow++; 0330 } 0331 cursor.movePosition(QTextCursor::End); 0332 } 0333 cursor.endEditBlock(); 0334 0335 QPrinter printer; 0336 QPrintDialog *dlg = new QPrintDialog(&printer, q); 0337 if (dlg->exec() == QDialog::Accepted) { 0338 doc.print(&printer); 0339 } 0340 delete dlg; 0341 }