diff --git a/syncthingmodel/syncthingfilemodel.cpp b/syncthingmodel/syncthingfilemodel.cpp index 3bdc70c..8547777 100644 --- a/syncthingmodel/syncthingfilemodel.cpp +++ b/syncthingmodel/syncthingfilemodel.cpp @@ -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 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; } diff --git a/syncthingmodel/syncthingfilemodel.h b/syncthingmodel/syncthingfilemodel.h index 1dab206..d4cf5d1 100644 --- a/syncthingmodel/syncthingfilemodel.h +++ b/syncthingmodel/syncthingfilemodel.h @@ -62,6 +62,8 @@ public: Q_INVOKABLE QString path(const QModelIndex &path) const; bool hasIgnorePatterns() const; const std::vector &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>; @@ -109,6 +110,7 @@ private: QueryResult m_pendingRequest; QFutureWatcher m_localItemLookup; std::unique_ptr m_root; + QString m_manuallyEditedIgnorePatterns; QChar m_pathSeparator; bool m_selectionMode; bool m_hasIgnorePatterns; diff --git a/syncthingwidgets/CMakeLists.txt b/syncthingwidgets/CMakeLists.txt index f02f8e1..7f56924 100644 --- a/syncthingwidgets/CMakeLists.txt +++ b/syncthingwidgets/CMakeLists.txt @@ -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 diff --git a/syncthingwidgets/misc/otherdialogs.cpp b/syncthingwidgets/misc/otherdialogs.cpp index fd9819d..27bcc8d 100644 --- a/syncthingwidgets/misc/otherdialogs.cpp +++ b/syncthingwidgets/misc/otherdialogs.cpp @@ -4,6 +4,7 @@ #include #include +#include #include // use meta-data of syncthingtray application here @@ -12,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -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(text.size()), QColor(Qt::red)); + } else if (text.startsWith(QChar('+'))) { + setFormat(0, static_cast(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 diff --git a/syncthingwidgets/misc/otherdialogs.h b/syncthingwidgets/misc/otherdialogs.h index 5876fbf..624abd1 100644 --- a/syncthingwidgets/misc/otherdialogs.h +++ b/syncthingwidgets/misc/otherdialogs.h @@ -5,6 +5,9 @@ #include +#include +#include + 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(