File indexing completed on 2024-04-28 05:35:27
0001 /* 0002 SPDX-FileCopyrightText: 2009 Dmitry Suzdalev <dimsuz@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "editactiondialog.h" 0008 0009 #include <QDialogButtonBox> 0010 #include <qcheckbox.h> 0011 #include <qcoreapplication.h> 0012 #include <qformlayout.h> 0013 #include <qgridlayout.h> 0014 #include <qheaderview.h> 0015 #include <qlabel.h> 0016 #include <qlineedit.h> 0017 #include <qpushbutton.h> 0018 #include <qtableview.h> 0019 #include <qwindow.h> 0020 0021 #include <klocalizedstring.h> 0022 #include <kmessagebox.h> 0023 #include <kwindowconfig.h> 0024 0025 #include "klipper_debug.h" 0026 0027 #include "configdialog.h" 0028 #include "editcommanddialog.h" 0029 0030 static QString output2text(ClipCommand::Output output) 0031 { 0032 switch (output) { 0033 case ClipCommand::IGNORE: 0034 return QString(i18n("Ignore")); 0035 case ClipCommand::REPLACE: 0036 return QString(i18n("Replace Clipboard")); 0037 case ClipCommand::ADD: 0038 return QString(i18n("Add to Clipboard")); 0039 } 0040 return QString(); 0041 } 0042 0043 ////////////////////////// 0044 // ActionDetailModel // 0045 ////////////////////////// 0046 0047 class ActionDetailModel : public QAbstractTableModel 0048 { 0049 public: 0050 explicit ActionDetailModel(ClipAction *action, QObject *parent = nullptr); 0051 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; 0052 Qt::ItemFlags flags(const QModelIndex &index) const override; 0053 int rowCount(const QModelIndex &parent = QModelIndex()) const override; 0054 int columnCount(const QModelIndex &parent) const override; 0055 QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; 0056 const QList<ClipCommand> &commands() const 0057 { 0058 return m_commands; 0059 } 0060 void addCommand(const ClipCommand &command); 0061 void removeCommand(const QModelIndex &idx); 0062 void replaceCommand(const ClipCommand &command, const QModelIndex &idx); 0063 0064 private: 0065 enum column_t { COMMAND_COL = 0, OUTPUT_COL = 1, DESCRIPTION_COL = 2 }; 0066 QList<ClipCommand> m_commands; 0067 QVariant displayData(ClipCommand *command, column_t column) const; 0068 QVariant decorationData(ClipCommand *command, column_t column) const; 0069 }; 0070 0071 ActionDetailModel::ActionDetailModel(ClipAction *action, QObject *parent) 0072 : QAbstractTableModel(parent) 0073 , m_commands(action->commands()) 0074 { 0075 } 0076 0077 Qt::ItemFlags ActionDetailModel::flags(const QModelIndex & /*index*/) const 0078 { 0079 return Qt::ItemIsEnabled | Qt::ItemIsSelectable; 0080 } 0081 0082 int ActionDetailModel::columnCount(const QModelIndex & /*parent*/) const 0083 { 0084 return 3; 0085 } 0086 0087 int ActionDetailModel::rowCount(const QModelIndex &) const 0088 { 0089 return m_commands.count(); 0090 } 0091 0092 QVariant ActionDetailModel::displayData(ClipCommand *command, ActionDetailModel::column_t column) const 0093 { 0094 switch (column) { 0095 case COMMAND_COL: 0096 return command->command; 0097 case OUTPUT_COL: 0098 return output2text(command->output); 0099 case DESCRIPTION_COL: 0100 return command->description; 0101 } 0102 return QVariant(); 0103 } 0104 0105 QVariant ActionDetailModel::decorationData(ClipCommand *command, ActionDetailModel::column_t column) const 0106 { 0107 switch (column) { 0108 case COMMAND_COL: 0109 return command->icon.isEmpty() ? QIcon::fromTheme(QStringLiteral("system-run")) : QIcon::fromTheme(command->icon); 0110 case OUTPUT_COL: 0111 case DESCRIPTION_COL: 0112 break; 0113 } 0114 return QVariant(); 0115 } 0116 0117 QVariant ActionDetailModel::headerData(int section, Qt::Orientation orientation, int role) const 0118 { 0119 if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { 0120 switch (static_cast<column_t>(section)) { 0121 case COMMAND_COL: 0122 return i18n("Command"); 0123 case OUTPUT_COL: 0124 return i18n("Output"); 0125 case DESCRIPTION_COL: 0126 return i18n("Description"); 0127 } 0128 } 0129 return QAbstractTableModel::headerData(section, orientation, role); 0130 } 0131 0132 QVariant ActionDetailModel::data(const QModelIndex &index, int role) const 0133 { 0134 const int column = index.column(); 0135 const int row = index.row(); 0136 ClipCommand cmd = m_commands.at(row); 0137 switch (role) { 0138 case Qt::DisplayRole: 0139 return displayData(&cmd, static_cast<column_t>(column)); 0140 case Qt::DecorationRole: 0141 return decorationData(&cmd, static_cast<column_t>(column)); 0142 } 0143 return QVariant(); 0144 } 0145 0146 void ActionDetailModel::addCommand(const ClipCommand &command) 0147 { 0148 beginInsertRows(QModelIndex(), rowCount(), rowCount()); 0149 m_commands << command; 0150 endInsertRows(); 0151 } 0152 0153 void ActionDetailModel::replaceCommand(const ClipCommand &command, const QModelIndex &idx) 0154 { 0155 if (!idx.isValid()) 0156 return; 0157 const int row = idx.row(); 0158 m_commands[row] = command; 0159 emit dataChanged(index(row, static_cast<int>(COMMAND_COL)), index(row, static_cast<int>(DESCRIPTION_COL))); 0160 } 0161 0162 void ActionDetailModel::removeCommand(const QModelIndex &idx) 0163 { 0164 if (!idx.isValid()) 0165 return; 0166 const int row = idx.row(); 0167 beginRemoveRows(QModelIndex(), row, row); 0168 m_commands.removeAt(row); 0169 endRemoveRows(); 0170 } 0171 0172 ////////////////////////// 0173 // EditActionDialog // 0174 ////////////////////////// 0175 0176 EditActionDialog::EditActionDialog(QWidget *parent) 0177 : QDialog(parent) 0178 { 0179 setWindowTitle(i18n("Action Properties")); 0180 QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); 0181 buttons->button(QDialogButtonBox::Ok)->setShortcut(Qt::CTRL | Qt::Key_Return); 0182 connect(buttons, &QDialogButtonBox::accepted, this, &EditActionDialog::slotAccepted); 0183 connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); 0184 0185 // Upper widget: pattern, description and options 0186 QWidget *optionsWidget = new QWidget(this); 0187 QFormLayout *optionsLayout = new QFormLayout(optionsWidget); 0188 0189 // General information label 0190 QLabel *hint = ConfigDialog::createHintLabel(xi18nc("@info", 0191 "An action takes effect when its \ 0192 <interface>match pattern</interface> matches the clipboard contents. \ 0193 When this happens, the action's <interface>commands</interface> appear \ 0194 in the Klipper popup menu; if one of them is chosen, \ 0195 the command is executed."), 0196 this); 0197 optionsLayout->addRow(hint); 0198 optionsLayout->addRow(QString(), new QLabel(optionsWidget)); 0199 0200 // Pattern (regular expression) 0201 m_regExpEdit = new QLineEdit(optionsWidget); 0202 m_regExpEdit->setClearButtonEnabled(true); 0203 m_regExpEdit->setPlaceholderText(i18n("Enter a pattern to match against the clipboard")); 0204 0205 optionsLayout->addRow(i18n("Match pattern:"), m_regExpEdit); 0206 0207 hint = ConfigDialog::createHintLabel(xi18nc("@info", 0208 "The match pattern is a regular expression. \ 0209 For more information see the \ 0210 <link url=\"https://en.wikipedia.org/wiki/Regular_expression\">Wikipedia entry</link> \ 0211 for this topic."), 0212 this); 0213 hint->setOpenExternalLinks(true); 0214 optionsLayout->addRow(QString(), hint); 0215 0216 // Description 0217 m_descriptionEdit = new QLineEdit(optionsWidget); 0218 m_descriptionEdit->setClearButtonEnabled(true); 0219 m_descriptionEdit->setPlaceholderText(i18n("Enter a description for the action")); 0220 optionsLayout->addRow(i18n("Description:"), m_descriptionEdit); 0221 0222 // Include in automatic popup 0223 m_automaticCheck = new QCheckBox(i18n("Include in automatic popup"), optionsWidget); 0224 optionsLayout->addRow(QString(), m_automaticCheck); 0225 0226 hint = ConfigDialog::createHintLabel(xi18nc("@info", 0227 "The commands \ 0228 for this match will be included in the automatic action popup, if it is enabled in \ 0229 the <interface>Action Menu</interface> page. If this option is turned off, the commands for \ 0230 this match will not be included in the automatic popup but they will be included if the \ 0231 popup is activated manually with the <shortcut>%1</shortcut> key shortcut.", 0232 ConfigDialog::manualShortcutString()), 0233 this); 0234 optionsLayout->addRow(QString(), hint); 0235 0236 optionsLayout->addRow(QString(), new QLabel(optionsWidget)); 0237 0238 // Lower widget: command list and action buttons 0239 QWidget *listWidget = new QWidget(this); 0240 QGridLayout *listLayout = new QGridLayout(listWidget); 0241 listLayout->setContentsMargins(0, 0, 0, 0); 0242 0243 // Command list 0244 m_commandList = new QTableView(listWidget); 0245 m_commandList->setAlternatingRowColors(true); 0246 m_commandList->setSelectionMode(QAbstractItemView::SingleSelection); 0247 m_commandList->setSelectionBehavior(QAbstractItemView::SelectRows); 0248 m_commandList->setShowGrid(false); 0249 m_commandList->setWordWrap(false); 0250 m_commandList->horizontalHeader()->setStretchLastSection(true); 0251 m_commandList->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); 0252 m_commandList->verticalHeader()->setVisible(false); 0253 // For some reason, the default row height is 30 pixels. 0254 // Set it to the minimumSectionSize instead, 0255 // which is the font height+struts. 0256 m_commandList->verticalHeader()->setDefaultSectionSize(m_commandList->verticalHeader()->minimumSectionSize()); 0257 0258 listLayout->addWidget(m_commandList, 0, 0, 1, -1); 0259 listLayout->setRowStretch(0, 1); 0260 0261 // "Add" button 0262 m_addCommandPb = new QPushButton(QIcon::fromTheme(QStringLiteral("list-add")), i18n("Add Command..."), listWidget); 0263 connect(m_addCommandPb, &QPushButton::clicked, this, &EditActionDialog::onAddCommand); 0264 listLayout->addWidget(m_addCommandPb, 1, 0); 0265 0266 // "Edit" button 0267 m_editCommandPb = new QPushButton(QIcon::fromTheme(QStringLiteral("document-edit")), i18n("Edit Command..."), this); 0268 connect(m_editCommandPb, &QPushButton::clicked, this, &EditActionDialog::onEditCommand); 0269 listLayout->addWidget(m_editCommandPb, 1, 1); 0270 listLayout->setColumnStretch(2, 1); 0271 0272 // "Delete" button 0273 m_removeCommandPb = new QPushButton(QIcon::fromTheme(QStringLiteral("list-remove")), i18n("Delete Command"), this); 0274 connect(m_removeCommandPb, &QPushButton::clicked, this, &EditActionDialog::onRemoveCommand); 0275 listLayout->addWidget(m_removeCommandPb, 1, 3); 0276 0277 // Add some vertical space between our buttons and the dialogue buttons 0278 listLayout->setRowMinimumHeight(2, 16); 0279 0280 // Main dialogue layout 0281 QVBoxLayout *mainLayout = new QVBoxLayout(this); 0282 mainLayout->addWidget(optionsWidget); 0283 mainLayout->addWidget(listWidget); 0284 mainLayout->setStretch(1, 1); 0285 mainLayout->addWidget(buttons); 0286 0287 (void)winId(); 0288 windowHandle()->resize(540, 560); // default, if there is no saved size 0289 const KConfigGroup grp = KSharedConfig::openConfig()->group(QLatin1String(metaObject()->className())); 0290 KWindowConfig::restoreWindowSize(windowHandle(), grp); 0291 resize(windowHandle()->size()); 0292 0293 QByteArray hdrState = grp.readEntry("ColumnState", QByteArray()); 0294 if (!hdrState.isEmpty()) { 0295 qCDebug(KLIPPER_LOG) << "Restoring column state"; 0296 m_commandList->horizontalHeader()->restoreState(QByteArray::fromBase64(hdrState)); 0297 } 0298 // do this after restoreState() 0299 m_commandList->horizontalHeader()->setHighlightSections(false); 0300 } 0301 0302 void EditActionDialog::setAction(ClipAction *act, int commandIdxToSelect) 0303 { 0304 m_action = act; 0305 m_model = new ActionDetailModel(act, this); 0306 m_commandList->setModel(m_model); 0307 connect(m_commandList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &EditActionDialog::onSelectionChanged); 0308 connect(m_commandList, &QAbstractItemView::doubleClicked, this, &EditActionDialog::onEditCommand); 0309 updateWidgets(commandIdxToSelect); 0310 } 0311 0312 void EditActionDialog::updateWidgets(int commandIdxToSelect) 0313 { 0314 if (!m_action) { 0315 qCDebug(KLIPPER_LOG) << "no action to edit was set"; 0316 return; 0317 } 0318 0319 m_regExpEdit->setText(m_action->actionRegexPattern()); 0320 m_descriptionEdit->setText(m_action->description()); 0321 m_automaticCheck->setChecked(m_action->automatic()); 0322 0323 if (commandIdxToSelect != -1) { 0324 m_commandList->setCurrentIndex(m_model->index(commandIdxToSelect, 0)); 0325 } 0326 0327 onSelectionChanged(); // update Remove/Edit buttons 0328 } 0329 0330 void EditActionDialog::saveAction() 0331 { 0332 if (!m_action) { 0333 qCDebug(KLIPPER_LOG) << "no action to edit was set"; 0334 return; 0335 } 0336 0337 m_action->setActionRegexPattern(m_regExpEdit->text()); 0338 m_action->setDescription(m_descriptionEdit->text()); 0339 m_action->setAutomatic(m_automaticCheck->isChecked()); 0340 0341 m_action->clearCommands(); 0342 0343 foreach (const ClipCommand &cmd, m_model->commands()) { 0344 m_action->addCommand(cmd); 0345 } 0346 } 0347 0348 void EditActionDialog::slotAccepted() 0349 { 0350 saveAction(); 0351 0352 qCDebug(KLIPPER_LOG) << "Saving dialogue state"; 0353 KConfigGroup grp = KSharedConfig::openConfig()->group(QLatin1String(metaObject()->className())); 0354 KWindowConfig::saveWindowSize(windowHandle(), grp); 0355 grp.writeEntry("ColumnState", m_commandList->horizontalHeader()->saveState().toBase64()); 0356 accept(); 0357 } 0358 0359 void EditActionDialog::onAddCommand() 0360 { 0361 ClipCommand command(QString(), QString(), true, QLatin1String("")); 0362 EditCommandDialog dlg(command, this); 0363 if (dlg.exec() != QDialog::Accepted) 0364 return; 0365 m_model->addCommand(dlg.command()); 0366 } 0367 0368 void EditActionDialog::onEditCommand() 0369 { 0370 QPersistentModelIndex commandIndex(m_commandList->selectionModel()->currentIndex()); 0371 if (!commandIndex.isValid()) 0372 return; 0373 0374 EditCommandDialog dlg(m_model->commands().at(commandIndex.row()), this); 0375 if (dlg.exec() != QDialog::Accepted) 0376 return; 0377 m_model->replaceCommand(dlg.command(), commandIndex); 0378 } 0379 0380 void EditActionDialog::onRemoveCommand() 0381 { 0382 QPersistentModelIndex commandIndex(m_commandList->selectionModel()->currentIndex()); 0383 if (!commandIndex.isValid()) 0384 return; 0385 0386 if (KMessageBox::warningContinueCancel( 0387 this, 0388 xi18nc("@info", "Delete the selected command <resource>%1</resource>?", m_model->commands().at(commandIndex.row()).description), 0389 i18n("Confirm Delete Command"), 0390 KStandardGuiItem::del(), 0391 KStandardGuiItem::cancel(), 0392 QStringLiteral("deleteCommand"), 0393 KMessageBox::Dangerous) 0394 == KMessageBox::Continue) { 0395 m_model->removeCommand(commandIndex); 0396 } 0397 } 0398 0399 void EditActionDialog::onSelectionChanged() 0400 { 0401 const bool itemIsSelected = (m_commandList->selectionModel() && m_commandList->selectionModel()->hasSelection()); 0402 m_removeCommandPb->setEnabled(itemIsSelected); 0403 m_editCommandPb->setEnabled(itemIsSelected); 0404 }