Skip to content

Commit

Permalink
Make file browser UI less clunky
Browse files Browse the repository at this point in the history
  • Loading branch information
Martchus committed Jan 31, 2025
1 parent d9602d5 commit 3290ac1
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 5 deletions.
9 changes: 8 additions & 1 deletion syncthingmodel/syncthingfilemodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,7 @@ QList<QAction *> 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;

Expand All @@ -789,6 +790,7 @@ QList<QAction *> 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);
Expand All @@ -802,25 +804,29 @@ QList<QAction *> 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;
}

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) {
Expand Down Expand Up @@ -879,6 +885,7 @@ QList<QAction *> 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) {
Expand Down Expand Up @@ -929,7 +936,7 @@ QList<QAction *> 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());
});
Expand Down
96 changes: 92 additions & 4 deletions syncthingwidgets/misc/otherdialogs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
#include <QTextDocument>
#include <QTextEdit>
#include <QTreeView>
#include <QToolBar>
#include <QToolButton>
#include <QVBoxLayout>

using namespace std;
Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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] {
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);

Expand Down

0 comments on commit 3290ac1

Please sign in to comment.