1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 1998 Mark Donohoe <donohoe@kde.org>
4 SPDX-FileCopyrightText: 1997 Nicolas Hadacek <hadacek@kde.org>
5 SPDX-FileCopyrightText: 1998 Matthias Ettrich <ettrich@kde.org>
6 SPDX-FileCopyrightText: 2001 Ellis Whitehead <ellis@kde.org>
7 SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org>
8 SPDX-FileCopyrightText: 2007 Roberto Raggi <roberto@kdevelop.org>
9 SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com>
10 SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz>
11
12 SPDX-License-Identifier: LGPL-2.0-or-later
13*/
14
15#include "config-xmlgui.h"
16
17#include "kshortcutseditor.h"
18
19// The following is needed for KShortcutsEditorPrivate and QTreeWidgetHack
20#include "debug.h"
21#include "kshortcutsdialog_p.h"
22
23#include <QAction>
24#include <QHeaderView>
25#include <QList>
26#include <QObject>
27#include <QPrintDialog>
28#include <QPrinter>
29#include <QTextCursor>
30#include <QTextDocument>
31#include <QTextTable>
32#include <QTextTableFormat>
33#include <QTimer>
34
35#include <KConfig>
36#include <KConfigGroup>
37#if HAVE_GLOBALACCEL
38#include <KGlobalAccel>
39#endif
40#include "kactioncategory.h"
41#include "kactioncollection.h"
42#include <KTreeWidgetSearchLine>
43
44//---------------------------------------------------------------------
45// KShortcutsEditor
46//---------------------------------------------------------------------
47
48KShortcutsEditor::KShortcutsEditor(KActionCollection *collection, QWidget *parent, ActionTypes actionType, LetterShortcuts allowLetterShortcuts)
49 : QWidget(parent)
50 , d(new KShortcutsEditorPrivate(this))
51{
52 d->initGUI(actionTypes: actionType, allowLetterShortcuts);
53 addCollection(collection);
54}
55
56KShortcutsEditor::KShortcutsEditor(QWidget *parent, ActionTypes actionType, LetterShortcuts allowLetterShortcuts)
57 : QWidget(parent)
58 , d(new KShortcutsEditorPrivate(this))
59{
60 d->initGUI(actionTypes: actionType, allowLetterShortcuts);
61}
62
63KShortcutsEditor::~KShortcutsEditor()
64{
65 // reset all pending changes
66 undo();
67}
68
69bool KShortcutsEditor::isModified() const
70{
71 // Iterate over all items
72 QTreeWidgetItemIterator it(d->ui.list, QTreeWidgetItemIterator::NoChildren);
73
74 for (; (*it); ++it) {
75 KShortcutsEditorItem *item = dynamic_cast<KShortcutsEditorItem *>(*it);
76 if (item && item->isModified()) {
77 return true;
78 }
79 }
80 return false;
81}
82
83void KShortcutsEditor::clearCollections()
84{
85 d->delegate->contractAll();
86 d->ui.list->clear();
87 d->actionCollections.clear();
88 QTimer::singleShot(interval: 0, receiver: this, slot: &KShortcutsEditor::resizeColumns);
89}
90
91void KShortcutsEditor::addCollection(KActionCollection *collection, const QString &title)
92{
93 // KXmlGui add action collections unconditionally. If some plugin doesn't
94 // provide actions we don't want to create empty subgroups.
95 if (collection->isEmpty()) {
96 return;
97 }
98
99 // We add a bunch of items. Prevent the treewidget from permanently
100 // updating.
101 setUpdatesEnabled(false);
102
103 d->actionCollections.append(t: collection);
104 // Forward our actionCollections to the delegate which does the conflict
105 // checking.
106 d->delegate->setCheckActionCollections(d->actionCollections);
107 QString displayTitle = title;
108 if (displayTitle.isEmpty()) {
109 // Use the programName (Translated).
110 displayTitle = collection->componentDisplayName();
111 }
112
113 KShortcutsEditorPrivate::HierarchyInfo hierarchy;
114 hierarchy.root = d->ui.list->invisibleRootItem();
115 hierarchy.program = d->findOrMakeItem(parent: hierarchy.root, name: displayTitle);
116 hierarchy.action = nullptr;
117
118 // Set to remember which actions we have seen.
119 QSet<QAction *> actionsSeen;
120
121 // Add all categories in their own subtree below the collections root node
122 const QList<KActionCategory *> categories = collection->findChildren<KActionCategory *>();
123 for (KActionCategory *category : categories) {
124 hierarchy.action = d->findOrMakeItem(parent: hierarchy.program, name: category->text());
125 const auto categoryActions = category->actions();
126 for (QAction *action : categoryActions) {
127 // Set a marker that we have seen this action
128 actionsSeen.insert(value: action);
129 d->addAction(action, parent: hierarchy.action);
130 }
131 }
132
133 // The rest of the shortcuts are added as direct children of the action
134 // collections root node
135 const auto collectionActions = collection->actions();
136 for (QAction *action : collectionActions) {
137 if (actionsSeen.contains(value: action)) {
138 continue;
139 }
140
141 d->addAction(action, parent: hierarchy.program);
142 }
143
144 // sort the list
145 d->ui.list->sortItems(column: Name, order: Qt::AscendingOrder);
146
147 // Hide Global shortcuts columns if there are no global shortcuts
148 d->setGlobalColumnsHidden(!d->m_hasAnyGlobalShortcuts);
149
150 // ...And hide local shortcuts columns if there are no local shortcuts
151 d->setLocalColumnsHidden(!d->m_hasAnyLocalShortcuts);
152
153 // re-enable updating
154 setUpdatesEnabled(true);
155
156 QTimer::singleShot(interval: 0, receiver: this, slot: &KShortcutsEditor::resizeColumns);
157}
158
159void KShortcutsEditor::importConfiguration(KConfigBase *config)
160{
161 d->importConfiguration(config);
162}
163
164void KShortcutsEditor::exportConfiguration(KConfigBase *config) const
165{
166 Q_ASSERT(config);
167 if (!config) {
168 return;
169 }
170
171 if (d->actionTypes & KShortcutsEditor::GlobalAction) {
172 QString groupName(QStringLiteral("Global Shortcuts"));
173 KConfigGroup group(config, groupName);
174 for (KActionCollection *collection : std::as_const(t&: d->actionCollections)) {
175 collection->exportGlobalShortcuts(config: &group, writeDefaults: true);
176 }
177 }
178 if (d->actionTypes & ~KShortcutsEditor::GlobalAction) {
179 QString groupName(QStringLiteral("Shortcuts"));
180 KConfigGroup group(config, groupName);
181 for (KActionCollection *collection : std::as_const(t&: d->actionCollections)) {
182 collection->writeSettings(config: &group, writeDefaults: true);
183 }
184 }
185}
186
187void KShortcutsEditor::writeConfiguration(KConfigGroup *config) const
188{
189 for (KActionCollection *collection : std::as_const(t&: d->actionCollections)) {
190 collection->writeSettings(config);
191 }
192}
193
194// slot
195void KShortcutsEditor::resizeColumns()
196{
197 for (int i = 0; i < d->ui.list->columnCount(); i++) {
198 d->ui.list->resizeColumnToContents(column: i);
199 }
200}
201
202void KShortcutsEditor::save()
203{
204 writeConfiguration();
205 // we have to call commit. If we wouldn't do that the changes would be
206 // undone on deletion! That would lead to weird problems. Changes to
207 // Global Shortcuts would vanish completely. Changes to local shortcuts
208 // would vanish for this session.
209 for (QTreeWidgetItemIterator it(d->ui.list); (*it); ++it) {
210 if (KShortcutsEditorItem *item = dynamic_cast<KShortcutsEditorItem *>(*it)) {
211 item->commit();
212 }
213 }
214}
215
216void KShortcutsEditor::undo()
217{
218 // This function used to crash sometimes when invoked by clicking on "cancel"
219 // with Qt 4.2.something. Apparently items were deleted too early by Qt.
220 // It seems to work with 4.3-ish Qt versions. Keep an eye on this.
221 for (QTreeWidgetItemIterator it(d->ui.list); (*it); ++it) {
222 if (KShortcutsEditorItem *item = dynamic_cast<KShortcutsEditorItem *>(*it)) {
223 item->undo();
224 }
225 }
226}
227
228// We ask the user here if there are any conflicts, as opposed to undo().
229// They don't do the same thing anyway, this just not to confuse any readers.
230// slot
231void KShortcutsEditor::allDefault()
232{
233 d->allDefault();
234}
235
236void KShortcutsEditor::printShortcuts() const
237{
238 d->printShortcuts();
239}
240
241KShortcutsEditor::ActionTypes KShortcutsEditor::actionTypes() const
242{
243 return d->actionTypes;
244}
245
246void KShortcutsEditor::setActionTypes(ActionTypes actionTypes)
247{
248 d->setActionTypes(actionTypes);
249}
250
251//---------------------------------------------------------------------
252// KShortcutsEditorPrivate
253//---------------------------------------------------------------------
254
255KShortcutsEditorPrivate::KShortcutsEditorPrivate(KShortcutsEditor *qq)
256 : q(qq)
257 , delegate(nullptr)
258{
259}
260
261void KShortcutsEditorPrivate::initGUI(KShortcutsEditor::ActionTypes types, KShortcutsEditor::LetterShortcuts allowLetterShortcuts)
262{
263 actionTypes = types;
264
265 ui.setupUi(q);
266 q->layout()->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
267 ui.searchFilter->searchLine()->setTreeWidget(ui.list); // Plug into search line
268 ui.list->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
269 ui.list->header()->hideSection(alogicalIndex: ShapeGesture); // mouse gestures didn't make it in time...
270 ui.list->header()->hideSection(alogicalIndex: RockerGesture);
271
272#if HAVE_GLOBALACCEL
273 const bool hideGlobals = !(actionTypes & KShortcutsEditor::GlobalAction);
274#else
275 const bool hideGlobals = true;
276#endif
277
278 if (hideGlobals) {
279 setGlobalColumnsHidden(true);
280 } else if (!(actionTypes & ~KShortcutsEditor::GlobalAction)) {
281 setLocalColumnsHidden(true);
282 }
283
284 // Create the Delegate. It is responsible for the KKeySeqeunceWidgets that
285 // really change the shortcuts.
286 delegate = new KShortcutsEditorDelegate(ui.list, allowLetterShortcuts == KShortcutsEditor::LetterShortcutsAllowed);
287
288 ui.list->setItemDelegate(delegate);
289 ui.list->setSelectionBehavior(QAbstractItemView::SelectItems);
290 ui.list->setSelectionMode(QAbstractItemView::SingleSelection);
291 // we have our own editing mechanism
292 ui.list->setEditTriggers(QAbstractItemView::NoEditTriggers);
293 ui.list->setAlternatingRowColors(true);
294
295 // TODO listen to changes to global shortcuts
296 QObject::connect(sender: delegate, signal: &KShortcutsEditorDelegate::shortcutChanged, context: q, slot: [this](const QVariant &newShortcut, const QModelIndex &index) {
297 capturedShortcut(newShortcut, index);
298 });
299 // hide the editor widget chen its item becomes hidden
300 QObject::connect(sender: ui.searchFilter->searchLine(), signal: &KTreeWidgetSearchLine::hiddenChanged, context: delegate, slot: &KShortcutsEditorDelegate::hiddenBySearchLine);
301
302 ui.searchFilter->setFocus();
303}
304
305void KShortcutsEditorPrivate::setGlobalColumnsHidden(bool hide)
306{
307 QHeaderView *header = ui.list->header();
308 header->setSectionHidden(logicalIndex: GlobalPrimary, hide);
309 header->setSectionHidden(logicalIndex: GlobalAlternate, hide);
310}
311
312void KShortcutsEditorPrivate::setLocalColumnsHidden(bool hide)
313{
314 QHeaderView *header = ui.list->header();
315 header->setSectionHidden(logicalIndex: LocalPrimary, hide);
316 header->setSectionHidden(logicalIndex: LocalAlternate, hide);
317}
318
319void KShortcutsEditorPrivate::setActionTypes(KShortcutsEditor::ActionTypes types)
320{
321 if (actionTypes == types) {
322 return;
323 }
324 actionTypes = types;
325
326 // Show/hide columns based on the newly set action types
327 setGlobalColumnsHidden(!(actionTypes & KShortcutsEditor::GlobalAction));
328 setLocalColumnsHidden(!(actionTypes & ~KShortcutsEditor::GlobalAction));
329}
330
331bool KShortcutsEditorPrivate::addAction(QAction *action, QTreeWidgetItem *parent)
332{
333 // If the action name starts with unnamed- spit out a warning and ignore
334 // it. That name will change at will and will break loading and writing
335 QString actionName = action->objectName();
336 if (actionName.isEmpty() || actionName.startsWith(s: QLatin1String("unnamed-"))) {
337 qCCritical(DEBUG_KXMLGUI) << "Skipping action without name " << action->text() << "," << actionName << "!";
338 return false;
339 }
340
341 const QVariant value = action->property(name: "isShortcutConfigurable");
342 if (!value.isValid() || value.toBool()) {
343 new KShortcutsEditorItem(parent, action);
344
345#if HAVE_GLOBALACCEL
346 if (!m_hasAnyGlobalShortcuts) { // If one global action was found, skip
347 m_hasAnyGlobalShortcuts = KGlobalAccel::self()->hasShortcut(action);
348 }
349
350 if (!m_hasAnyLocalShortcuts) { // If one local action was found, skip
351 m_hasAnyLocalShortcuts = !KGlobalAccel::self()->hasShortcut(action);
352 }
353#else
354 m_hasAnyLocalShortcuts = true;
355#endif
356
357 return true;
358 }
359
360 return false;
361}
362
363void KShortcutsEditorPrivate::allDefault()
364{
365 for (QTreeWidgetItemIterator it(ui.list); (*it); ++it) {
366 if (!(*it)->parent() || (*it)->type() != ActionItem) {
367 continue;
368 }
369
370 KShortcutsEditorItem *item = static_cast<KShortcutsEditorItem *>(*it);
371 QAction *act = item->m_action;
372
373 QList<QKeySequence> defaultShortcuts = act->property(name: "defaultShortcuts").value<QList<QKeySequence>>();
374 if (act->shortcuts() != defaultShortcuts) {
375 QKeySequence primary = defaultShortcuts.isEmpty() ? QKeySequence() : defaultShortcuts.at(i: 0);
376 QKeySequence alternate = defaultShortcuts.size() <= 1 ? QKeySequence() : defaultShortcuts.at(i: 1);
377 changeKeyShortcut(item, column: LocalPrimary, capture: primary);
378 changeKeyShortcut(item, column: LocalAlternate, capture: alternate);
379 }
380
381#if HAVE_GLOBALACCEL
382 if (KGlobalAccel::self()->shortcut(action: act) != KGlobalAccel::self()->defaultShortcut(action: act)) {
383 QList<QKeySequence> defaultShortcut = KGlobalAccel::self()->defaultShortcut(action: act);
384 changeKeyShortcut(item, column: GlobalPrimary, capture: primarySequence(sequences: defaultShortcut));
385 changeKeyShortcut(item, column: GlobalAlternate, capture: alternateSequence(sequences: defaultShortcut));
386 }
387#endif
388 }
389}
390
391// static
392KShortcutsEditorItem *KShortcutsEditorPrivate::itemFromIndex(QTreeWidget *const w, const QModelIndex &index)
393{
394 QTreeWidgetItem *item = static_cast<QTreeWidgetHack *>(w)->itemFromIndex(index);
395 if (item && item->type() == ActionItem) {
396 return static_cast<KShortcutsEditorItem *>(item);
397 }
398 return nullptr;
399}
400
401QTreeWidgetItem *KShortcutsEditorPrivate::findOrMakeItem(QTreeWidgetItem *parent, const QString &name)
402{
403 for (int i = 0; i < parent->childCount(); i++) {
404 QTreeWidgetItem *child = parent->child(index: i);
405 if (child->text(column: 0) == name) {
406 return child;
407 }
408 }
409 QTreeWidgetItem *ret = new QTreeWidgetItem(parent, NonActionItem);
410 ret->setText(column: 0, atext: name);
411 ui.list->expandItem(item: ret);
412 ret->setFlags(ret->flags() & ~Qt::ItemIsSelectable);
413 return ret;
414}
415
416// private slot
417void KShortcutsEditorPrivate::capturedShortcut(const QVariant &newShortcut, const QModelIndex &index)
418{
419 // dispatch to the right handler
420 if (!index.isValid()) {
421 return;
422 }
423 int column = index.column();
424 KShortcutsEditorItem *item = itemFromIndex(w: ui.list, index);
425 Q_ASSERT(item);
426
427 if (column >= LocalPrimary && column <= GlobalAlternate) {
428 changeKeyShortcut(item, column, capture: newShortcut.value<QKeySequence>());
429 }
430}
431
432void KShortcutsEditorPrivate::changeKeyShortcut(KShortcutsEditorItem *item, uint column, const QKeySequence &capture)
433{
434 // The keySequence we get is cleared by KKeySequenceWidget. No conflicts.
435 if (capture == item->keySequence(column)) {
436 return;
437 }
438
439 item->setKeySequence(column, seq: capture);
440 Q_EMIT q->keyChange();
441 // force view update
442 item->setText(column, atext: capture.toString(format: QKeySequence::NativeText));
443}
444
445void KShortcutsEditorPrivate::importConfiguration(KConfigBase *config)
446{
447 Q_ASSERT(config);
448 if (!config) {
449 return;
450 }
451
452 KConfigGroup globalShortcutsGroup(config, QStringLiteral("Global Shortcuts"));
453 if ((actionTypes & KShortcutsEditor::GlobalAction) && globalShortcutsGroup.exists()) {
454 for (QTreeWidgetItemIterator it(ui.list); (*it); ++it) {
455 if (!(*it)->parent()) {
456 continue;
457 }
458
459 KShortcutsEditorItem *item = static_cast<KShortcutsEditorItem *>(*it);
460 const QString actionId = item->data(column: Id).toString();
461 if (!globalShortcutsGroup.hasKey(key: actionId)) {
462 continue;
463 }
464
465 QList<QKeySequence> sc = QKeySequence::listFromString(str: globalShortcutsGroup.readEntry(key: actionId, aDefault: QString()));
466 changeKeyShortcut(item, column: GlobalPrimary, capture: primarySequence(sequences: sc));
467 changeKeyShortcut(item, column: GlobalAlternate, capture: alternateSequence(sequences: sc));
468 }
469 }
470
471 if (actionTypes & ~KShortcutsEditor::GlobalAction) {
472 const KConfigGroup localShortcutsGroup(config, QStringLiteral("Shortcuts"));
473 for (QTreeWidgetItemIterator it(ui.list); (*it); ++it) {
474 if (!(*it)->parent()) {
475 continue;
476 }
477 KShortcutsEditorItem *item = static_cast<KShortcutsEditorItem *>(*it);
478 const QString actionId = item->data(column: Id).toString();
479 if (!localShortcutsGroup.hasKey(key: actionId)) {
480 continue;
481 }
482
483 QList<QKeySequence> sc = QKeySequence::listFromString(str: localShortcutsGroup.readEntry(key: actionId, aDefault: QString()));
484 changeKeyShortcut(item, column: LocalPrimary, capture: primarySequence(sequences: sc));
485 changeKeyShortcut(item, column: LocalAlternate, capture: alternateSequence(sequences: sc));
486 }
487 }
488}
489
490/*TODO for the printShortcuts function
491Nice to have features (which I'm not sure I can do before may due to
492more important things):
493
494- adjust the general page borders, IMHO they're too wide
495
496- add a custom printer options page that allows to filter out all
497 actions that don't have a shortcut set to reduce this list. IMHO this
498 should be optional as people might want to simply print all and when
499 they find a new action that they assign a shortcut they can simply use
500 a pen to fill out the empty space
501
502- find a way to align the Main/Alternate/Global entries in the shortcuts
503 column without adding borders. I first did this without a nested table
504 but instead simply added 3 rows and merged the 3 cells in the Action
505 name and description column, but unfortunately I didn't find a way to
506 remove the borders between the 6 shortcut cells.
507*/
508void KShortcutsEditorPrivate::printShortcuts() const
509{
510// One can't print on wince
511#ifndef _WIN32_WCE
512 QTreeWidgetItem *root = ui.list->invisibleRootItem();
513 QTextDocument doc;
514
515 doc.setDefaultFont(QFontDatabase::systemFont(type: QFontDatabase::GeneralFont));
516
517 QTextCursor cursor(&doc);
518 cursor.beginEditBlock();
519 QTextCharFormat headerFormat;
520 headerFormat.setProperty(propertyId: QTextFormat::FontSizeAdjustment, value: 3);
521 headerFormat.setFontWeight(QFont::Bold);
522 cursor.insertText(i18nc("header for an applications shortcut list", "Shortcuts for %1", QGuiApplication::applicationDisplayName()), format: headerFormat);
523 QTextCharFormat componentFormat;
524 componentFormat.setProperty(propertyId: QTextFormat::FontSizeAdjustment, value: 2);
525 componentFormat.setFontWeight(QFont::Bold);
526 QTextBlockFormat componentBlockFormat = cursor.blockFormat();
527 componentBlockFormat.setTopMargin(16);
528 componentBlockFormat.setBottomMargin(16);
529
530 QTextTableFormat tableformat;
531 tableformat.setHeaderRowCount(1);
532 tableformat.setCellPadding(4.0);
533 tableformat.setCellSpacing(0);
534 tableformat.setBorderStyle(QTextFrameFormat::BorderStyle_Solid);
535 tableformat.setBorder(0.5);
536
537 struct ColumnInfo {
538 QString title;
539 ColumnDesignation column;
540 };
541 const ColumnInfo shortcutTitleToColumnMap[] = {
542 {i18n("Main:"), .column: LocalPrimary},
543 {i18n("Alternate:"), .column: LocalAlternate},
544 {i18n("Global:"), .column: GlobalPrimary},
545 {i18n("Global alternate:"), .column: GlobalAlternate},
546 };
547
548 for (int i = 0; i < root->childCount(); i++) {
549 QTreeWidgetItem *item = root->child(index: i);
550 cursor.insertBlock(format: componentBlockFormat, charFormat: componentFormat);
551 cursor.insertText(text: item->text(column: 0));
552
553 QTextTable *table = cursor.insertTable(rows: 1, cols: 3);
554 table->setFormat(tableformat);
555 int currow = 0;
556
557 QTextTableCell cell = table->cellAt(row: currow, col: 0);
558 QTextCharFormat format = cell.format();
559 format.setFontWeight(QFont::Bold);
560 cell.setFormat(format);
561 cell.firstCursorPosition().insertText(i18n("Action Name"));
562
563 cell = table->cellAt(row: currow, col: 1);
564 cell.setFormat(format);
565 cell.firstCursorPosition().insertText(i18n("Shortcuts"));
566
567 cell = table->cellAt(row: currow, col: 2);
568 cell.setFormat(format);
569 cell.firstCursorPosition().insertText(i18n("Description"));
570 currow++;
571
572 for (QTreeWidgetItemIterator it(item); *it; ++it) {
573 if ((*it)->type() != ActionItem) {
574 continue;
575 }
576
577 KShortcutsEditorItem *editoritem = static_cast<KShortcutsEditorItem *>(*it);
578 table->insertRows(pos: table->rows(), num: 1);
579 QVariant data = editoritem->data(column: Name, role: Qt::DisplayRole);
580 table->cellAt(row: currow, col: 0).firstCursorPosition().insertText(text: data.toString());
581
582 QTextTable *shortcutTable = nullptr;
583 for (const auto &[title, column] : shortcutTitleToColumnMap) {
584 data = editoritem->data(column, role: Qt::DisplayRole);
585 QString key = data.value<QKeySequence>().toString();
586
587 if (!key.isEmpty()) {
588 if (!shortcutTable) {
589 shortcutTable = table->cellAt(row: currow, col: 1).firstCursorPosition().insertTable(rows: 1, cols: 2);
590 QTextTableFormat shortcutTableFormat = tableformat;
591 shortcutTableFormat.setCellSpacing(0.0);
592 shortcutTableFormat.setHeaderRowCount(0);
593 shortcutTableFormat.setBorder(0.0);
594 shortcutTable->setFormat(shortcutTableFormat);
595 } else {
596 shortcutTable->insertRows(pos: shortcutTable->rows(), num: 1);
597 }
598 shortcutTable->cellAt(row: shortcutTable->rows() - 1, col: 0).firstCursorPosition().insertText(text: title);
599 shortcutTable->cellAt(row: shortcutTable->rows() - 1, col: 1).firstCursorPosition().insertText(text: key);
600 }
601 }
602
603 QAction *action = editoritem->m_action;
604 cell = table->cellAt(row: currow, col: 2);
605 format = cell.format();
606 format.setProperty(propertyId: QTextFormat::FontSizeAdjustment, value: -1);
607 cell.setFormat(format);
608 cell.firstCursorPosition().insertHtml(html: action->whatsThis());
609
610 currow++;
611 }
612 cursor.movePosition(op: QTextCursor::End);
613 }
614 cursor.endEditBlock();
615
616 QPrinter printer;
617 QPrintDialog *dlg = new QPrintDialog(&printer, q);
618 if (dlg->exec() == QDialog::Accepted) {
619 doc.print(printer: &printer);
620 }
621 delete dlg;
622#endif
623}
624
625#include "moc_kshortcutseditor.cpp"
626

source code of kxmlgui/src/kshortcutseditor.cpp