diff --git a/syncthingmodel/syncthingfilemodel.cpp b/syncthingmodel/syncthingfilemodel.cpp index 3e3ee050..f2da6d29 100644 --- a/syncthingmodel/syncthingfilemodel.cpp +++ b/syncthingmodel/syncthingfilemodel.cpp @@ -773,6 +773,7 @@ QList SyncthingFileModel::selectionActions() if (!m_selectionMode) { auto *const startSelectionAction = new QAction(tr("Select items to sync/ignore"), this); startSelectionAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-select"))); + startSelectionAction->setData(QStringLiteral("primary")); connect(startSelectionAction, &QAction::triggered, this, [this] { setSelectionModeEnabled(true); }); res << startSelectionAction; @@ -789,6 +790,7 @@ QList SyncthingFileModel::selectionActions() } else { auto *const discardAction = new QAction(tr("Uncheck all and discard staged changes"), this); discardAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo"))); + discardAction->setData(QStringLiteral("primary")); connect(discardAction, &QAction::triggered, this, [this] { if (const auto rootIndex = index(0, 0); rootIndex.isValid()) { setCheckState(index(0, 0), Qt::Unchecked, true); @@ -802,18 +804,21 @@ QList SyncthingFileModel::selectionActions() auto *const ignoreSelectedAction = new QAction(tr("Ignore checked items (and their children)"), this); ignoreSelectedAction->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); + ignoreSelectedAction->setData(QStringLiteral("ignore")); connect(ignoreSelectedAction, &QAction::triggered, this, [this]() { ignoreSelectedItems(); }); res << ignoreSelectedAction; if (!m_localPath.isEmpty()) { auto *const ignoreAndDeleteSelectedAction = new QAction(tr("Ignore and locally delete checked items (and their children)"), this); ignoreAndDeleteSelectedAction->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); + ignoreAndDeleteSelectedAction->setData(QStringLiteral("ignore")); connect(ignoreAndDeleteSelectedAction, &QAction::triggered, this, [this]() { ignoreSelectedItems(true, true); }); res << ignoreAndDeleteSelectedAction; } auto *const includeSelectedAction = new QAction(tr("Include checked items (and their children)"), this); includeSelectedAction->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); + includeSelectedAction->setData(QStringLiteral("include")); connect(includeSelectedAction, &QAction::triggered, this, [this]() { ignoreSelectedItems(false); }); res << includeSelectedAction; } @@ -821,6 +826,7 @@ QList SyncthingFileModel::selectionActions() auto *const ignoreByDefaultAction = new QAction(m_isIgnoringAllByDefault ? tr("Include all items by default") : tr("Ignore all items by default"), this); ignoreByDefaultAction->setIcon(QIcon::fromTheme(QStringLiteral("question"))); + ignoreByDefaultAction->setData(m_isIgnoringAllByDefault ? QStringLiteral("include") : QStringLiteral("ignore")); connect(ignoreByDefaultAction, &QAction::triggered, this, [this, isIgnoringAllByDefault = m_isIgnoringAllByDefault]() { auto &lastLine = m_stagedChanges[m_presentIgnorePatterns.size() - 1]; if (isIgnoringAllByDefault) { @@ -879,6 +885,7 @@ QList SyncthingFileModel::selectionActions() if (!m_stagedChanges.isEmpty() || !m_stagedLocalFileDeletions.isEmpty()) { auto *const applyStagedChangesAction = new RejectableAction(tr("Review and apply staged changes"), this); applyStagedChangesAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))); + applyStagedChangesAction->setData(QStringLiteral("primary")); connect(applyStagedChangesAction, &QAction::triggered, this, [this, action = applyStagedChangesAction]() mutable { // allow user to review changes before applying them if (action->needsConfirmation) { @@ -929,7 +936,7 @@ QList SyncthingFileModel::selectionActions() if (failedDeletions.isEmpty()) { emit notification(QStringLiteral("info"), tr("Ignore patterns have been changed.")); } else { - emit notification(QStringLiteral("info"), tr("Ignore patterns have been changed but the following local files could not be deleted:\n") + failedDeletions.join(QChar('\n'))); + emit notification(QStringLiteral("error"), tr("Ignore patterns have been changed but the following local files could not be deleted:\n") + failedDeletions.join(QChar('\n'))); } emit hasStagedChangesChanged(hasStagedChanges()); }); diff --git a/syncthingwidgets/misc/otherdialogs.cpp b/syncthingwidgets/misc/otherdialogs.cpp index 7c0e271c..842d30be 100644 --- a/syncthingwidgets/misc/otherdialogs.cpp +++ b/syncthingwidgets/misc/otherdialogs.cpp @@ -34,6 +34,8 @@ #include #include #include +#include +#include #include using namespace std; @@ -95,6 +97,25 @@ QWidget *ownDeviceIdWidget(Data::SyncthingConnection &connection, int size, QWid return widget; } +/// \cond +static QToolButton *initToolButton(const QString &text, QAction *action, QWidget *parent) +{ + auto btn = new QToolButton(parent); + btn->setText(text); + btn->setIcon(action->icon()); + btn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + btn->setPopupMode(QToolButton::InstantPopup); + btn->addAction(action); + return btn; +} + +static QString firstWord(const QString &text) +{ + const auto spacePos = text.indexOf(QChar(' ')); + return spacePos > 0 ? text.mid(0, spacePos) : text; +} +/// \endcond + QDialog *browseRemoteFilesDialog(Data::SyncthingConnection &connection, const Data::SyncthingDir &dir, QWidget *parent) { auto dlg = new QDialog(parent); @@ -108,6 +129,71 @@ QDialog *browseRemoteFilesDialog(Data::SyncthingConnection &connection, const Da auto model = new Data::SyncthingFileModel(connection, dir, view); view->setModel(model); + // setup toolbar + auto toolBar = new QToolBar(dlg); + toolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + auto updateToolBarActions = [toolBar, model] { + const auto existingActions = toolBar->actions(); + for (auto *const action : existingActions) { + toolBar->removeAction(action); + } + const auto newActions = model->selectionActions(); + QToolButton *primaryBtn = nullptr, *ignoreBtn = nullptr, *includeBtn = nullptr, *otherBtn = nullptr; + QStringList primaryTexts; + QAction *firstPrimaryAction = nullptr; + for (auto *const action : newActions) { + const auto category = action->data().toString(); + if (category == QStringLiteral("primary")) { + if (!firstPrimaryAction) { + firstPrimaryAction = action; + continue; + } else if (!primaryBtn) { + primaryBtn = initToolButton(QString(), firstPrimaryAction, toolBar); + primaryTexts << firstWord(firstPrimaryAction->text()); + } + primaryBtn->addAction(action); + primaryTexts << firstWord(action->text()); + } else if (category == QStringLiteral("ignore")) { + if (ignoreBtn) { + ignoreBtn->addAction(action); + } else { + ignoreBtn = initToolButton(QCoreApplication::translate("QtGui::OtherDialogs", "Ignore"), action, toolBar); + } + } else if (category == QStringLiteral("include")) { + if (includeBtn) { + includeBtn->addAction(action); + } else { + includeBtn = initToolButton(QCoreApplication::translate("QtGui::OtherDialogs", "Include"), action, toolBar); + } + } else { + if (otherBtn) { + otherBtn->addAction(action); + } else { + otherBtn = initToolButton(QCoreApplication::translate("QtGui::OtherDialogs", "Other"), action, toolBar); + } + } + } + if (firstPrimaryAction) { + if (primaryBtn) { + primaryBtn->setText(primaryTexts.join(QChar('/'))); + toolBar->addWidget(primaryBtn); + } else { + toolBar->addAction(firstPrimaryAction); + } + } + if (ignoreBtn) { + toolBar->addWidget(ignoreBtn); + } + if (includeBtn) { + toolBar->addWidget(includeBtn); + } + if (otherBtn) { + toolBar->addWidget(otherBtn); + } + }; + updateToolBarActions(); + QObject::connect(model, &Data::SyncthingFileModel::selectionActionsChanged, toolBar, std::move(updateToolBarActions)); + // setup context menu view->setContextMenuPolicy(Qt::CustomContextMenu); QObject::connect(view, &QTreeView::customContextMenuRequested, view, [view, model](const QPoint &pos) { @@ -177,6 +263,7 @@ QDialog *browseRemoteFilesDialog(Data::SyncthingConnection &connection, const Da 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); + auto widgetIndex = 0; 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] { @@ -193,7 +280,7 @@ QDialog *browseRemoteFilesDialog(Data::SyncthingConnection &connection, const Da buttonLayout->addWidget(yesBtn); buttonLayout->addWidget(noBtn); browser->setText(details); - messageBox.layout()->insertWidget(0, new QLabel(message, &messageBox)); + messageBox.layout()->insertWidget(widgetIndex++, new QLabel(message, &messageBox)); auto *deletionModel = localDeletions.isEmpty() ? nullptr : new QtUtilities::ChecklistModel(&messageBox); if (deletionModel) { auto *deletionView = new QListView(&messageBox); @@ -204,9 +291,9 @@ QDialog *browseRemoteFilesDialog(Data::SyncthingConnection &connection, const Da } deletionModel->setItems(deletionItems); deletionView->setModel(deletionModel); - messageBox.layout()->insertWidget(1, new QLabel(QCoreApplication::translate("QtGui::OtherDialogs", "Deletion of the following local files:"), &messageBox)); - messageBox.layout()->insertWidget(2, deletionView); - messageBox.layout()->insertWidget(3, new QLabel(QCoreApplication::translate("QtGui::OtherDialogs", "Changes to ignore patterns:"), &messageBox)); + messageBox.layout()->insertWidget(widgetIndex++, new QLabel(QCoreApplication::translate("QtGui::OtherDialogs", "Deletion of the following local files:"), &messageBox)); + messageBox.layout()->insertWidget(widgetIndex++, deletionView); + messageBox.layout()->insertWidget(widgetIndex++, new QLabel(QCoreApplication::translate("QtGui::OtherDialogs", "Changes to ignore patterns:"), &messageBox)); } messageBox.layout()->addLayout(buttonLayout); messageBox.setAttribute(Qt::WA_DeleteOnClose, false); @@ -235,6 +322,7 @@ QDialog *browseRemoteFilesDialog(Data::SyncthingConnection &connection, const Da layout->setAlignment(Qt::AlignCenter); layout->setSpacing(0); layout->setContentsMargins(QMargins()); + layout->addWidget(toolBar); layout->addWidget(view); dlg->setLayout(layout);