00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026 #include "kshortcutseditor.h"
00027
00028
00029 #include "kshortcutsdialog_p.h"
00030
00031 #include <QHeaderView>
00032 #include <QTimer>
00033 #include <QTextDocument>
00034 #include <QTextTable>
00035 #include <QTextCursor>
00036 #include <QTextTableFormat>
00037 #include <QPrinter>
00038 #include <QPrintDialog>
00039
00040 #include "kaction.h"
00041 #include "kactioncollection.h"
00042 #include "kdebug.h"
00043 #include "kglobalaccel.h"
00044 #include "kmessagebox.h"
00045 #include "kaboutdata.h"
00046
00047
00048
00049
00050
00051 KShortcutsEditor::KShortcutsEditor(KActionCollection *collection, QWidget *parent, ActionTypes actionType,
00052 LetterShortcuts allowLetterShortcuts )
00053 : QWidget( parent )
00054 , d(new KShortcutsEditorPrivate(this))
00055 {
00056 d->initGUI(actionType, allowLetterShortcuts);
00057 addCollection(collection);
00058 }
00059
00060
00061 KShortcutsEditor::KShortcutsEditor(QWidget *parent, ActionTypes actionType, LetterShortcuts allowLetterShortcuts)
00062 : QWidget(parent)
00063 , d(new KShortcutsEditorPrivate(this))
00064 {
00065 d->initGUI(actionType, allowLetterShortcuts);
00066 }
00067
00068
00069 KShortcutsEditor::~KShortcutsEditor()
00070 {
00071 delete d;
00072 }
00073
00074
00075 bool KShortcutsEditor::isModified() const
00076 {
00077 for (QTreeWidgetItemIterator it(d->ui.list); (*it); ++it) {
00078 if ((*it)->childCount())
00079 continue;
00080
00081 if (static_cast<KShortcutsEditorItem *>(*it)->isModified()) {
00082 return true;
00083 }
00084 }
00085 return false;
00086 }
00087
00088 void KShortcutsEditor::clearCollections()
00089 {
00090 d->delegate->contractAll();
00091 d->ui.list->clear();
00092 d->actionCollections.clear();
00093 QTimer::singleShot(0, this, SLOT(resizeColumns()));
00094 }
00095
00096 void KShortcutsEditor::addCollection(KActionCollection *collection, const QString &title)
00097 {
00098 d->actionCollections.append(collection);
00099 enum hierarchyLevel {Root = 0, Program, Group, Action};
00100 KAction *kact;
00101 QTreeWidgetItem *hier[3];
00102 uint l = Program;
00103 hier[Root] = d->ui.list->invisibleRootItem();
00104 hier[Program] = d->findOrMakeItem(hier[Root], title.isEmpty() ? i18n("Shortcuts") : title);
00105 hier[Group] = d->findOrMakeItem(hier[Program], "if you see this, something went wrong");
00106
00107 foreach (QAction *action, collection->actions()) {
00108 QString name = action->text().remove('&');
00109
00110 if (name.startsWith(QLatin1String("Program:")))
00111 l = Program;
00112 else if (name.startsWith(QLatin1String("Group:")))
00113 l = Group;
00114 else if (qobject_cast<QAction *>(action)) {
00115
00116 if ((kact = qobject_cast<KAction *>(action)) && kact->isShortcutConfigurable()) {
00117
00118 if (!kact->isShortcutConfigurable()) {
00119 continue;
00120 }
00121
00122 new KShortcutsEditorItem((hier[l]), kact);
00123 }
00124 continue;
00125 }
00126 if (!hier[l]->childCount())
00127 delete hier[l];
00128
00129 hier[l] = d->findOrMakeItem(hier[l - 1], name);
00130 }
00131
00132 for (l = Group; l >= Program; l--) {
00133 if (!hier[l]->childCount())
00134 delete hier[l];
00135 }
00136
00137 d->ui.list->sortItems(0, Qt::AscendingOrder);
00138 QTimer::singleShot(0, this, SLOT(resizeColumns()));
00139 }
00140
00141 void KShortcutsEditor::importConfiguration( KConfig *config)
00142 {
00143 if (d->actionTypes & KShortcutsEditor::GlobalAction) {
00144 QString groupName = "Global Shortcuts";
00145 KConfigGroup group( config, groupName );
00146 foreach (KActionCollection* collection, d->actionCollections) {
00147 collection->importGlobalShortcuts( &group );
00148 }
00149 }
00150 if (d->actionTypes & ~KShortcutsEditor::GlobalAction) {
00151 QString groupName = "Shortcuts";
00152 KConfigGroup group( config, groupName );
00153 foreach (KActionCollection* collection, d->actionCollections) {
00154 collection->readSettings( &group );
00155 }
00156 }
00157 }
00158
00159 void KShortcutsEditor::exportConfiguration( KConfig *config) const
00160 {
00161 if (d->actionTypes & KShortcutsEditor::GlobalAction) {
00162 QString groupName = "Global Shortcuts";
00163 KConfigGroup group( config, groupName );
00164 foreach (KActionCollection* collection, d->actionCollections) {
00165 collection->exportGlobalShortcuts( &group, true );
00166 }
00167 }
00168 if (d->actionTypes & ~KShortcutsEditor::GlobalAction) {
00169 QString groupName = "Shortcuts";
00170 KConfigGroup group( config, groupName );
00171 foreach (KActionCollection* collection, d->actionCollections) {
00172 collection->writeSettings( &group, true );
00173 }
00174 }
00175 }
00176
00177
00178 void KShortcutsEditor::writeConfiguration( KConfigGroup *config) const
00179 {
00180 foreach (KActionCollection* collection, d->actionCollections) {
00181 collection->writeSettings(config);
00182 }
00183 }
00184
00185
00186
00187 void KShortcutsEditor::resizeColumns()
00188 {
00189 for (int i = 0; i < d->ui.list->columnCount(); i++)
00190 d->ui.list->resizeColumnToContents(i);
00191 }
00192
00193
00194 void KShortcutsEditor::save()
00195 {
00196
00197
00198
00199 for (QTreeWidgetItemIterator it(d->ui.list); (*it); ++it) {
00200 if ((*it)->childCount())
00201 continue;
00202
00203 static_cast<KShortcutsEditorItem *>(*it)->commit();
00204 }
00205
00206 writeConfiguration();
00207 }
00208
00209
00210 void KShortcutsEditor::undoChanges()
00211 {
00212
00213
00214
00215 for (QTreeWidgetItemIterator it(d->ui.list); (*it); ++it) {
00216 if ((*it)->childCount())
00217 continue;
00218
00219 static_cast<KShortcutsEditorItem *>(*it)->undo();
00220 }
00221 }
00222
00223
00224
00225
00226
00227 void KShortcutsEditor::allDefault()
00228 {
00229 d->allDefault();
00230 }
00231
00232
00233 void KShortcutsEditor::printShortcuts() const
00234 {
00235 d->printShortcuts();
00236 }
00237
00238
00239
00240
00241
00242
00243 KShortcutsEditorPrivate::KShortcutsEditorPrivate( KShortcutsEditor *q )
00244 : q(q),
00245 delegate(0)
00246 {}
00247
00248 void KShortcutsEditorPrivate::initGUI( KShortcutsEditor::ActionTypes types, KShortcutsEditor::LetterShortcuts allowLetterShortcuts )
00249 {
00250
00251 actionTypes = types;
00252
00253 ui.setupUi(q);
00254 ui.searchFilter->searchLine()->setTreeWidget(ui.list);
00255 ui.list->header()->setResizeMode(QHeaderView::ResizeToContents);
00256 ui.list->header()->hideSection(GlobalAlternate);
00257 ui.list->header()->hideSection(ShapeGesture);
00258 ui.list->header()->hideSection(RockerGesture);
00259 if (!(actionTypes & KShortcutsEditor::GlobalAction)) {
00260 ui.list->header()->hideSection(GlobalPrimary);
00261 } else if (!(actionTypes & ~KShortcutsEditor::GlobalAction)) {
00262 ui.list->header()->hideSection(LocalPrimary);
00263 ui.list->header()->hideSection(LocalAlternate);
00264 }
00265
00266 delegate = new KShortcutsEditorDelegate(ui.list,
00267 allowLetterShortcuts == KShortcutsEditor::LetterShortcutsAllowed);
00268 ui.list->setItemDelegate(delegate);
00269 ui.list->setSelectionBehavior(QAbstractItemView::SelectItems);
00270 ui.list->setSelectionMode(QAbstractItemView::SingleSelection);
00271
00272 ui.list->setEditTriggers(QAbstractItemView::NoEditTriggers);
00273 ui.list->setAlternatingRowColors(true);
00274
00275
00276 QObject::connect(delegate, SIGNAL(shortcutChanged(QVariant, const QModelIndex &)),
00277 q, SLOT(capturedShortcut(QVariant, const QModelIndex &)));
00278
00279 QObject::connect(ui.searchFilter->searchLine(), SIGNAL(hiddenChanged(QTreeWidgetItem *, bool)),
00280 delegate, SLOT(hiddenBySearchLine(QTreeWidgetItem *, bool)));
00281
00282 ui.searchFilter->setFocus();
00283 }
00284
00285
00286
00287 class QTreeWidgetHack : public QTreeWidget
00288 {
00289 public:
00290 QTreeWidgetItem *itemFromIndex(const QModelIndex &index) const
00291 { return QTreeWidget::itemFromIndex(index); }
00292 };
00293
00294
00295 void KShortcutsEditorPrivate::allDefault()
00296 {
00297 for (QTreeWidgetItemIterator it(ui.list); (*it); ++it) {
00298 if (!(*it)->parent())
00299 continue;
00300
00301 KShortcutsEditorItem *item = static_cast<KShortcutsEditorItem *>(*it);
00302 KAction *act = item->m_action;
00303
00304 if (act->shortcut() != act->shortcut(KAction::DefaultShortcut)) {
00305 changeKeyShortcut(item, LocalPrimary, act->shortcut(KAction::DefaultShortcut).primary());
00306 changeKeyShortcut(item, LocalAlternate, act->shortcut(KAction::DefaultShortcut).alternate());
00307 }
00308
00309 if (act->globalShortcut() != act->globalShortcut(KAction::DefaultShortcut)) {
00310 changeKeyShortcut(item, GlobalPrimary, act->globalShortcut(KAction::DefaultShortcut).primary());
00311 changeKeyShortcut(item, GlobalAlternate, act->globalShortcut(KAction::DefaultShortcut).alternate());
00312 }
00313
00314 if (act->shapeGesture() != act->shapeGesture(KAction::DefaultShortcut))
00315 changeShapeGesture(item, act->shapeGesture(KAction::DefaultShortcut));
00316
00317 if (act->rockerGesture() != act->rockerGesture(KAction::DefaultShortcut))
00318 changeRockerGesture(item, act->rockerGesture(KAction::DefaultShortcut));
00319 }
00320 }
00321
00322
00323 KShortcutsEditorItem *KShortcutsEditorPrivate::itemFromIndex(QTreeWidget *const w,
00324 const QModelIndex &index)
00325 {
00326 QTreeWidgetItem *item = static_cast<QTreeWidgetHack *>(w)->itemFromIndex(index);
00327 if (item && item->type() == ActionItem) {
00328 return static_cast<KShortcutsEditorItem *>(item);
00329 }
00330 return 0;
00331 }
00332
00333
00334 QTreeWidgetItem *KShortcutsEditorPrivate::findOrMakeItem(QTreeWidgetItem *parent, const QString &name)
00335 {
00336 for (int i = 0; i < parent->childCount(); i++) {
00337 QTreeWidgetItem *child = parent->child(i);
00338 if (child->text(0) == name)
00339 return child;
00340 }
00341 QTreeWidgetItem *ret = new QTreeWidgetItem(parent, NonActionItem);
00342 ret->setText(0, name);
00343 ui.list->expandItem(ret);
00344 ret->setFlags(ret->flags() & ~Qt::ItemIsSelectable);
00345 return ret;
00346 }
00347
00348
00349
00350 void KShortcutsEditorPrivate::capturedShortcut(const QVariant &newShortcut, const QModelIndex &index)
00351 {
00352
00353 if (!index.isValid())
00354 return;
00355 int column = index.column();
00356 KShortcutsEditorItem *item = itemFromIndex(ui.list, index);
00357 Q_ASSERT(item);
00358
00359 if (column >= LocalPrimary && column <= GlobalAlternate)
00360 changeKeyShortcut(item, column, newShortcut.value<QKeySequence>());
00361 else if (column == ShapeGesture)
00362 changeShapeGesture(item, newShortcut.value<KShapeGesture>());
00363 else if (column == RockerGesture)
00364 changeRockerGesture(item, newShortcut.value<KRockerGesture>());
00365 }
00366
00367
00368 void KShortcutsEditorPrivate::changeKeyShortcut(KShortcutsEditorItem *item, uint column, const QKeySequence &capture)
00369 {
00370 if (capture == item->keySequence(column))
00371 return;
00372
00373 if (!capture.isEmpty()) {
00374 unsigned int i = 0;
00375
00376
00377 if (column == GlobalPrimary || column == GlobalAlternate) {
00378 KStandardShortcut::StandardShortcut ssc = KStandardShortcut::find(capture);
00379 if (ssc != KStandardShortcut::AccelNone && !stealStandardShortcut(ssc, capture)) {
00380 return;
00381 }
00382 }
00383
00384
00385 bool conflict = false;
00386 KShortcutsEditorItem *otherItem = 0;
00387 for (QTreeWidgetItemIterator it(ui.list); (*it) && !conflict; ++it) {
00388
00389
00390 if ((*it)->childCount() || (*it == item))
00391 continue;
00392
00393 otherItem = static_cast<KShortcutsEditorItem *>(*it);
00394
00395
00396
00397
00398 for (i = LocalPrimary; i <= LocalAlternate; i++) {
00399 if (capture == otherItem->keySequence(i)) {
00400 conflict = true;
00401 break;
00402 }
00403 }
00404 }
00405
00406 if (conflict && !stealShortcut(otherItem, i, capture))
00407 return;
00408
00409
00410 QStringList conflicting = KGlobalAccel::findActionNameSystemwide(capture);
00411 if (!conflicting.isEmpty()) {
00412 if (KGlobalAccel::promptStealShortcutSystemwide(0, conflicting, capture)) {
00413 KGlobalAccel::stealShortcutSystemwide(capture);
00414 } else {
00415 return;
00416 }
00417 }
00418 }
00419
00420 item->setKeySequence(column, capture);
00421 q->keyChange();
00422
00423 item->setText(column, capture.toString(QKeySequence::NativeText));
00424 }
00425
00426
00427 void KShortcutsEditorPrivate::changeShapeGesture(KShortcutsEditorItem *item, const KShapeGesture &capture)
00428 {
00429 if (capture == item->m_action->shapeGesture())
00430 return;
00431
00432 if (capture.isValid()) {
00433 bool conflict = false;
00434 KShortcutsEditorItem *otherItem;
00435
00436
00437 for (QTreeWidgetItemIterator it(ui.list); (*it); ++it) {
00438 if (!(*it)->parent() || (*it == item))
00439 continue;
00440
00441 otherItem = static_cast<KShortcutsEditorItem *>(*it);
00442
00443
00444 if (!otherItem->m_action->shapeGesture().isValid())
00445 continue;
00446
00447 if (capture == otherItem->m_action->shapeGesture()) {
00448 conflict = true;
00449 break;
00450 }
00451 }
00452
00453 if (conflict && !stealShapeGesture(otherItem, capture))
00454 return;
00455 }
00456
00457 item->setShapeGesture(capture);
00458 }
00459
00460
00461 void KShortcutsEditorPrivate::changeRockerGesture(KShortcutsEditorItem *item, const KRockerGesture &capture)
00462 {
00463 if (capture == item->m_action->rockerGesture())
00464 return;
00465
00466 if (capture.isValid()) {
00467 bool conflict = false;
00468 KShortcutsEditorItem *otherItem;
00469
00470 for (QTreeWidgetItemIterator it(ui.list); (*it); ++it) {
00471 if (!(*it)->parent() || (*it == item))
00472 continue;
00473
00474 otherItem = static_cast<KShortcutsEditorItem *>(*it);
00475
00476 if (capture == otherItem->m_action->rockerGesture()) {
00477 conflict = true;
00478 break;
00479 }
00480 }
00481
00482 if (conflict && !stealRockerGesture(otherItem, capture))
00483 return;
00484 }
00485
00486 item->setRockerGesture(capture);
00487 }
00488
00489
00490 bool KShortcutsEditorPrivate::stealShortcut(KShortcutsEditorItem *item, unsigned int column, const QKeySequence &seq)
00491 {
00492 QString title = i18n("Key Conflict");
00493 QString message = i18n("The '%1' key combination has already been allocated to the \"%2\" action.\n"
00494 "Do you want to reassign it from that action to the current one?",
00495 seq.toString(QKeySequence::NativeText), item->m_action->text().remove('&'));
00496
00497 if (KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18n("Reassign")))
00498 != KMessageBox::Continue)
00499 return false;
00500
00501 item->setKeySequence(column - LocalPrimary, QKeySequence());
00502 q->keyChange();
00503 return true;
00504 }
00505
00506
00507 bool KShortcutsEditorPrivate::stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq)
00508 {
00509 QString title = i18n("Conflict with Standard Application Shortcut");
00510 QString message = i18n("The '%1' key combination is also used for the standard action "
00511 "\"%2\" that some applications use.\n"
00512 "Do you really want to use it as a global shortcut as well?",
00513 seq.toString(QKeySequence::NativeText), KStandardShortcut::name(std));
00514
00515 if (KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18n("Reassign"))) != KMessageBox::Continue) {
00516 return false;
00517 }
00518 return true;
00519 }
00520
00521
00522 bool KShortcutsEditorPrivate::stealShapeGesture(KShortcutsEditorItem *item, const KShapeGesture &gst)
00523 {
00524 QString title = i18n("Key Conflict");
00525 QString message = i18n("The '%1' shape gesture has already been allocated to the \"%2\" action.\n"
00526 "Do you want to reassign it from that action to the current one?",
00527 gst.shapeName(), item->m_action->text());
00528
00529 if (KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18n("Reassign")))
00530 != KMessageBox::Continue)
00531 return false;
00532
00533 item->setShapeGesture(KShapeGesture());
00534 return true;
00535 }
00536
00537
00538 bool KShortcutsEditorPrivate::stealRockerGesture(KShortcutsEditorItem *item, const KRockerGesture &gst)
00539 {
00540 QString title = i18n("Key Conflict");
00541 QString message = i18n("The '%1' rocker gesture has already been allocated to the \"%2\" action.\n"
00542 "Do you want to reassign it from that action to the current one?",
00543 gst.rockerName(), item->m_action->text());
00544
00545 if (KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18n("Reassign")))
00546 != KMessageBox::Continue)
00547 return false;
00548
00549 item->setRockerGesture(KRockerGesture());
00550 return true;
00551 }
00552
00553
00554
00555
00556 void KShortcutsEditorPrivate::globalSettingsChangedSystemwide(int which)
00557 {
00558 Q_UNUSED(which)
00559
00560 }
00561
00562
00563
00564
00565
00566
00567
00568
00569
00570
00571
00572
00573
00574
00575
00576
00577
00578
00579
00580 void KShortcutsEditorPrivate::printShortcuts() const
00581 {
00582 QTreeWidgetItem* root = ui.list->invisibleRootItem();
00583 QTextDocument doc;
00584 doc.setDefaultFont(KGlobalSettings::generalFont());
00585 QTextCursor cursor(&doc);
00586 cursor.beginEditBlock();
00587 QTextCharFormat headerFormat;
00588 headerFormat.setProperty(QTextFormat::FontSizeAdjustment, 3);
00589 headerFormat.setFontWeight(QFont::Bold);
00590 cursor.insertText(i18nc("header for an applications shortcut list","Shortcuts for %1",
00591 KGlobal::mainComponent().aboutData()->programName()),
00592 headerFormat);
00593 QTextCharFormat componentFormat;
00594 componentFormat.setProperty(QTextFormat::FontSizeAdjustment, 2);
00595 componentFormat.setFontWeight(QFont::Bold);
00596 QTextBlockFormat componentBlockFormat = cursor.blockFormat();
00597 componentBlockFormat.setTopMargin(16);
00598 componentBlockFormat.setBottomMargin(16);
00599
00600 QTextTableFormat tableformat;
00601 tableformat.setHeaderRowCount(1);
00602 tableformat.setCellPadding(4.0);
00603 tableformat.setCellSpacing(0);
00604 tableformat.setBorderStyle(QTextFrameFormat::BorderStyle_Solid);
00605 tableformat.setBorder(0.5);
00606
00607 QList<QPair<QString,ColumnDesignation> > shortcutTitleToColumn;
00608 shortcutTitleToColumn << qMakePair(i18n("Main:"), LocalPrimary);
00609 shortcutTitleToColumn << qMakePair(i18n("Alternate:"), LocalAlternate);
00610 shortcutTitleToColumn << qMakePair(i18n("Global:"), GlobalPrimary);
00611
00612 for (int i = 0; i < root->childCount(); i++) {
00613 QTreeWidgetItem* item = root->child(i);
00614 cursor.insertBlock(componentBlockFormat, componentFormat);
00615 cursor.insertText(item->text(0));
00616
00617 QTextTable* table = cursor.insertTable(1,3);
00618 table->setFormat(tableformat);
00619 int currow = 0;
00620
00621 QTextTableCell cell = table->cellAt(currow,0);
00622 QTextCharFormat format = cell.format();
00623 format.setFontWeight(QFont::Bold);
00624 cell.setFormat(format);
00625 cell.firstCursorPosition().insertText(i18n("Action Name"));
00626
00627 cell = table->cellAt(currow,1);
00628 cell.setFormat(format);
00629 cell.firstCursorPosition().insertText(i18n("Shortcuts"));
00630
00631 cell = table->cellAt(currow,2);
00632 cell.setFormat(format);
00633 cell.firstCursorPosition().insertText(i18n("Description"));
00634 currow++;
00635
00636 for (int j = 0; j < item->childCount(); j++) {
00637 QTreeWidgetItem* actionitem = item->child(j);
00638 KShortcutsEditorItem* editoritem =
00639 dynamic_cast<KShortcutsEditorItem*>(actionitem);
00640 table->insertRows(table->rows(),1);
00641 QVariant data = editoritem->data(Name,Qt::DisplayRole);
00642 table->cellAt(currow, 0).firstCursorPosition().insertText(data.toString());
00643
00644 QTextTable* shortcutTable = 0 ;
00645 for(int k = 0; k < shortcutTitleToColumn.count(); k++) {
00646 data = editoritem->data(shortcutTitleToColumn.at(k).second,Qt::DisplayRole);
00647 QString key = data.value<QKeySequence>().toString();
00648
00649 if(!key.isEmpty()) {
00650 if( !shortcutTable ) {
00651 shortcutTable = table->cellAt(currow, 1).firstCursorPosition().insertTable(1,2);
00652 QTextTableFormat shortcutTableFormat = tableformat;
00653 shortcutTableFormat.setCellSpacing(0.0);
00654 shortcutTableFormat.setHeaderRowCount(0);
00655 shortcutTableFormat.setBorder(0.0);
00656 shortcutTable->setFormat(shortcutTableFormat);
00657 } else {
00658 shortcutTable->insertRows(shortcutTable->rows(),1);
00659 }
00660 shortcutTable->cellAt(shortcutTable->rows()-1,0).firstCursorPosition().insertText(shortcutTitleToColumn.at(k).first);
00661 shortcutTable->cellAt(shortcutTable->rows()-1,1).firstCursorPosition().insertText(key);
00662 }
00663 }
00664
00665 KAction* action = editoritem->m_action;
00666 cell = table->cellAt(currow, 2);
00667 format = cell.format();
00668 format.setProperty(QTextFormat::FontSizeAdjustment, -1);
00669 cell.setFormat(format);
00670 cell.firstCursorPosition().insertHtml(action->whatsThis());
00671
00672 currow++;
00673 }
00674 cursor.movePosition(QTextCursor::End);
00675 }
00676 cursor.endEditBlock();
00677
00678 QPrinter printer;
00679 QPrintDialog dlg(&printer);
00680 if (dlg.exec() == QDialog::Accepted) {
00681 doc.print(&printer);
00682 }
00683 }
00684
00685 #include "kshortcutseditor.moc"