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 &nbsp;&nbsp;<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 }