/* SPDX-FileCopyrightText: 2009 Dmitry Suzdalev SPDX-License-Identifier: GPL-2.0-or-later */ #include "editactiondialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "klipper_debug.h" #include "configdialog.h" #include "editcommanddialog.h" static QString output2text(ClipCommand::Output output) { switch (output) { case ClipCommand::IGNORE: return QString(i18n("Ignore")); case ClipCommand::REPLACE: return QString(i18n("Replace Clipboard")); case ClipCommand::ADD: return QString(i18n("Add to Clipboard")); } return QString(); } ////////////////////////// // ActionDetailModel // ////////////////////////// class ActionDetailModel : public QAbstractTableModel { public: explicit ActionDetailModel(ClipAction *action, QObject *parent = nullptr); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; const QList &commands() const { return m_commands; } void addCommand(const ClipCommand &command); void removeCommand(const QModelIndex &idx); void replaceCommand(const ClipCommand &command, const QModelIndex &idx); private: enum column_t { COMMAND_COL = 0, OUTPUT_COL = 1, DESCRIPTION_COL = 2 }; QList m_commands; QVariant displayData(ClipCommand *command, column_t column) const; QVariant decorationData(ClipCommand *command, column_t column) const; }; ActionDetailModel::ActionDetailModel(ClipAction *action, QObject *parent) : QAbstractTableModel(parent) , m_commands(action->commands()) { } Qt::ItemFlags ActionDetailModel::flags(const QModelIndex & /*index*/) const { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } int ActionDetailModel::columnCount(const QModelIndex & /*parent*/) const { return 3; } int ActionDetailModel::rowCount(const QModelIndex &) const { return m_commands.count(); } QVariant ActionDetailModel::displayData(ClipCommand *command, ActionDetailModel::column_t column) const { switch (column) { case COMMAND_COL: return command->command; case OUTPUT_COL: return output2text(command->output); case DESCRIPTION_COL: return command->description; } return QVariant(); } QVariant ActionDetailModel::decorationData(ClipCommand *command, ActionDetailModel::column_t column) const { switch (column) { case COMMAND_COL: return command->icon.isEmpty() ? QIcon::fromTheme(QStringLiteral("system-run")) : QIcon::fromTheme(command->icon); case OUTPUT_COL: case DESCRIPTION_COL: break; } return QVariant(); } QVariant ActionDetailModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (static_cast(section)) { case COMMAND_COL: return i18n("Command"); case OUTPUT_COL: return i18n("Output"); case DESCRIPTION_COL: return i18n("Description"); } } return QAbstractTableModel::headerData(section, orientation, role); } QVariant ActionDetailModel::data(const QModelIndex &index, int role) const { const int column = index.column(); const int row = index.row(); ClipCommand cmd = m_commands.at(row); switch (role) { case Qt::DisplayRole: return displayData(&cmd, static_cast(column)); case Qt::DecorationRole: return decorationData(&cmd, static_cast(column)); } return QVariant(); } void ActionDetailModel::addCommand(const ClipCommand &command) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_commands << command; endInsertRows(); } void ActionDetailModel::replaceCommand(const ClipCommand &command, const QModelIndex &idx) { if (!idx.isValid()) return; const int row = idx.row(); m_commands[row] = command; Q_EMIT dataChanged(index(row, static_cast(COMMAND_COL)), index(row, static_cast(DESCRIPTION_COL))); } void ActionDetailModel::removeCommand(const QModelIndex &idx) { if (!idx.isValid()) return; const int row = idx.row(); beginRemoveRows(QModelIndex(), row, row); m_commands.removeAt(row); endRemoveRows(); } ////////////////////////// // EditActionDialog // ////////////////////////// EditActionDialog::EditActionDialog(QWidget *parent) : QDialog(parent) { setWindowTitle(i18n("Action Properties")); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); buttons->button(QDialogButtonBox::Ok)->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttons, &QDialogButtonBox::accepted, this, &EditActionDialog::slotAccepted); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); // Upper widget: pattern, description and options QWidget *optionsWidget = new QWidget(this); QFormLayout *optionsLayout = new QFormLayout(optionsWidget); // General information label QLabel *hint = ConfigDialog::createHintLabel(xi18nc("@info", "An action takes effect when its \ match pattern matches the clipboard contents. \ When this happens, the action's commands appear \ in the Klipper popup menu; if one of them is chosen, \ the command is executed."), this); optionsLayout->addRow(hint); optionsLayout->addRow(QString(), new QLabel(optionsWidget)); // Pattern (regular expression) m_regExpEdit = new QLineEdit(optionsWidget); m_regExpEdit->setClearButtonEnabled(true); m_regExpEdit->setPlaceholderText(i18n("Enter a pattern to match against the clipboard")); optionsLayout->addRow(i18n("Match pattern:"), m_regExpEdit); hint = ConfigDialog::createHintLabel(xi18nc("@info", "The match pattern is a regular expression. \ For more information see the \ Wikipedia entry \ for this topic."), this); hint->setOpenExternalLinks(true); optionsLayout->addRow(QString(), hint); // Description m_descriptionEdit = new QLineEdit(optionsWidget); m_descriptionEdit->setClearButtonEnabled(true); m_descriptionEdit->setPlaceholderText(i18n("Enter a description for the action")); optionsLayout->addRow(i18n("Description:"), m_descriptionEdit); // Include in automatic popup m_automaticCheck = new QCheckBox(i18n("Include in automatic popup"), optionsWidget); optionsLayout->addRow(QString(), m_automaticCheck); hint = ConfigDialog::createHintLabel(xi18nc("@info", "The commands \ for this match will be included in the automatic action popup, if it is enabled in \ the Action Menu page. If this option is turned off, the commands for \ this match will not be included in the automatic popup but they will be included if the \ popup is activated manually with the %1 key shortcut.", ConfigDialog::manualShortcutString()), this); optionsLayout->addRow(QString(), hint); optionsLayout->addRow(QString(), new QLabel(optionsWidget)); // Lower widget: command list and action buttons QWidget *listWidget = new QWidget(this); QGridLayout *listLayout = new QGridLayout(listWidget); listLayout->setContentsMargins(0, 0, 0, 0); // Command list m_commandList = new QTableView(listWidget); m_commandList->setAlternatingRowColors(true); m_commandList->setSelectionMode(QAbstractItemView::SingleSelection); m_commandList->setSelectionBehavior(QAbstractItemView::SelectRows); m_commandList->setShowGrid(false); m_commandList->setWordWrap(false); m_commandList->horizontalHeader()->setStretchLastSection(true); m_commandList->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); m_commandList->verticalHeader()->setVisible(false); // For some reason, the default row height is 30 pixels. // Set it to the minimumSectionSize instead, // which is the font height+struts. m_commandList->verticalHeader()->setDefaultSectionSize(m_commandList->verticalHeader()->minimumSectionSize()); listLayout->addWidget(m_commandList, 0, 0, 1, -1); listLayout->setRowStretch(0, 1); // "Add" button m_addCommandPb = new QPushButton(QIcon::fromTheme(QStringLiteral("list-add")), i18n("Add Command..."), listWidget); connect(m_addCommandPb, &QPushButton::clicked, this, &EditActionDialog::onAddCommand); listLayout->addWidget(m_addCommandPb, 1, 0); // "Edit" button m_editCommandPb = new QPushButton(QIcon::fromTheme(QStringLiteral("document-edit")), i18n("Edit Command..."), this); connect(m_editCommandPb, &QPushButton::clicked, this, &EditActionDialog::onEditCommand); listLayout->addWidget(m_editCommandPb, 1, 1); listLayout->setColumnStretch(2, 1); // "Delete" button m_removeCommandPb = new QPushButton(QIcon::fromTheme(QStringLiteral("list-remove")), i18n("Delete Command"), this); connect(m_removeCommandPb, &QPushButton::clicked, this, &EditActionDialog::onRemoveCommand); listLayout->addWidget(m_removeCommandPb, 1, 3); // Add some vertical space between our buttons and the dialogue buttons listLayout->setRowMinimumHeight(2, 16); // Main dialogue layout QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(optionsWidget); mainLayout->addWidget(listWidget); mainLayout->setStretch(1, 1); mainLayout->addWidget(buttons); (void)winId(); windowHandle()->resize(540, 560); // default, if there is no saved size const KConfigGroup grp = KSharedConfig::openConfig()->group(QLatin1String(metaObject()->className())); KWindowConfig::restoreWindowSize(windowHandle(), grp); resize(windowHandle()->size()); QByteArray hdrState = grp.readEntry("ColumnState", QByteArray()); if (!hdrState.isEmpty()) { qCDebug(KLIPPER_LOG) << "Restoring column state"; m_commandList->horizontalHeader()->restoreState(QByteArray::fromBase64(hdrState)); } // do this after restoreState() m_commandList->horizontalHeader()->setHighlightSections(false); } void EditActionDialog::setAction(ClipAction *act, int commandIdxToSelect) { m_action = act; m_model = new ActionDetailModel(act, this); m_commandList->setModel(m_model); connect(m_commandList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &EditActionDialog::onSelectionChanged); connect(m_commandList, &QAbstractItemView::doubleClicked, this, &EditActionDialog::onEditCommand); updateWidgets(commandIdxToSelect); } void EditActionDialog::updateWidgets(int commandIdxToSelect) { if (!m_action) { qCDebug(KLIPPER_LOG) << "no action to edit was set"; return; } m_regExpEdit->setText(m_action->actionRegexPattern()); m_descriptionEdit->setText(m_action->description()); m_automaticCheck->setChecked(m_action->automatic()); if (commandIdxToSelect != -1) { m_commandList->setCurrentIndex(m_model->index(commandIdxToSelect, 0)); } onSelectionChanged(); // update Remove/Edit buttons } void EditActionDialog::saveAction() { if (!m_action) { qCDebug(KLIPPER_LOG) << "no action to edit was set"; return; } m_action->setActionRegexPattern(m_regExpEdit->text()); m_action->setDescription(m_descriptionEdit->text()); m_action->setAutomatic(m_automaticCheck->isChecked()); m_action->clearCommands(); for (const auto commands = m_model->commands(); const ClipCommand &cmd : commands) { m_action->addCommand(cmd); } } void EditActionDialog::slotAccepted() { saveAction(); qCDebug(KLIPPER_LOG) << "Saving dialogue state"; KConfigGroup grp = KSharedConfig::openConfig()->group(QLatin1String(metaObject()->className())); KWindowConfig::saveWindowSize(windowHandle(), grp); grp.writeEntry("ColumnState", m_commandList->horizontalHeader()->saveState().toBase64()); accept(); } void EditActionDialog::onAddCommand() { ClipCommand command(QString(), QString(), true, QLatin1String("")); EditCommandDialog dlg(command, this); if (dlg.exec() != QDialog::Accepted) return; m_model->addCommand(dlg.command()); } void EditActionDialog::onEditCommand() { QPersistentModelIndex commandIndex(m_commandList->selectionModel()->currentIndex()); if (!commandIndex.isValid()) return; EditCommandDialog dlg(m_model->commands().at(commandIndex.row()), this); if (dlg.exec() != QDialog::Accepted) return; m_model->replaceCommand(dlg.command(), commandIndex); } void EditActionDialog::onRemoveCommand() { QPersistentModelIndex commandIndex(m_commandList->selectionModel()->currentIndex()); if (!commandIndex.isValid()) return; if (KMessageBox::warningContinueCancel( this, xi18nc("@info", "Delete the selected command %1?", m_model->commands().at(commandIndex.row()).description), i18n("Confirm Delete Command"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), QStringLiteral("deleteCommand"), KMessageBox::Dangerous) == KMessageBox::Continue) { m_model->removeCommand(commandIndex); } } void EditActionDialog::onSelectionChanged() { const bool itemIsSelected = (m_commandList->selectionModel() && m_commandList->selectionModel()->hasSelection()); m_removeCommandPb->setEnabled(itemIsSelected); m_editCommandPb->setEnabled(itemIsSelected); }