You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
405 lines
14 KiB
405 lines
14 KiB
/* |
|
SPDX-FileCopyrightText: 2009 Dmitry Suzdalev <dimsuz@gmail.com> |
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later |
|
*/ |
|
|
|
#include "editactiondialog.h" |
|
|
|
#include <QDialogButtonBox> |
|
#include <qcheckbox.h> |
|
#include <qcoreapplication.h> |
|
#include <qformlayout.h> |
|
#include <qgridlayout.h> |
|
#include <qheaderview.h> |
|
#include <qlabel.h> |
|
#include <qlineedit.h> |
|
#include <qpushbutton.h> |
|
#include <qtableview.h> |
|
#include <qwindow.h> |
|
|
|
#include <klocalizedstring.h> |
|
#include <kmessagebox.h> |
|
#include <kwindowconfig.h> |
|
|
|
#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<ClipCommand> &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<ClipCommand> 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<column_t>(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_t>(column)); |
|
case Qt::DecorationRole: |
|
return decorationData(&cmd, static_cast<column_t>(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; |
|
emit dataChanged(index(row, static_cast<int>(COMMAND_COL)), index(row, static_cast<int>(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 \ |
|
<interface>match pattern</interface> matches the clipboard contents. \ |
|
When this happens, the action's <interface>commands</interface> 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 \ |
|
<link url=\"https://en.wikipedia.org/wiki/Regular_expression\">Wikipedia entry</link> \ |
|
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 <interface>Action Menu</interface> 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 <shortcut>%1</shortcut> 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(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(); |
|
|
|
foreach (const ClipCommand &cmd, m_model->commands()) { |
|
m_action->addCommand(cmd); |
|
} |
|
} |
|
|
|
void EditActionDialog::slotAccepted() |
|
{ |
|
saveAction(); |
|
|
|
qCDebug(KLIPPER_LOG) << "Saving dialogue state"; |
|
KConfigGroup grp = KSharedConfig::openConfig()->group(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 <resource>%1</resource>?", 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); |
|
}
|
|
|