Improve dialog for confirming ignore pattern changes from file browser

* Show diff in proper text view with basic syntax highlighting
* Allow manual edits before applying changes
This commit is contained in:
Martchus 2024-06-14 21:23:35 +02:00
parent 8a51248791
commit 0729e180cb
5 changed files with 106 additions and 7 deletions

View File

@ -197,6 +197,10 @@ QString SyncthingFileModel::computeIgnorePatternDiff() const
SyncthingIgnores SyncthingFileModel::computeNewIgnorePatterns() const
{
auto newIgnorePatterns = SyncthingIgnores();
if (!m_manuallyEditedIgnorePatterns.isEmpty()) {
newIgnorePatterns.ignore = m_manuallyEditedIgnorePatterns.split(QChar('\n'));
return newIgnorePatterns;
}
auto index = std::size_t();
if (const auto change = m_stagedChanges.find(beforeFirstLine); change != m_stagedChanges.end()) {
for (const auto &line : change->newLines) {
@ -218,6 +222,11 @@ SyncthingIgnores SyncthingFileModel::computeNewIgnorePatterns() const
return newIgnorePatterns;
}
void SyncthingFileModel::editIgnorePatternsManually(const QString &ignorePatterns)
{
m_manuallyEditedIgnorePatterns = ignorePatterns;
}
QModelIndex SyncthingFileModel::parent(const QModelIndex &child) const
{
if (!child.isValid()) {
@ -622,6 +631,7 @@ QList<QAction *> SyncthingFileModel::selectionActions()
// allow user to review changes before applying them
if (!askedConfirmation) {
askedConfirmation = true;
m_manuallyEditedIgnorePatterns.clear();
emit actionNeedsConfirmation(action, tr("Do you want to apply the folliwng changes?"), computeIgnorePatternDiff());
return;
}

View File

@ -62,6 +62,8 @@ public:
Q_INVOKABLE QString path(const QModelIndex &path) const;
bool hasIgnorePatterns() const;
const std::vector<SyncthingIgnorePattern> &presentIgnorePatterns() const;
SyncthingIgnores computeNewIgnorePatterns() const;
void editIgnorePatternsManually(const QString &ignorePatterns);
Q_SIGNALS:
void fetchQueueEmpty();
@ -82,7 +84,6 @@ private:
void matchItemAgainstIgnorePatterns(SyncthingItem &item) const;
void ignoreSelectedItems(bool ignore = true);
QString computeIgnorePatternDiff() const;
SyncthingIgnores computeNewIgnorePatterns() const;
private:
using SyncthingItems = std::vector<std::unique_ptr<SyncthingItem>>;
@ -109,6 +110,7 @@ private:
QueryResult m_pendingRequest;
QFutureWatcher<LocalLookupRes> m_localItemLookup;
std::unique_ptr<SyncthingItem> m_root;
QString m_manuallyEditedIgnorePatterns;
QChar m_pathSeparator;
bool m_selectionMode;
bool m_hasIgnorePatterns;

View File

@ -63,7 +63,10 @@ set(TS_FILES translations/${META_PROJECT_NAME}_zh_CN.ts translations/${META_PROJ
set(REQUIRED_ICONS
color-profile
dialog-ok
dialog-cancel
document-open
document-edit
preferences-other
process-stop
list-add

View File

@ -4,6 +4,7 @@
#include <syncthingconnector/syncthingconnection.h>
#include <syncthingconnector/syncthingdir.h>
#include <syncthingmodel/colors.h>
#include <syncthingmodel/syncthingfilemodel.h>
// use meta-data of syncthingtray application here
@ -12,6 +13,7 @@
#include <QClipboard>
#include <QDialog>
#include <QFont>
#include <QFontDatabase>
#include <QGuiApplication>
#include <QIcon>
#include <QLabel>
@ -30,6 +32,29 @@ using namespace Data;
namespace QtGui {
DiffHighlighter::DiffHighlighter(QTextDocument *parent)
: QSyntaxHighlighter(parent)
, m_enabled(true)
{
auto font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
m_baseFormat.setFont(font);
font.setBold(true);
m_addedFormat.setFont(font);
m_addedFormat.setForeground(Colors::green(true));
m_deletedFormat.setFont(font);
m_deletedFormat.setForeground(Colors::red(true));
}
void DiffHighlighter::highlightBlock(const QString &text)
{
if (text.startsWith(QChar('-'))) {
setFormat(0, static_cast<int>(text.size()), QColor(Qt::red));
} else if (text.startsWith(QChar('+'))) {
setFormat(0, static_cast<int>(text.size()), QColor(Qt::green));
}
}
static void setupOwnDeviceIdDialog(Data::SyncthingConnection &connection, int size, QWidget *dlg)
{
dlg->setWindowTitle(QCoreApplication::translate("QtGui::OtherDialogs", "Own device ID") + QStringLiteral(" - " APP_NAME));
@ -141,14 +166,44 @@ QDialog *browseRemoteFilesDialog(Data::SyncthingConnection &connection, const Da
messageBox.exec();
});
QObject::connect(
model, &Data::SyncthingFileModel::actionNeedsConfirmation, dlg, [](QAction *action, const QString &message, const QString &details) {
auto messageBox
= QMessageBox(QMessageBox::Warning, QStringLiteral("Confirm action - " APP_NAME), message, QMessageBox::Yes | QMessageBox::No);
messageBox.setDetailedText(details);
model, &Data::SyncthingFileModel::actionNeedsConfirmation, dlg, [model](QAction *action, const QString &message, const QString &details) {
auto messageBox = TextViewDialog(QStringLiteral("Confirm action - " APP_NAME));
auto *const browser = messageBox.browser();
auto *const highlighter = new DiffHighlighter(browser->document());
auto *const buttonLayout = new QHBoxLayout(&messageBox);
auto *const editBtn = new QPushButton(
QIcon::fromTheme(QStringLiteral("document-edit")), QCoreApplication::translate("QtGui::OtherDialogs", "Edit manually"), &messageBox);
auto *const yesBtn = new QPushButton(
QIcon::fromTheme(QStringLiteral("dialog-ok")), QCoreApplication::translate("QtGui::OtherDialogs", "Apply"), &messageBox);
auto *const noBtn = new QPushButton(
QIcon::fromTheme(QStringLiteral("dialog-cancel")), QCoreApplication::translate("QtGui::OtherDialogs", "No"), &messageBox);
QObject::connect(yesBtn, &QAbstractButton::clicked, &messageBox, [&messageBox] { messageBox.accept(); });
QObject::connect(noBtn, &QAbstractButton::clicked, &messageBox, [&messageBox] { messageBox.reject(); });
QObject::connect(editBtn, &QAbstractButton::clicked, &messageBox, [&messageBox, model, editBtn, highlighter] {
auto *const b = messageBox.browser();
editBtn->hide();
b->clear();
highlighter->setEnabled(false);
b->setText(model->computeNewIgnorePatterns().ignore.join(QChar('\n')));
b->setReadOnly(false);
b->setUndoRedoEnabled(true);
});
buttonLayout->addWidget(editBtn);
buttonLayout->addStretch();
buttonLayout->addWidget(yesBtn);
buttonLayout->addWidget(noBtn);
browser->setText(details);
messageBox.layout()->insertWidget(0, new QLabel(message, &messageBox));
messageBox.layout()->addLayout(buttonLayout);
messageBox.setAttribute(Qt::WA_DeleteOnClose, false);
action->setParent(&messageBox);
if (messageBox.exec() == QMessageBox::Yes) {
action->trigger();
if (messageBox.exec() != QDialog::Accepted) {
return;
}
if (!browser->isReadOnly()) {
model->editIgnorePatternsManually(browser->toPlainText());
}
action->trigger();
});
// setup layout

View File

@ -5,6 +5,9 @@
#include <QtGlobal>
#include <QSyntaxHighlighter>
#include <QTextCharFormat>
QT_FORWARD_DECLARE_CLASS(QDialog)
QT_FORWARD_DECLARE_CLASS(QWidget)
@ -16,6 +19,32 @@ struct SyncthingDir;
namespace QtGui {
class TextViewDialog;
class SYNCTHINGWIDGETS_EXPORT DiffHighlighter : public QSyntaxHighlighter {
Q_OBJECT
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled)
public:
explicit DiffHighlighter(QTextDocument *parent = nullptr);
bool isEnabled() const
{
return m_enabled;
}
void setEnabled(bool enabled)
{
if (enabled != m_enabled) {
m_enabled = enabled;
rehighlight();
}
}
protected:
void highlightBlock(const QString &text) override;
private:
QTextCharFormat m_baseFormat, m_addedFormat, m_deletedFormat;
bool m_enabled;
};
SYNCTHINGWIDGETS_EXPORT QDialog *ownDeviceIdDialog(Data::SyncthingConnection &connection);
SYNCTHINGWIDGETS_EXPORT QWidget *ownDeviceIdWidget(Data::SyncthingConnection &connection, int size, QWidget *parent = nullptr);
SYNCTHINGWIDGETS_EXPORT QDialog *browseRemoteFilesDialog(