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 }