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 | |
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 | for (int i = 0; i < d->ui.list->columnCount(); i++) { |
198 | d->ui.list->resizeColumnToContents(column: i); |
199 | } |
200 | } |
201 | |
202 | void 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 | |
216 | void 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 |
231 | void KShortcutsEditor::allDefault() |
232 | { |
233 | d->allDefault(); |
234 | } |
235 | |
236 | void KShortcutsEditor::printShortcuts() const |
237 | { |
238 | d->printShortcuts(); |
239 | } |
240 | |
241 | KShortcutsEditor::ActionTypes KShortcutsEditor::actionTypes() const |
242 | { |
243 | return d->actionTypes; |
244 | } |
245 | |
246 | void KShortcutsEditor::setActionTypes(ActionTypes actionTypes) |
247 | { |
248 | d->setActionTypes(actionTypes); |
249 | } |
250 | |
251 | //--------------------------------------------------------------------- |
252 | // KShortcutsEditorPrivate |
253 | //--------------------------------------------------------------------- |
254 | |
255 | KShortcutsEditorPrivate::KShortcutsEditorPrivate(KShortcutsEditor *qq) |
256 | : q(qq) |
257 | , delegate(nullptr) |
258 | { |
259 | } |
260 | |
261 | void 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 | |
305 | void KShortcutsEditorPrivate::setGlobalColumnsHidden(bool hide) |
306 | { |
307 | QHeaderView * = ui.list->header(); |
308 | header->setSectionHidden(logicalIndex: GlobalPrimary, hide); |
309 | header->setSectionHidden(logicalIndex: GlobalAlternate, hide); |
310 | } |
311 | |
312 | void KShortcutsEditorPrivate::setLocalColumnsHidden(bool hide) |
313 | { |
314 | QHeaderView * = ui.list->header(); |
315 | header->setSectionHidden(logicalIndex: LocalPrimary, hide); |
316 | header->setSectionHidden(logicalIndex: LocalAlternate, hide); |
317 | } |
318 | |
319 | void 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 | |
331 | bool 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 | |
363 | void 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 |
392 | KShortcutsEditorItem *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 | |
401 | QTreeWidgetItem *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 |
417 | void 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 | |
432 | void 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 | |
445 | void 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 |
491 | Nice to have features (which I'm not sure I can do before may due to |
492 | more 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 | */ |
508 | void 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 ; |
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 | |