File indexing completed on 2024-04-28 16:54:23
0001 /* 0002 SPDX-FileCopyrightText: 2000 Carsten Pfeiffer <pfeiffer@kde.org> 0003 SPDX-FileCopyrightText: 2008-2009 Dmitry Suzdalev <dimsuz@gmail.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "configdialog.h" 0009 0010 #include <qbuttongroup.h> 0011 #include <qcheckbox.h> 0012 #include <qfontdatabase.h> 0013 #include <qformlayout.h> 0014 #include <qgridlayout.h> 0015 #include <qheaderview.h> 0016 #include <qlabel.h> 0017 #include <qpushbutton.h> 0018 #include <qradiobutton.h> 0019 #include <qtooltip.h> 0020 #include <qwindow.h> 0021 0022 #include <KConfigSkeleton> 0023 #include <KEditListWidget> 0024 #include <KShortcutsEditor> 0025 #include <kconfigskeleton.h> 0026 #include <kglobalaccel.h> 0027 #include <kmessagebox.h> 0028 #include <kmessagewidget.h> 0029 #include <kpluralhandlingspinbox.h> 0030 #include <kwindowconfig.h> 0031 0032 #include "klipper_debug.h" 0033 0034 #include "actionstreewidget.h" 0035 #include "editactiondialog.h" 0036 #include "klipper.h" 0037 #include "klippersettings.h" 0038 0039 /* static */ QLabel *ConfigDialog::createHintLabel(const QString &text, QWidget *parent) 0040 { 0041 QLabel *hintLabel = new QLabel(text, parent); 0042 hintLabel->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); 0043 hintLabel->setWordWrap(true); 0044 hintLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); 0045 0046 // The minimum width needs to be set so that QLabel will wrap the text 0047 // to fill that width. Otherwise, it will try to adjust the text wrapping 0048 // width so that the text is laid out in a pleasing looking rectangle, 0049 // which unfortunately leaves a lot of white space below the actual text. 0050 // See the "tryWidth" block in QLabelPrivate::sizeForWidth(). 0051 hintLabel->setMinimumWidth(400); 0052 0053 return (hintLabel); 0054 } 0055 0056 /* static */ QLabel *ConfigDialog::createHintLabel(const KConfigSkeletonItem *item, QWidget *parent) 0057 { 0058 return (createHintLabel(item->whatsThis(), parent)); 0059 } 0060 0061 /* static */ QString ConfigDialog::manualShortcutString() 0062 { 0063 const QList<QKeySequence> keys = KGlobalAccel::self()->globalShortcut(QCoreApplication::applicationName(), QStringLiteral("repeat_action")); 0064 return (keys.value(0).toString(QKeySequence::NativeText)); 0065 } 0066 0067 ////////////////////////// 0068 // GeneralWidget // 0069 ////////////////////////// 0070 0071 GeneralWidget::GeneralWidget(QWidget *parent) 0072 : QWidget(parent) 0073 { 0074 QFormLayout *layout = new QFormLayout(this); 0075 0076 // Synchronise selection and clipboard 0077 const KConfigSkeletonItem *item = KlipperSettings::self()->syncClipboardsItem(); 0078 m_syncClipboardsCb = new QCheckBox(item->label(), this); 0079 m_syncClipboardsCb->setObjectName(QLatin1String("kcfg_SyncClipboards")); 0080 layout->addRow(i18n("Selection and Clipboard:"), m_syncClipboardsCb); 0081 0082 QLabel *hint = ConfigDialog::createHintLabel(item, this); 0083 layout->addRow(QString(), hint); 0084 connect(hint, &QLabel::linkActivated, this, [this, hint]() { 0085 QToolTip::showText(QCursor::pos(), 0086 xi18nc("@info:tooltip", 0087 "When text or an area of the screen is highlighted with the mouse or keyboard, \ 0088 this is the <emphasis>selection</emphasis>. It can be pasted using the middle mouse button.\ 0089 <nl/>\ 0090 <nl/>\ 0091 If the selection is explicitly copied using a <interface>Copy</interface> or <interface>Cut</interface> action, \ 0092 it is saved to the <emphasis>clipboard</emphasis>. It can be pasted using a <interface>Paste</interface> action. \ 0093 <nl/>\ 0094 <nl/>\ 0095 When turned on this option keeps the selection and the clipboard the same, so that any selection is immediately available to paste by any means. \ 0096 If it is turned off, the selection may still be saved in the clipboard history (subject to the options below), but it can only be pasted using the middle mouse button."), 0097 hint); 0098 }); 0099 0100 layout->addRow(QString(), new QLabel(this)); 0101 0102 // Retain clipboard history 0103 item = KlipperSettings::self()->keepClipboardContentsItem(); 0104 m_enableHistoryCb = new QCheckBox(item->label(), this); 0105 m_enableHistoryCb->setObjectName(QLatin1String("kcfg_KeepClipboardContents")); 0106 layout->addRow(i18n("Clipboard history:"), m_enableHistoryCb); 0107 0108 // Clipboard history size 0109 item = KlipperSettings::self()->maxClipItemsItem(); 0110 m_historySizeSb = new KPluralHandlingSpinBox(this); 0111 m_historySizeSb->setObjectName(QLatin1String("kcfg_MaxClipItems")); 0112 m_historySizeSb->setSuffix(ki18ncp("Number of entries", " entry", " entries")); 0113 layout->addRow(item->label(), m_historySizeSb); 0114 0115 layout->addRow(QString(), new QLabel(this)); 0116 0117 // Radio button group: Storing text selections in history 0118 // 0119 // These two options correspond to the 'ignoreSelection' internal 0120 // Klipper setting. 0121 // 0122 // The 'Always' option is not available if selection/clipboard synchronisation 0123 // is turned off - in this case the selection is never automatically saved 0124 // in the clipboard history. 0125 0126 QButtonGroup *buttonGroup = new QButtonGroup(this); 0127 0128 // This widget is not managed by KConfigDialogManager, but 0129 // the other radio button is. That is sufficient for the 0130 // manager to handle widget changes, the Apply button etc. 0131 m_alwaysTextRb = new QRadioButton(i18n("Always save in history"), this); 0132 m_alwaysTextRb->setChecked(true); // may be updated from settings later 0133 connect(m_alwaysTextRb, &QAbstractButton::toggled, this, &GeneralWidget::widgetChanged); 0134 buttonGroup->addButton(m_alwaysTextRb); 0135 layout->addRow(i18n("Text selection:"), m_alwaysTextRb); 0136 0137 m_copiedTextRb = new QRadioButton(i18n("Only when explicitly copied"), this); 0138 m_copiedTextRb->setObjectName(QLatin1String("kcfg_IgnoreSelection")); 0139 buttonGroup->addButton(m_copiedTextRb); 0140 layout->addRow(QString(), m_copiedTextRb); 0141 0142 layout->addRow(QString(), ConfigDialog::createHintLabel(i18n("Whether text selections are saved in the clipboard history."), this)); 0143 0144 // Radio button group: Storing non-text selections in history 0145 // 0146 // The truth table for the 4 possible combinations of internal Klipper 0147 // settings (of which two are equivalent, making 3 user-visible options) 0148 // controlling what is stored in the clipboard history is: 0149 // 0150 // selectionTextOnly ignoreImages Selected Selected Copied Copied Option 0151 // text image/other text image/other 0152 // 0153 // false false yes yes yes yes 1 0154 // true false yes no yes yes 2 0155 // false true yes no yes no 3 0156 // true true yes no yes no 3 0157 // 0158 // Option 1: Always store images in history 0159 // 2: Only when explicitly copied 0160 // 3: Never store images in history 0161 // 0162 // The 'Always' option is not available if selection/clipboard synchronisation 0163 // is turned off. 0164 0165 buttonGroup = new QButtonGroup(this); 0166 0167 // This widget is not managed by KConfigDialogManager, 0168 // but the other two radio buttons are. 0169 m_alwaysImageRb = new QRadioButton(i18n("Always save in history"), this); 0170 m_alwaysImageRb->setChecked(true); // may be updated from settings later 0171 connect(m_alwaysImageRb, &QAbstractButton::toggled, this, &GeneralWidget::widgetChanged); 0172 buttonGroup->addButton(m_alwaysImageRb); 0173 layout->addRow(i18n("Non-text selection:"), m_alwaysImageRb); 0174 0175 m_copiedImageRb = new QRadioButton(i18n("Only when explicitly copied"), this); 0176 m_copiedImageRb->setObjectName(QLatin1String("kcfg_SelectionTextOnly")); 0177 buttonGroup->addButton(m_copiedImageRb); 0178 layout->addRow(QString(), m_copiedImageRb); 0179 0180 m_neverImageRb = new QRadioButton(i18n("Never save in history"), this); 0181 m_neverImageRb->setObjectName(QLatin1String("kcfg_IgnoreImages")); 0182 buttonGroup->addButton(m_neverImageRb); 0183 layout->addRow(QString(), m_neverImageRb); 0184 0185 layout->addRow(QString(), ConfigDialog::createHintLabel(i18n("Whether non-text selections (such as images) are saved in the clipboard history."), this)); 0186 0187 m_havePrevAlwaysImageTextConfig = false; 0188 } 0189 0190 void GeneralWidget::updateWidgets() 0191 { 0192 // Initialise widgets which are not managed by KConfigDialogManager 0193 // from the application settings. 0194 0195 // SelectionTextOnly takes precedence over IgnoreImages, 0196 // see Klipper::checkClipData(). Give that radio button 0197 // priority too. 0198 if (KlipperSettings::selectionTextOnly()) { 0199 KlipperSettings::setIgnoreImages(false); 0200 } 0201 } 0202 0203 void GeneralWidget::initWidgetStates() 0204 { 0205 Q_ASSERT(!m_havePrevAlwaysImageTextConfig); 0206 // During dialog setup, disable / change some widgets according to current settings to achieve the same 0207 // internal consistency as settings changes made by the user after opening the dialog. 0208 slotWidgetModified(); 0209 m_havePrevAlwaysImageTextConfig = false; 0210 } 0211 0212 void GeneralWidget::slotWidgetModified() 0213 { 0214 // A setting widget has been changed. Update the state of 0215 // any other widgets that depend on it. 0216 0217 if (m_syncClipboardsCb->isChecked()) { 0218 m_alwaysImageRb->setEnabled(true); 0219 m_alwaysTextRb->setEnabled(true); 0220 m_copiedTextRb->setEnabled(true); 0221 0222 if (m_havePrevAlwaysImageTextConfig) { 0223 m_alwaysTextRb->setChecked(m_prevAlwaysText); 0224 m_alwaysImageRb->setChecked(m_prevAlwaysImage); 0225 m_havePrevAlwaysImageTextConfig = false; 0226 } 0227 } else { 0228 m_prevAlwaysText = m_alwaysTextRb->isChecked(); 0229 m_prevAlwaysImage = m_alwaysImageRb->isChecked(); 0230 m_havePrevAlwaysImageTextConfig = true; 0231 0232 if (m_alwaysImageRb->isChecked()) { 0233 m_copiedImageRb->setChecked(true); 0234 } 0235 0236 if (m_alwaysTextRb->isChecked()) { 0237 m_copiedTextRb->setChecked(true); 0238 } 0239 0240 m_alwaysImageRb->setEnabled(false); 0241 m_alwaysTextRb->setEnabled(false); 0242 m_copiedTextRb->setEnabled(false); 0243 } 0244 } 0245 0246 ////////////////////////// 0247 // PopupWidget // 0248 ////////////////////////// 0249 0250 PopupWidget::PopupWidget(QWidget *parent) 0251 : QWidget(parent) 0252 { 0253 QFormLayout *layout = new QFormLayout(this); 0254 0255 // Automatic popup 0256 const KConfigSkeletonItem *item = KlipperSettings::self()->uRLGrabberEnabledItem(); 0257 m_enablePopupCb = new QCheckBox(item->label(), this); 0258 m_enablePopupCb->setObjectName(QLatin1String("kcfg_URLGrabberEnabled")); 0259 layout->addRow(i18n("Show action popup menu:"), m_enablePopupCb); 0260 0261 // Replay from history popup 0262 item = KlipperSettings::self()->replayActionInHistoryItem(); 0263 m_historyPopupCb = new QCheckBox(item->label(), this); 0264 m_historyPopupCb->setObjectName(QLatin1String("kcfg_ReplayActionInHistory")); 0265 layout->addRow(QString(), m_historyPopupCb); 0266 0267 const QList<QKeySequence> keys = KGlobalAccel::self()->globalShortcut(QCoreApplication::applicationName(), QStringLiteral("repeat_action")); 0268 QLabel *hint = ConfigDialog::createHintLabel(xi18nc("@info", 0269 "When text that matches an action pattern is selected or is chosen from \ 0270 the clipboard history, automatically show the popup menu with applicable actions. \ 0271 If the automatic menu is turned off here, or it is not shown for an excluded window, \ 0272 then it can be shown by using the <shortcut>%1</shortcut> key shortcut.", 0273 ConfigDialog::manualShortcutString()), 0274 this); 0275 layout->addRow(QString(), hint); 0276 0277 // Exclusions 0278 QPushButton *exclusionsButton = new QPushButton(QIcon::fromTheme(QStringLiteral("configure")), i18n("Exclude Windows..."), this); 0279 connect(exclusionsButton, &QPushButton::clicked, this, &PopupWidget::onAdvanced); 0280 0281 // Right align the push button, regardless of the QFormLayout style 0282 QHBoxLayout *hb = new QHBoxLayout; 0283 hb->setContentsMargins(0, 0, 0, 0); 0284 hb->addStretch(1); 0285 hb->addWidget(exclusionsButton); 0286 layout->addRow(QString(), hb); 0287 0288 // Action popup time 0289 item = KlipperSettings::self()->timeoutForActionPopupsItem(); 0290 m_actionTimeoutSb = new KPluralHandlingSpinBox(this); 0291 m_actionTimeoutSb->setObjectName(QLatin1String("kcfg_TimeoutForActionPopups")); 0292 m_actionTimeoutSb->setSuffix(ki18ncp("Unit of time", " second", " seconds")); 0293 m_actionTimeoutSb->setSpecialValueText(i18nc("No timeout", "None")); 0294 layout->addRow(item->label(), m_actionTimeoutSb); 0295 0296 layout->addRow(QString(), new QLabel(this)); 0297 0298 // Remove whitespace 0299 item = KlipperSettings::self()->stripWhiteSpaceItem(); 0300 m_stripWhitespaceCb = new QCheckBox(item->label(), this); 0301 m_stripWhitespaceCb->setObjectName(QLatin1String("kcfg_StripWhiteSpace")); 0302 layout->addRow(i18n("Options:"), m_stripWhitespaceCb); 0303 layout->addRow(QString(), ConfigDialog::createHintLabel(item, this)); 0304 0305 // MIME actions 0306 item = KlipperSettings::self()->enableMagicMimeActionsItem(); 0307 m_mimeActionsCb = new QCheckBox(item->label(), this); 0308 m_mimeActionsCb->setObjectName(QLatin1String("kcfg_EnableMagicMimeActions")); 0309 layout->addRow(QString(), m_mimeActionsCb); 0310 layout->addRow(QString(), ConfigDialog::createHintLabel(item, this)); 0311 0312 layout->addRow(QString(), new QLabel(this)); 0313 } 0314 0315 void PopupWidget::setExcludedWMClasses(const QStringList &excludedWMClasses) 0316 { 0317 m_exclWMClasses = excludedWMClasses; 0318 } 0319 0320 QStringList PopupWidget::excludedWMClasses() const 0321 { 0322 return m_exclWMClasses; 0323 } 0324 0325 void PopupWidget::onAdvanced() 0326 { 0327 QDialog dlg(this); 0328 dlg.setModal(true); 0329 dlg.setWindowTitle(i18n("Exclude Windows")); 0330 QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dlg); 0331 buttons->button(QDialogButtonBox::Ok)->setShortcut(Qt::CTRL | Qt::Key_Return); 0332 connect(buttons, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); 0333 connect(buttons, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); 0334 0335 AdvancedWidget *widget = new AdvancedWidget(&dlg); 0336 widget->setWMClasses(m_exclWMClasses); 0337 0338 QVBoxLayout *layout = new QVBoxLayout(&dlg); 0339 layout->addWidget(widget); 0340 layout->addWidget(buttons); 0341 0342 if (dlg.exec() == QDialog::Accepted) { 0343 m_exclWMClasses = widget->wmClasses(); 0344 } 0345 } 0346 0347 ////////////////////////// 0348 // ActionsWidget // 0349 ////////////////////////// 0350 0351 ActionsWidget::ActionsWidget(QWidget *parent) 0352 : QWidget(parent) 0353 { 0354 QGridLayout *layout = new QGridLayout(this); 0355 layout->setContentsMargins(0, 0, 0, 0); 0356 0357 // General information label 0358 QLabel *hint = ConfigDialog::createHintLabel(xi18nc("@info", 0359 "When a <interface>match pattern</interface> \ 0360 matches the clipboard contents, its <interface>commands</interface> \ 0361 appear in the Klipper popup menu and can be executed."), 0362 this); 0363 layout->addWidget(hint, 0, 0, 1, -1); 0364 0365 // Scrolling list 0366 m_actionsTree = new ActionsTreeWidget(this); 0367 m_actionsTree->setColumnCount(2); 0368 m_actionsTree->setHeaderLabels({i18nc("@title:column", "Match pattern and commands"), i18nc("@title:column", "Description")}); 0369 0370 layout->addWidget(m_actionsTree, 1, 0, 1, -1); 0371 layout->setRowStretch(1, 1); 0372 0373 // Action buttons 0374 m_addActionButton = new QPushButton(QIcon::fromTheme(QStringLiteral("list-add")), i18n("Add Action..."), this); 0375 connect(m_addActionButton, &QPushButton::clicked, this, &ActionsWidget::onAddAction); 0376 layout->addWidget(m_addActionButton, 2, 0); 0377 0378 m_editActionButton = new QPushButton(QIcon::fromTheme(QStringLiteral("document-edit")), i18n("Edit Action..."), this); 0379 connect(m_editActionButton, &QPushButton::clicked, this, &ActionsWidget::onEditAction); 0380 layout->addWidget(m_editActionButton, 2, 1); 0381 layout->setColumnStretch(2, 1); 0382 0383 m_deleteActionButton = new QPushButton(QIcon::fromTheme(QStringLiteral("list-remove")), i18n("Delete Action"), this); 0384 connect(m_deleteActionButton, &QPushButton::clicked, this, &ActionsWidget::onDeleteAction); 0385 layout->addWidget(m_deleteActionButton, 2, 3); 0386 0387 // Where to configure the action options 0388 if (KlipperSettings::actionsInfoMessageShown()) { 0389 KMessageWidget *msg = new KMessageWidget(xi18nc("@info", 0390 "These actions appear in the popup menu \ 0391 which can be configured on the <interface>Action Menu</interface> page."), 0392 this); 0393 msg->setMessageType(KMessageWidget::Information); 0394 msg->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); 0395 msg->setWordWrap(true); 0396 msg->setCloseButtonVisible(true); 0397 0398 connect(msg, &KMessageWidget::hideAnimationFinished, this, []() { 0399 KlipperSettings::setActionsInfoMessageShown(false); 0400 }); 0401 layout->addWidget(msg, 3, 0, 1, -1); 0402 } 0403 0404 // Add some vertical space between our buttons and the dialogue buttons 0405 layout->setRowMinimumHeight(4, 16); 0406 0407 const KConfigGroup grp = KSharedConfig::openConfig()->group(metaObject()->className()); 0408 QByteArray hdrState = grp.readEntry("ColumnState", QByteArray()); 0409 if (!hdrState.isEmpty()) { 0410 qCDebug(KLIPPER_LOG) << "Restoring column state"; 0411 m_actionsTree->header()->restoreState(QByteArray::fromBase64(hdrState)); 0412 } else { 0413 m_actionsTree->header()->resizeSection(0, 250); 0414 } 0415 0416 connect(m_actionsTree, &QTreeWidget::itemSelectionChanged, this, &ActionsWidget::onSelectionChanged); 0417 connect(m_actionsTree, &QTreeWidget::itemDoubleClicked, this, &ActionsWidget::onEditAction); 0418 0419 onSelectionChanged(); 0420 } 0421 0422 void ActionsWidget::setActionList(const ActionList &list) 0423 { 0424 qDeleteAll(m_actionList); 0425 m_actionList.clear(); 0426 0427 for (const ClipAction *action : list) { 0428 if (!action) { 0429 qCDebug(KLIPPER_LOG) << "action is null!"; 0430 continue; 0431 } 0432 0433 // make a copy for us to work with from now on 0434 m_actionList.append(new ClipAction(*action)); 0435 } 0436 0437 updateActionListView(); 0438 } 0439 0440 void ActionsWidget::updateActionListView() 0441 { 0442 m_actionsTree->clear(); 0443 0444 for (const ClipAction *action : m_actionList) { 0445 if (!action) { 0446 qCDebug(KLIPPER_LOG) << "action is null!"; 0447 continue; 0448 } 0449 0450 QTreeWidgetItem *item = new QTreeWidgetItem; 0451 updateActionItem(item, action); 0452 0453 m_actionsTree->addTopLevelItem(item); 0454 } 0455 0456 // after all actions loaded, reset modified state of tree widget. 0457 // Needed because tree widget reacts on item changed events to tell if it is changed 0458 // this will ensure that apply button state will be correctly changed 0459 m_actionsTree->resetModifiedState(); 0460 } 0461 0462 void ActionsWidget::updateActionItem(QTreeWidgetItem *item, const ClipAction *action) 0463 { 0464 if (!item || !action) { 0465 qCDebug(KLIPPER_LOG) << "null pointer passed to function, nothing done"; 0466 return; 0467 } 0468 0469 // clear children if any 0470 item->takeChildren(); 0471 item->setText(0, action->actionRegexPattern()); 0472 item->setText(1, action->description()); 0473 0474 for (const ClipCommand &command : action->commands()) { 0475 QStringList cmdProps; 0476 cmdProps << command.command << command.description; 0477 QTreeWidgetItem *child = new QTreeWidgetItem(item, cmdProps); 0478 child->setIcon(0, QIcon::fromTheme(command.icon.isEmpty() ? QStringLiteral("system-run") : command.icon)); 0479 } 0480 } 0481 0482 ActionList ActionsWidget::actionList() const 0483 { 0484 // return a copy of our action list 0485 ActionList list; 0486 for (const ClipAction *action : m_actionList) { 0487 if (!action) { 0488 qCDebug(KLIPPER_LOG) << "action is null"; 0489 continue; 0490 } 0491 0492 list.append(new ClipAction(*action)); 0493 } 0494 0495 return list; 0496 } 0497 0498 void ActionsWidget::resetModifiedState() 0499 { 0500 m_actionsTree->resetModifiedState(); 0501 0502 qCDebug(KLIPPER_LOG) << "Saving column state"; 0503 KConfigGroup grp = KSharedConfig::openConfig()->group(metaObject()->className()); 0504 grp.writeEntry("ColumnState", m_actionsTree->header()->saveState().toBase64()); 0505 } 0506 0507 void ActionsWidget::onSelectionChanged() 0508 { 0509 const bool itemIsSelected = !m_actionsTree->selectedItems().isEmpty(); 0510 m_editActionButton->setEnabled(itemIsSelected); 0511 m_deleteActionButton->setEnabled(itemIsSelected); 0512 } 0513 0514 void ActionsWidget::onAddAction() 0515 { 0516 EditActionDialog dlg(this); 0517 ClipAction *newAct = new ClipAction; 0518 dlg.setAction(newAct); 0519 0520 if (dlg.exec() == QDialog::Accepted) { 0521 m_actionList.append(newAct); 0522 0523 QTreeWidgetItem *item = new QTreeWidgetItem; 0524 updateActionItem(item, newAct); 0525 m_actionsTree->addTopLevelItem(item); 0526 emit widgetChanged(); 0527 } 0528 } 0529 0530 void ActionsWidget::onEditAction() 0531 { 0532 QTreeWidgetItem *item = m_actionsTree->currentItem(); 0533 if (!item) { 0534 return; 0535 } 0536 0537 int commandIdx = -1; 0538 if (item->parent()) { 0539 commandIdx = item->parent()->indexOfChild(item); 0540 item = item->parent(); // interested in toplevel action 0541 } 0542 0543 int idx = m_actionsTree->indexOfTopLevelItem(item); 0544 ClipAction *action = m_actionList.at(idx); 0545 0546 if (!action) { 0547 qCDebug(KLIPPER_LOG) << "action is null"; 0548 return; 0549 } 0550 0551 EditActionDialog dlg(this); 0552 dlg.setAction(action, commandIdx); 0553 // dialog will save values into action if user hits OK 0554 if (dlg.exec() == QDialog::Accepted) { 0555 updateActionItem(item, action); 0556 emit widgetChanged(); 0557 } 0558 } 0559 0560 void ActionsWidget::onDeleteAction() 0561 { 0562 QTreeWidgetItem *item = m_actionsTree->currentItem(); 0563 if (!item) { 0564 return; 0565 } 0566 0567 // If the item has a parent, then it is a command (the second level 0568 // of the tree). Find the complete action. 0569 if (item->parent()) { 0570 item = item->parent(); 0571 } 0572 0573 if (KMessageBox::warningContinueCancel(this, 0574 xi18nc("@info", "Delete the selected action <resource>%1</resource><nl/>and all of its commands?", item->text(1)), 0575 i18n("Confirm Delete Action"), 0576 KStandardGuiItem::del(), 0577 KStandardGuiItem::cancel(), 0578 QStringLiteral("deleteAction"), 0579 KMessageBox::Dangerous) 0580 == KMessageBox::Continue) { 0581 int idx = m_actionsTree->indexOfTopLevelItem(item); 0582 m_actionList.removeAt(idx); 0583 delete item; 0584 emit widgetChanged(); 0585 } 0586 } 0587 0588 bool ActionsWidget::hasChanged() const 0589 { 0590 return (m_actionsTree->actionsChanged() != -1); 0591 } 0592 0593 ////////////////////////// 0594 // ConfigDialog // 0595 ////////////////////////// 0596 0597 ConfigDialog::ConfigDialog(QWidget *parent, KConfigSkeleton *skeleton, Klipper *klipper, KActionCollection *collection) 0598 : KConfigDialog(parent, QStringLiteral("preferences"), skeleton) 0599 , m_generalPage(new GeneralWidget(this)) 0600 , m_popupPage(new PopupWidget(this)) 0601 , m_actionsPage(new ActionsWidget(this)) 0602 , m_klipper(klipper) 0603 { 0604 addPage(m_generalPage, i18nc("General Config", "General"), QStringLiteral("klipper"), i18n("General Configuration")); 0605 addPage(m_popupPage, i18nc("Popup Menu Config", "Action Menu"), QStringLiteral("open-menu-symbolic"), i18n("Action Menu")); 0606 addPage(m_actionsPage, i18nc("Actions Config", "Actions Configuration"), QStringLiteral("system-run"), i18n("Actions Configuration")); 0607 0608 m_shortcutsWidget = new KShortcutsEditor(collection, this, KShortcutsEditor::GlobalAction); 0609 addPage(m_shortcutsWidget, i18nc("Shortcuts Config", "Shortcuts"), QStringLiteral("preferences-desktop-keyboard"), i18n("Shortcuts Configuration")); 0610 0611 connect(m_generalPage, &GeneralWidget::widgetChanged, this, &ConfigDialog::settingsChangedSlot); 0612 connect(m_actionsPage, &ActionsWidget::widgetChanged, this, &ConfigDialog::settingsChangedSlot); 0613 connect(this, &KConfigDialog::widgetModified, m_generalPage, &GeneralWidget::slotWidgetModified); 0614 m_generalPage->initWidgetStates(); 0615 0616 // from KWindowConfig::restoreWindowSize() API documentation 0617 (void)winId(); 0618 const KConfigGroup grp = KSharedConfig::openConfig()->group(metaObject()->className()); 0619 KWindowConfig::restoreWindowSize(windowHandle(), grp); 0620 resize(windowHandle()->size()); 0621 0622 setMinimumHeight(550); 0623 } 0624 0625 void ConfigDialog::updateSettings() 0626 { 0627 // The user clicked "OK" or "Apply". Save the settings from the widgets 0628 // to the application settings. 0629 0630 if (!m_klipper) { 0631 qCDebug(KLIPPER_LOG) << "Klipper object is null"; 0632 return; 0633 } 0634 0635 m_shortcutsWidget->save(); 0636 m_actionsPage->resetModifiedState(); 0637 0638 m_klipper->setURLGrabberEnabled(KlipperSettings::uRLGrabberEnabled()); 0639 m_klipper->urlGrabber()->setActionList(m_actionsPage->actionList()); 0640 m_klipper->urlGrabber()->setExcludedWMClasses(m_popupPage->excludedWMClasses()); 0641 m_klipper->saveSettings(); 0642 0643 KlipperSettings::self()->save(); 0644 0645 KConfigGroup grp = KSharedConfig::openConfig()->group("ConfigDialog"); 0646 KWindowConfig::saveWindowSize(windowHandle(), grp); 0647 } 0648 0649 void ConfigDialog::updateWidgets() 0650 { 0651 // The dialogue is being shown. Initialise widgets which are not 0652 // managed by KConfigDialogManager from the application settings. 0653 0654 if (m_klipper && m_klipper->urlGrabber()) { 0655 m_actionsPage->setActionList(m_klipper->urlGrabber()->actionList()); 0656 m_popupPage->setExcludedWMClasses(m_klipper->urlGrabber()->excludedWMClasses()); 0657 } else { 0658 qCDebug(KLIPPER_LOG) << "Klipper or grabber object is null"; 0659 return; 0660 } 0661 0662 m_generalPage->updateWidgets(); 0663 } 0664 0665 void ConfigDialog::updateWidgetsDefault() 0666 { 0667 // The user clicked "Defaults". Restore the default values for 0668 // widgets which are not managed by KConfigDialogManager. The 0669 // settings of "Actions Configuration" and "Excluded Windows" 0670 // are not reset to the default. 0671 0672 m_shortcutsWidget->allDefault(); 0673 } 0674 0675 bool ConfigDialog::hasChanged() 0676 { 0677 return (m_actionsPage->hasChanged() || m_shortcutsWidget->isModified()); 0678 } 0679 0680 ////////////////////////// 0681 // AdvancedWidget // 0682 ////////////////////////// 0683 0684 AdvancedWidget::AdvancedWidget(QWidget *parent) 0685 : QWidget(parent) 0686 { 0687 QVBoxLayout *mainLayout = new QVBoxLayout(this); 0688 0689 QLabel *hint = ConfigDialog::createHintLabel(xi18nc("@info", 0690 "The action popup will not be shown automatically for these windows, \ 0691 even if it is enabled. This is because, for example, a web browser may highlight a URL \ 0692 in the address bar while typing, so the menu would show for every keystroke.\ 0693 <nl/>\ 0694 <nl/>\ 0695 If the action menu appears unexpectedly when using a particular application, then add it to this list. \ 0696 <link>How to find the name to enter</link>."), 0697 this); 0698 0699 mainLayout->addWidget(hint); 0700 connect(hint, &QLabel::linkActivated, this, [this, hint]() { 0701 QToolTip::showText(QCursor::pos(), 0702 xi18nc("@info:tooltip", 0703 "The name that needs to be entered here is the WM_CLASS name of the window to be excluded. \ 0704 To find the WM_CLASS name for a window, in another terminal window enter the command:\ 0705 <nl/>\ 0706 <nl/>\ 0707 <icode>xprop | grep WM_CLASS</icode>\ 0708 <nl/>\ 0709 <nl/>\ 0710 and click on the window that you want to exclude. \ 0711 The first name that it displays after the equal sign is the one that you need to enter."), 0712 hint); 0713 }); 0714 0715 mainLayout->addWidget(hint); 0716 mainLayout->addWidget(new QLabel(this)); 0717 0718 m_editListBox = new KEditListWidget(this); 0719 m_editListBox->setButtons(KEditListWidget::Add | KEditListWidget::Remove); 0720 m_editListBox->setCheckAtEntering(true); 0721 mainLayout->addWidget(m_editListBox); 0722 0723 m_editListBox->setFocus(); 0724 } 0725 0726 void AdvancedWidget::setWMClasses(const QStringList &items) 0727 { 0728 m_editListBox->setItems(items); 0729 } 0730 0731 QStringList AdvancedWidget::wmClasses() const 0732 { 0733 return m_editListBox->items(); 0734 }