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