| 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 | |