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 |
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 | |
48 | KShortcutsEditor::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 | |
56 | KShortcutsEditor::KShortcutsEditor(QWidget *parent, ActionTypes actionType, LetterShortcuts allowLetterShortcuts) |
57 | : QWidget(parent) |
58 | , d(new KShortcutsEditorPrivate(this)) |
59 | { |
60 | d->initGUI(actionTypes: actionType, allowLetterShortcuts); |
61 | } |
62 | |
63 | KShortcutsEditor::~KShortcutsEditor() |
64 | { |
65 | // reset all pending changes |
66 | undo(); |
67 | } |
68 | |
69 | bool 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 | |
83 | void 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 | |
91 | void 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 | |
159 | void KShortcutsEditor::importConfiguration(KConfigBase *config) |
160 | { |
161 | d->importConfiguration(config); |
162 | } |
163 | |
164 | void 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 | |
187 | void 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 |
195 | void KShortcutsEditor::resizeColumns() |
196 | { |
197 | // skip Name column as its section resize mode will take care of resizing to contents |
198 | for (int i = Name + 1; i < d->ui.list->columnCount(); i++) { |
199 | if (d->ui.list->isColumnHidden(column: i)) { |
200 | continue; |
201 | } |
202 | d->ui.list->resizeColumnToContents(column: i); |
203 | } |
204 | } |
205 | |
206 | void KShortcutsEditor::save() |
207 | { |
208 | writeConfiguration(); |
209 | // we have to call commit. If we wouldn't do that the changes would be |
210 | // undone on deletion! That would lead to weird problems. Changes to |
211 | // Global Shortcuts would vanish completely. Changes to local shortcuts |
212 | // would vanish for this session. |
213 | for (QTreeWidgetItemIterator it(d->ui.list); (*it); ++it) { |
214 | if (KShortcutsEditorItem *item = dynamic_cast<KShortcutsEditorItem *>(*it)) { |
215 | item->commit(); |
216 | } |
217 | } |
218 | } |
219 | |
220 | void KShortcutsEditor::undo() |
221 | { |
222 | // This function used to crash sometimes when invoked by clicking on "cancel" |
223 | // with Qt 4.2.something. Apparently items were deleted too early by Qt. |
224 | // It seems to work with 4.3-ish Qt versions. Keep an eye on this. |
225 | for (QTreeWidgetItemIterator it(d->ui.list); (*it); ++it) { |
226 | if (KShortcutsEditorItem *item = dynamic_cast<KShortcutsEditorItem *>(*it)) { |
227 | item->undo(); |
228 | } |
229 | } |
230 | } |
231 | |
232 | // We ask the user here if there are any conflicts, as opposed to undo(). |
233 | // They don't do the same thing anyway, this just not to confuse any readers. |
234 | // slot |
235 | void KShortcutsEditor::allDefault() |
236 | { |
237 | d->allDefault(); |
238 | } |
239 | |
240 | void KShortcutsEditor::printShortcuts() const |
241 | { |
242 | d->printShortcuts(); |
243 | } |
244 | |
245 | KShortcutsEditor::ActionTypes KShortcutsEditor::actionTypes() const |
246 | { |
247 | return d->actionTypes; |
248 | } |
249 | |
250 | void KShortcutsEditor::setActionTypes(ActionTypes actionTypes) |
251 | { |
252 | d->setActionTypes(actionTypes); |
253 | } |
254 | |
255 | //--------------------------------------------------------------------- |
256 | // KShortcutsEditorPrivate |
257 | //--------------------------------------------------------------------- |
258 | |
259 | KShortcutsEditorPrivate::KShortcutsEditorPrivate(KShortcutsEditor *qq) |
260 | : q(qq) |
261 | , delegate(nullptr) |
262 | { |
263 | } |
264 | |
265 | void KShortcutsEditorPrivate::initGUI(KShortcutsEditor::ActionTypes types, KShortcutsEditor::LetterShortcuts allowLetterShortcuts) |
266 | { |
267 | actionTypes = types; |
268 | |
269 | ui.setupUi(q); |
270 | q->layout()->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0); |
271 | ui.searchFilter->searchLine()->setTreeWidget(ui.list); // Plug into search line |
272 | ui.list->header()->setSectionResizeMode(QHeaderView::ResizeToContents); |
273 | ui.list->header()->hideSection(alogicalIndex: ShapeGesture); // mouse gestures didn't make it in time... |
274 | ui.list->header()->hideSection(alogicalIndex: RockerGesture); |
275 | |
276 | #if HAVE_GLOBALACCEL |
277 | const bool hideGlobals = !(actionTypes & KShortcutsEditor::GlobalAction); |
278 | #else |
279 | const bool hideGlobals = true; |
280 | #endif |
281 | |
282 | if (hideGlobals) { |
283 | setGlobalColumnsHidden(true); |
284 | } else if (!(actionTypes & ~KShortcutsEditor::GlobalAction)) { |
285 | setLocalColumnsHidden(true); |
286 | } |
287 | |
288 | // Create the Delegate. It is responsible for the KKeySeqeunceWidgets that |
289 | // really change the shortcuts. |
290 | delegate = new KShortcutsEditorDelegate(ui.list, allowLetterShortcuts == KShortcutsEditor::LetterShortcutsAllowed); |
291 | |
292 | ui.list->setItemDelegate(delegate); |
293 | ui.list->setSelectionBehavior(QAbstractItemView::SelectItems); |
294 | ui.list->setSelectionMode(QAbstractItemView::SingleSelection); |
295 | // we have our own editing mechanism |
296 | ui.list->setEditTriggers(QAbstractItemView::NoEditTriggers); |
297 | ui.list->setAlternatingRowColors(true); |
298 | |
299 | // TODO listen to changes to global shortcuts |
300 | QObject::connect(sender: delegate, signal: &KShortcutsEditorDelegate::shortcutChanged, context: q, slot: [this](const QVariant &newShortcut, const QModelIndex &index) { |
301 | capturedShortcut(newShortcut, index); |
302 | }); |
303 | // hide the editor widget chen its item becomes hidden |
304 | QObject::connect(sender: ui.searchFilter->searchLine(), signal: &KTreeWidgetSearchLine::hiddenChanged, context: delegate, slot: &KShortcutsEditorDelegate::hiddenBySearchLine); |
305 | |
306 | ui.searchFilter->setFocus(); |
307 | } |
308 | |
309 | void KShortcutsEditorPrivate::setGlobalColumnsHidden(bool hide) |
310 | { |
311 | QHeaderView * = ui.list->header(); |
312 | header->setSectionHidden(logicalIndex: GlobalPrimary, hide); |
313 | header->setSectionHidden(logicalIndex: GlobalAlternate, hide); |
314 | } |
315 | |
316 | void KShortcutsEditorPrivate::setLocalColumnsHidden(bool hide) |
317 | { |
318 | QHeaderView * = ui.list->header(); |
319 | header->setSectionHidden(logicalIndex: LocalPrimary, hide); |
320 | header->setSectionHidden(logicalIndex: LocalAlternate, hide); |
321 | } |
322 | |
323 | void KShortcutsEditorPrivate::setActionTypes(KShortcutsEditor::ActionTypes types) |
324 | { |
325 | if (actionTypes == types) { |
326 | return; |
327 | } |
328 | actionTypes = types; |
329 | |
330 | // Show/hide columns based on the newly set action types |
331 | setGlobalColumnsHidden(!(actionTypes & KShortcutsEditor::GlobalAction)); |
332 | setLocalColumnsHidden(!(actionTypes & ~KShortcutsEditor::GlobalAction)); |
333 | } |
334 | |
335 | bool KShortcutsEditorPrivate::addAction(QAction *action, QTreeWidgetItem *parent) |
336 | { |
337 | // If the action name starts with unnamed- spit out a warning and ignore |
338 | // it. That name will change at will and will break loading and writing |
339 | QString actionName = action->objectName(); |
340 | if (actionName.isEmpty() || actionName.startsWith(s: QLatin1String("unnamed-" ))) { |
341 | qCCritical(DEBUG_KXMLGUI) << "Skipping action without name " << action->text() << "," << actionName << "!" ; |
342 | return false; |
343 | } |
344 | |
345 | const QVariant value = action->property(name: "isShortcutConfigurable" ); |
346 | if (!value.isValid() || value.toBool()) { |
347 | new KShortcutsEditorItem(parent, action); |
348 | |
349 | #if HAVE_GLOBALACCEL |
350 | if (!m_hasAnyGlobalShortcuts) { // If one global action was found, skip |
351 | m_hasAnyGlobalShortcuts = KGlobalAccel::self()->hasShortcut(action); |
352 | } |
353 | |
354 | if (!m_hasAnyLocalShortcuts) { // If one local action was found, skip |
355 | m_hasAnyLocalShortcuts = !KGlobalAccel::self()->hasShortcut(action); |
356 | } |
357 | #else |
358 | m_hasAnyLocalShortcuts = true; |
359 | #endif |
360 | |
361 | return true; |
362 | } |
363 | |
364 | return false; |
365 | } |
366 | |
367 | void KShortcutsEditorPrivate::allDefault() |
368 | { |
369 | for (QTreeWidgetItemIterator it(ui.list); (*it); ++it) { |
370 | if (!(*it)->parent() || (*it)->type() != ActionItem) { |
371 | continue; |
372 | } |
373 | |
374 | KShortcutsEditorItem *item = static_cast<KShortcutsEditorItem *>(*it); |
375 | QAction *act = item->m_action; |
376 | |
377 | QList<QKeySequence> defaultShortcuts = act->property(name: "defaultShortcuts" ).value<QList<QKeySequence>>(); |
378 | if (act->shortcuts() != defaultShortcuts) { |
379 | QKeySequence primary = defaultShortcuts.isEmpty() ? QKeySequence() : defaultShortcuts.at(i: 0); |
380 | QKeySequence alternate = defaultShortcuts.size() <= 1 ? QKeySequence() : defaultShortcuts.at(i: 1); |
381 | changeKeyShortcut(item, column: LocalPrimary, capture: primary); |
382 | changeKeyShortcut(item, column: LocalAlternate, capture: alternate); |
383 | } |
384 | |
385 | #if HAVE_GLOBALACCEL |
386 | if (KGlobalAccel::self()->shortcut(action: act) != KGlobalAccel::self()->defaultShortcut(action: act)) { |
387 | QList<QKeySequence> defaultShortcut = KGlobalAccel::self()->defaultShortcut(action: act); |
388 | changeKeyShortcut(item, column: GlobalPrimary, capture: primarySequence(sequences: defaultShortcut)); |
389 | changeKeyShortcut(item, column: GlobalAlternate, capture: alternateSequence(sequences: defaultShortcut)); |
390 | } |
391 | #endif |
392 | } |
393 | } |
394 | |
395 | // static |
396 | KShortcutsEditorItem *KShortcutsEditorPrivate::itemFromIndex(QTreeWidget *const w, const QModelIndex &index) |
397 | { |
398 | QTreeWidgetItem *item = w->itemFromIndex(index); |
399 | if (item && item->type() == ActionItem) { |
400 | return static_cast<KShortcutsEditorItem *>(item); |
401 | } |
402 | return nullptr; |
403 | } |
404 | |
405 | QTreeWidgetItem *KShortcutsEditorPrivate::findOrMakeItem(QTreeWidgetItem *parent, const QString &name) |
406 | { |
407 | for (int i = 0; i < parent->childCount(); i++) { |
408 | QTreeWidgetItem *child = parent->child(index: i); |
409 | if (child->text(column: 0) == name) { |
410 | return child; |
411 | } |
412 | } |
413 | QTreeWidgetItem *ret = new QTreeWidgetItem(parent, NonActionItem); |
414 | ret->setText(column: 0, atext: name); |
415 | ui.list->expandItem(item: ret); |
416 | ret->setFlags(ret->flags() & ~Qt::ItemIsSelectable); |
417 | return ret; |
418 | } |
419 | |
420 | // private slot |
421 | void KShortcutsEditorPrivate::capturedShortcut(const QVariant &newShortcut, const QModelIndex &index) |
422 | { |
423 | // dispatch to the right handler |
424 | if (!index.isValid()) { |
425 | return; |
426 | } |
427 | int column = index.column(); |
428 | KShortcutsEditorItem *item = itemFromIndex(w: ui.list, index); |
429 | Q_ASSERT(item); |
430 | |
431 | if (column >= LocalPrimary && column <= GlobalAlternate) { |
432 | changeKeyShortcut(item, column, capture: newShortcut.value<QKeySequence>()); |
433 | } |
434 | } |
435 | |
436 | void KShortcutsEditorPrivate::changeKeyShortcut(KShortcutsEditorItem *item, uint column, const QKeySequence &capture) |
437 | { |
438 | // The keySequence we get is cleared by KKeySequenceWidget. No conflicts. |
439 | if (capture == item->keySequence(column)) { |
440 | return; |
441 | } |
442 | |
443 | item->setKeySequence(column, seq: capture); |
444 | Q_EMIT q->keyChange(); |
445 | // force view update |
446 | item->setText(column, atext: capture.toString(format: QKeySequence::NativeText)); |
447 | } |
448 | |
449 | void KShortcutsEditorPrivate::importConfiguration(KConfigBase *config) |
450 | { |
451 | Q_ASSERT(config); |
452 | if (!config) { |
453 | return; |
454 | } |
455 | |
456 | KConfigGroup globalShortcutsGroup(config, QStringLiteral("Global Shortcuts" )); |
457 | if ((actionTypes & KShortcutsEditor::GlobalAction) && globalShortcutsGroup.exists()) { |
458 | for (QTreeWidgetItemIterator it(ui.list); (*it); ++it) { |
459 | if (!(*it)->parent()) { |
460 | continue; |
461 | } |
462 | |
463 | KShortcutsEditorItem *item = static_cast<KShortcutsEditorItem *>(*it); |
464 | const QString actionId = item->data(column: Id).toString(); |
465 | if (!globalShortcutsGroup.hasKey(key: actionId)) { |
466 | continue; |
467 | } |
468 | |
469 | QList<QKeySequence> sc = QKeySequence::listFromString(str: globalShortcutsGroup.readEntry(key: actionId, aDefault: QString())); |
470 | changeKeyShortcut(item, column: GlobalPrimary, capture: primarySequence(sequences: sc)); |
471 | changeKeyShortcut(item, column: GlobalAlternate, capture: alternateSequence(sequences: sc)); |
472 | } |
473 | } |
474 | |
475 | if (actionTypes & ~KShortcutsEditor::GlobalAction) { |
476 | const KConfigGroup localShortcutsGroup(config, QStringLiteral("Shortcuts" )); |
477 | for (QTreeWidgetItemIterator it(ui.list); (*it); ++it) { |
478 | if (!(*it)->parent()) { |
479 | continue; |
480 | } |
481 | KShortcutsEditorItem *item = static_cast<KShortcutsEditorItem *>(*it); |
482 | const QString actionId = item->data(column: Id).toString(); |
483 | if (!localShortcutsGroup.hasKey(key: actionId)) { |
484 | continue; |
485 | } |
486 | |
487 | QList<QKeySequence> sc = QKeySequence::listFromString(str: localShortcutsGroup.readEntry(key: actionId, aDefault: QString())); |
488 | changeKeyShortcut(item, column: LocalPrimary, capture: primarySequence(sequences: sc)); |
489 | changeKeyShortcut(item, column: LocalAlternate, capture: alternateSequence(sequences: sc)); |
490 | } |
491 | } |
492 | } |
493 | |
494 | /*TODO for the printShortcuts function |
495 | Nice to have features (which I'm not sure I can do before may due to |
496 | more important things): |
497 | |
498 | - adjust the general page borders, IMHO they're too wide |
499 | |
500 | - add a custom printer options page that allows to filter out all |
501 | actions that don't have a shortcut set to reduce this list. IMHO this |
502 | should be optional as people might want to simply print all and when |
503 | they find a new action that they assign a shortcut they can simply use |
504 | a pen to fill out the empty space |
505 | |
506 | - find a way to align the Main/Alternate/Global entries in the shortcuts |
507 | column without adding borders. I first did this without a nested table |
508 | but instead simply added 3 rows and merged the 3 cells in the Action |
509 | name and description column, but unfortunately I didn't find a way to |
510 | remove the borders between the 6 shortcut cells. |
511 | */ |
512 | void KShortcutsEditorPrivate::printShortcuts() const |
513 | { |
514 | // One can't print on wince |
515 | #ifndef _WIN32_WCE |
516 | QTreeWidgetItem *root = ui.list->invisibleRootItem(); |
517 | QTextDocument doc; |
518 | |
519 | doc.setDefaultFont(QFontDatabase::systemFont(type: QFontDatabase::GeneralFont)); |
520 | |
521 | QTextCursor cursor(&doc); |
522 | cursor.beginEditBlock(); |
523 | QTextCharFormat ; |
524 | headerFormat.setProperty(propertyId: QTextFormat::FontSizeAdjustment, value: 3); |
525 | headerFormat.setFontWeight(QFont::Bold); |
526 | cursor.insertText(i18nc("header for an applications shortcut list" , "Shortcuts for %1" , QGuiApplication::applicationDisplayName()), format: headerFormat); |
527 | QTextCharFormat componentFormat; |
528 | componentFormat.setProperty(propertyId: QTextFormat::FontSizeAdjustment, value: 2); |
529 | componentFormat.setFontWeight(QFont::Bold); |
530 | QTextBlockFormat componentBlockFormat = cursor.blockFormat(); |
531 | componentBlockFormat.setTopMargin(16); |
532 | componentBlockFormat.setBottomMargin(16); |
533 | |
534 | QTextTableFormat tableformat; |
535 | tableformat.setHeaderRowCount(1); |
536 | tableformat.setCellPadding(4.0); |
537 | tableformat.setCellSpacing(0); |
538 | tableformat.setBorderStyle(QTextFrameFormat::BorderStyle_Solid); |
539 | tableformat.setBorder(0.5); |
540 | |
541 | struct ColumnInfo { |
542 | QString title; |
543 | ColumnDesignation column; |
544 | }; |
545 | const ColumnInfo shortcutTitleToColumnMap[] = { |
546 | {i18n("Main:" ), .column: LocalPrimary}, |
547 | {i18n("Alternate:" ), .column: LocalAlternate}, |
548 | {i18n("Global:" ), .column: GlobalPrimary}, |
549 | {i18n("Global alternate:" ), .column: GlobalAlternate}, |
550 | }; |
551 | |
552 | for (int i = 0; i < root->childCount(); i++) { |
553 | QTreeWidgetItem *item = root->child(index: i); |
554 | cursor.insertBlock(format: componentBlockFormat, charFormat: componentFormat); |
555 | cursor.insertText(text: item->text(column: 0)); |
556 | |
557 | QTextTable *table = cursor.insertTable(rows: 1, cols: 3); |
558 | table->setFormat(tableformat); |
559 | int currow = 0; |
560 | |
561 | QTextTableCell cell = table->cellAt(row: currow, col: 0); |
562 | QTextCharFormat format = cell.format(); |
563 | format.setFontWeight(QFont::Bold); |
564 | cell.setFormat(format); |
565 | cell.firstCursorPosition().insertText(i18n("Action Name" )); |
566 | |
567 | cell = table->cellAt(row: currow, col: 1); |
568 | cell.setFormat(format); |
569 | cell.firstCursorPosition().insertText(i18n("Shortcuts" )); |
570 | |
571 | cell = table->cellAt(row: currow, col: 2); |
572 | cell.setFormat(format); |
573 | cell.firstCursorPosition().insertText(i18n("Description" )); |
574 | currow++; |
575 | |
576 | for (QTreeWidgetItemIterator it(item); *it; ++it) { |
577 | if ((*it)->type() != ActionItem) { |
578 | continue; |
579 | } |
580 | |
581 | KShortcutsEditorItem *editoritem = static_cast<KShortcutsEditorItem *>(*it); |
582 | table->insertRows(pos: table->rows(), num: 1); |
583 | QVariant data = editoritem->data(column: Name, role: Qt::DisplayRole); |
584 | table->cellAt(row: currow, col: 0).firstCursorPosition().insertText(text: data.toString()); |
585 | |
586 | QTextTable *shortcutTable = nullptr; |
587 | for (const auto &[title, column] : shortcutTitleToColumnMap) { |
588 | data = editoritem->data(column, role: Qt::DisplayRole); |
589 | QString key = data.value<QKeySequence>().toString(); |
590 | |
591 | if (!key.isEmpty()) { |
592 | if (!shortcutTable) { |
593 | shortcutTable = table->cellAt(row: currow, col: 1).firstCursorPosition().insertTable(rows: 1, cols: 2); |
594 | QTextTableFormat shortcutTableFormat = tableformat; |
595 | shortcutTableFormat.setCellSpacing(0.0); |
596 | shortcutTableFormat.setHeaderRowCount(0); |
597 | shortcutTableFormat.setBorder(0.0); |
598 | shortcutTable->setFormat(shortcutTableFormat); |
599 | } else { |
600 | shortcutTable->insertRows(pos: shortcutTable->rows(), num: 1); |
601 | } |
602 | shortcutTable->cellAt(row: shortcutTable->rows() - 1, col: 0).firstCursorPosition().insertText(text: title); |
603 | shortcutTable->cellAt(row: shortcutTable->rows() - 1, col: 1).firstCursorPosition().insertText(text: key); |
604 | } |
605 | } |
606 | |
607 | QAction *action = editoritem->m_action; |
608 | cell = table->cellAt(row: currow, col: 2); |
609 | format = cell.format(); |
610 | format.setProperty(propertyId: QTextFormat::FontSizeAdjustment, value: -1); |
611 | cell.setFormat(format); |
612 | cell.firstCursorPosition().insertHtml(html: action->whatsThis()); |
613 | |
614 | currow++; |
615 | } |
616 | cursor.movePosition(op: QTextCursor::End); |
617 | } |
618 | cursor.endEditBlock(); |
619 | |
620 | QPrinter printer; |
621 | QPrintDialog *dlg = new QPrintDialog(&printer, q); |
622 | if (dlg->exec() == QDialog::Accepted) { |
623 | doc.print(printer: &printer); |
624 | } |
625 | delete dlg; |
626 | #endif |
627 | } |
628 | |
629 | #include "moc_kshortcutseditor.cpp" |
630 | |