From fbd01ac1076bd8ce9a8bb7d6b5cd6737f149e536 Mon Sep 17 00:00:00 2001 From: memurats Date: Fri, 24 Oct 2025 16:49:12 +0200 Subject: [PATCH 1/2] folderview settings dialog changes --- src/gui/accountsettings.cpp | 92 ++++++-------- src/gui/accountsetupcommandlinemanager.cpp | 2 +- src/gui/folderstatusdelegate.cpp | 141 +++++++++++++++++++-- src/gui/folderstatusdelegate.h | 2 + src/gui/folderstatusmodel.cpp | 11 -- src/gui/folderstatusview.cpp | 40 ++++++ src/gui/folderstatusview.h | 4 + theme/more.svg | 8 +- 8 files changed, 225 insertions(+), 75 deletions(-) diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index f246a7026495e..b70f78bba24de 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -156,7 +156,7 @@ class MouseCursorChanger : public QObject const auto index = folderList->indexAt(pos); if (model->classify(index) == FolderStatusModel::RootFolder && (FolderStatusDelegate::errorsListRect(folderList->visualRect(index)).contains(pos) || - FolderStatusDelegate::optionsButtonRect(folderList->visualRect(index),folderList->layoutDirection()).contains(pos))) { + FolderStatusDelegate::moreRectPos(folderList->visualRect(index)).contains(pos))) { shape = Qt::PointingHandCursor; } folderList->setCursor(shape); @@ -579,8 +579,9 @@ void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index { Q_UNUSED(pos); - QMenu menu; - auto ac = menu.addAction(tr("Open folder")); + auto menu = new QMenu(this); + menu->setAttribute(Qt::WA_DeleteOnClose); + auto ac = menu->addAction(tr("Open folder")); connect(ac, &QAction::triggered, this, &AccountSettings::slotOpenCurrentLocalSubFolder); const auto fileName = _model->data(index, FolderStatusDelegate::FolderPathRole).toString(); @@ -599,7 +600,7 @@ void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index const auto isExternal = info->_isExternal; if (!isEncrypted && !isParentEncrypted && !isExternal && isTopFolder) { - ac = menu.addAction(tr("Encrypt")); + ac = menu->addAction(tr("Encrypt")); connect(ac, &QAction::triggered, [this, info] { slotMarkSubfolderEncrypted(info); }); } else { // Ignore decrypting for now since it only works with an empty folder @@ -607,16 +608,13 @@ void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index } } - ac = menu.addAction(tr("Edit Ignored Files")); - connect(ac, &QAction::triggered, this, &AccountSettings::slotEditCurrentLocalIgnoredFiles); - - ac = menu.addAction(tr("Create new folder")); + ac = menu->addAction(tr("Create new folder")); connect(ac, &QAction::triggered, this, &AccountSettings::slotOpenMakeFolderDialog); ac->setEnabled(QFile::exists(fileName)); const auto folder = info->_folder; if (folder && folder->virtualFilesEnabled()) { - auto availabilityMenu = menu.addMenu(tr("Availability")); + auto availabilityMenu = menu->addMenu(tr("Availability")); // Has '/' suffix convention for paths here but VFS and // sync engine expects no such suffix @@ -639,7 +637,23 @@ void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index connect(ac, &QAction::triggered, this, [this, folder, path] { slotSetSubFolderAvailability(folder, path, PinState::OnlineOnly); }); } - menu.exec(QCursor::pos()); + const auto highlightColor = palette().highlight().color(); + menu->setStyleSheet(QString(R"( + QMenu { + border: 1px solid black; + border-radius: 4px; + padding: 6px; + } + QMenu::item { + padding: 8px; + } + QMenu::item:selected, + QMenu::item:hover { + background-color: %1; + } + )").arg(highlightColor.name(QColor::HexRgb))); + + menu->popup(QCursor::pos()); } void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) @@ -670,16 +684,13 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) return; } - const auto menu = new QMenu(treeView); + auto menu = new QMenu(treeView); menu->setAttribute(Qt::WA_DeleteOnClose); auto ac = menu->addAction(tr("Open folder")); connect(ac, &QAction::triggered, this, &AccountSettings::slotOpenCurrentFolder); - ac = menu->addAction(tr("Edit Ignored Files")); - connect(ac, &QAction::triggered, this, &AccountSettings::slotEditCurrentIgnoredFiles); - ac = menu->addAction(tr("Create new folder")); connect(ac, &QAction::triggered, this, &AccountSettings::slotOpenMakeFolderDialog); ac->setEnabled(QFile::exists(folder->path())); @@ -720,52 +731,33 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) ac->setDisabled(Theme::instance()->enforceVirtualFilesSyncFolder()); } - if (const auto mode = bestAvailableVfsMode(); - !Theme::instance()->disableVirtualFilesSyncFolder() && - Theme::instance()->showVirtualFilesOption() && !folder->virtualFilesEnabled() && Vfs::checkAvailability(folder->path(), mode)) { - if (mode == Vfs::WindowsCfApi || ConfigFile().showExperimentalOptions()) { - ac = menu->addAction(tr("Enable virtual file support %1 …").arg(mode == Vfs::WindowsCfApi ? QString() : tr("(experimental)"))); - // TODO: remove when UX decision is made - ac->setEnabled(!Utility::isPathWindowsDrivePartitionRoot(folder->path())); - // - connect(ac, &QAction::triggered, this, &AccountSettings::slotEnableVfsCurrentFolder); - } - } + const auto highlightColor = palette().highlight().color(); + menu->setStyleSheet(QString(R"( + QMenu { + border: 1px solid black; + border-radius: 4px; + padding: 6px; + } + QMenu::item { + padding: 8px; + } + QMenu::item:selected, + QMenu::item:hover { + background-color: %1; + } + )").arg(highlightColor.name(QColor::HexRgb))); - menu->popup(treeView->mapToGlobal(pos)); + menu->popup(treeView->viewport()->mapToGlobal(pos)); } void AccountSettings::slotFolderListClicked(const QModelIndex &indx) { - if (indx.data(FolderStatusDelegate::AddButton).toBool()) { - // "Add Folder Sync Connection" - const auto treeView = _ui->_folderList; - const auto pos = treeView->mapFromGlobal(QCursor::pos()); - QStyleOptionViewItem opt; - opt.initFrom(treeView); - const auto btnRect = treeView->visualRect(indx); - const auto btnSize = treeView->itemDelegateForIndex(indx)->sizeHint(opt, indx); - const auto actual = QStyle::visualRect(opt.direction, btnRect, QRect(btnRect.topLeft(), btnSize)); - if (!actual.contains(pos)) { - return; - } - - if (indx.flags() & Qt::ItemIsEnabled) { - slotAddFolder(); - } else { - QToolTip::showText( - QCursor::pos(), - _model->data(indx, Qt::ToolTipRole).toString(), - this); - } - return; - } if (_model->classify(indx) == FolderStatusModel::RootFolder) { // tries to find if we clicked on the '...' button. const auto treeView = _ui->_folderList; const auto pos = treeView->mapFromGlobal(QCursor::pos()); - if (FolderStatusDelegate::optionsButtonRect(treeView->visualRect(indx), layoutDirection()).contains(pos)) { + if (FolderStatusDelegate::moreRectPos(treeView->visualRect(indx)).contains(pos)) { slotCustomContextMenuRequested(pos); return; } diff --git a/src/gui/accountsetupcommandlinemanager.cpp b/src/gui/accountsetupcommandlinemanager.cpp index 8d89213cfe73f..e84f70e87d174 100644 --- a/src/gui/accountsetupcommandlinemanager.cpp +++ b/src/gui/accountsetupcommandlinemanager.cpp @@ -105,6 +105,6 @@ void AccountSetupCommandLineManager::setupAccountFromCommandLine() _serverUrl.clear(); _remoteDirPath.clear(); _localDirPath.clear(); - _isVfsEnabled = true; + _isVfsEnabled = false; } } diff --git a/src/gui/folderstatusdelegate.cpp b/src/gui/folderstatusdelegate.cpp index b8453dc5cbb40..d1ae3012f1b95 100644 --- a/src/gui/folderstatusdelegate.cpp +++ b/src/gui/folderstatusdelegate.cpp @@ -14,6 +14,8 @@ #include #include +#include +#include #include #include #include @@ -83,9 +85,28 @@ QSize FolderStatusDelegate::sizeHint(const QStyleOptionViewItem &option, } } + // Make sure its at least 76 Pixel high + h = std::max(h, 76); + return {0, h}; } +QRect FolderStatusDelegate::moreRectPos(const QRect &rectIndex) +{ + if (rectIndex.isValid()) + { + constexpr int buttonWidth = 88; + constexpr int buttonHeight = 32; + constexpr int margin = 16; + + const int xMoreButton = rectIndex.right() - buttonWidth - margin; + const int yMoreButton = rectIndex.center().y() - (buttonHeight / 2); + + return QRect(xMoreButton, yMoreButton, buttonWidth, buttonHeight); + } + return {}; +} + int FolderStatusDelegate::rootFolderHeightWithoutErrors(const QFontMetrics &fm, const QFontMetrics &aliasFm) { const int aliasMargin = aliasFm.height() / 2; @@ -107,6 +128,56 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & const_cast(option).showDecorationSelected = false; } + const QModelIndex parentIndex = index.parent(); // NMC customization + { + painter->save(); + + // Verhindere das Zeichnen des "Neuer Ordner"-Buttons + if (index.data(AddButton).toBool()) { + return; + } + + const QRect leftRect(0, option.rect.y(), option.rect.x(), option.rect.height()); + + if (option.state & QStyle::State_MouseOver) { + QColor hoverColor = QApplication::palette().color(QPalette::Mid); + painter->fillRect(option.rect, hoverColor); + painter->fillRect(leftRect, hoverColor); + } + + if (option.state & QStyle::State_Selected) { + // Auswahlhintergrundfarbe abrufen + const QColor selectionColor = option.palette.color(QPalette::Highlight); + painter->fillRect(option.rect, selectionColor); + painter->fillRect(leftRect, selectionColor); + } + + const QTreeView* treeView = qobject_cast(option.widget); + if (treeView) { + QIcon leftIcon; + QSize iconSize(16, 16); + + if (!parentIndex.isValid()) { + // Wir befinden uns im Stammverzeichnis, also Icon vergrößern + iconSize = QSize(24, 24); + } + + if (index.isValid() && treeView->isExpanded(index)) { + // Das übergeordnete Element ist erweitert + leftIcon = QIcon(Theme::createColorAwareIcon(QStringLiteral(":/client/theme/NMCIcons/collapse-down.svg"))); + } else { + // Das übergeordnete Element ist nicht erweitert + leftIcon = QIcon(Theme::createColorAwareIcon(QStringLiteral(":/client/theme/NMCIcons/collapse-right.svg"))); + } + + const QPoint iconPos(leftRect.width() - iconSize.width(), + leftRect.y() + leftRect.height() / 2 - iconSize.height() / 2); + painter->drawPixmap(iconPos, leftIcon.pixmap(iconSize)); + } + + painter->restore(); + } + QStyledItemDelegate::paint(painter, option, index); auto textAlign = Qt::AlignLeft; @@ -183,19 +254,26 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & iconRect.setBottom(localPathRect.bottom()); iconRect.setWidth(iconRect.height()); - const auto nextToIcon = iconRect.right() + aliasMargin; + const auto nextToIcon = iconRect.right() + std::max(aliasMargin, 16); aliasRect.setLeft(nextToIcon); localPathRect.setLeft(nextToIcon); remotePathRect.setLeft(nextToIcon); auto optionsButtonVisualRect = optionsButtonRect(option.rect, option.direction); - statusIcon.paint( - painter, - QStyle::visualRect(option.direction, option.rect, iconRect), - Qt::AlignCenter, - syncEnabled ? QIcon::Normal : QIcon::Disabled - ); + // NMC Customization + if (!parentIndex.isValid()) { + QIcon nmcFolderIcon = QIcon(QLatin1String(":/client/theme/NMCIcons/folderLogo.svg")); + const auto nmcFolderPixmap = nmcFolderIcon.pixmap(iconSize, iconSize, QIcon::Normal); + painter->drawPixmap(QStyle::visualRect(option.direction, option.rect, iconRect).left(), iconRect.top(), nmcFolderPixmap); + + const QSize statusIconSize(24,24); + const auto statusPixmap = statusIcon.pixmap(statusIconSize.width(), statusIconSize.height(), syncEnabled ? QIcon::Normal : QIcon::Disabled); + painter->drawPixmap(QStyle::visualRect(option.direction, option.rect, iconRect).right() - statusIconSize.width() * 0.6, iconRect.bottom() - statusIconSize.height() * 0.8, statusPixmap); + } else { + const auto statusPixmap = statusIcon.pixmap(iconSize, iconSize, syncEnabled ? QIcon::Normal : QIcon::Disabled); + painter->drawPixmap(QStyle::visualRect(option.direction, option.rect, iconRect).left(), iconRect.top(), statusPixmap); + } auto palette = option.palette; @@ -267,18 +345,23 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & drawTextBox(infoTexts, QColor(0x4d, 0x4d, 0xba)); } + // NMC customization: we need these infos already here to adjust the progress bar + const QRect currentButtonRectPos = moreRectPos(option.rect); + const int nmcWidth = currentButtonRectPos.x() - nextToIcon - 8; // 8 is the margin to "More" button + // Sync File Progress Bar: Show it if syncFile is not empty. if (showProgess) { const auto fileNameTextHeight = subFm.boundingRect(tr("File")).height(); constexpr auto barHeight = 7; // same height as quota bar const auto overallWidth = option.rect.right() - aliasMargin - optionsButtonVisualRect.width() - nextToIcon; + Q_UNUSED(overallWidth); painter->save(); // Overall Progress Bar. const auto progressBarRect = QRect(nextToIcon, remotePathRect.top(), - overallWidth - 2 * margin, + nmcWidth, barHeight); QStyleOptionProgressBar progressBarOpt; @@ -310,8 +393,6 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & painter->restore(); } - painter->restore(); - { QStyleOptionToolButton btnOpt; btnOpt.state = option.state; @@ -319,12 +400,48 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & btnOpt.state |= QStyle::State_Raised; btnOpt.arrowType = Qt::NoArrow; btnOpt.subControls = QStyle::SC_ToolButton; - btnOpt.rect = optionsButtonVisualRect; + //NMC customization + btnOpt.rect = currentButtonRectPos; + //make sure the button is not too far away from the left border + btnOpt.rect.setRight(btnOpt.rect.x() + btnOpt.rect.width() + 4); + + // Create QPainterPath with rounded corners + QPainterPath path; + path.addRoundedRect(btnOpt.rect, 4, 4); // 4 ist der Radius für die abgerundeten Ecken + + // Draw border line + QPen borderPen(QColor(0, 0, 0)); // Beispiel: Schwarzer Rand + borderPen.setWidth(1); + painter->setPen(borderPen); + painter->drawPath(path); + + // Fill the rectangle + painter->fillPath(path, Qt::transparent); + + // Draw the icon in rectangle btnOpt.icon = _iconMore; const auto buttonSize = QApplication::style()->pixelMetric(QStyle::PM_ButtonIconSize); btnOpt.iconSize = QSize(buttonSize, buttonSize); - QApplication::style()->drawComplexControl(QStyle::CC_ToolButton, &btnOpt, painter); + + // Set icon position + int iconX = btnOpt.rect.x() + btnOpt.rect.width()/5; + int iconY = btnOpt.rect.y() + (btnOpt.rect.height() - btnOpt.iconSize.height()) / 2; + + painter->drawPixmap(iconX, iconY, btnOpt.icon.pixmap(btnOpt.iconSize)); + + //Add text + const QString buttonText = QCoreApplication::translate("", "MORE"); + painter->setFont(btnOpt.font); + painter->setPen(option.palette.color(QPalette::ButtonText)); + int textX = iconX + btnOpt.iconSize.width() + 10; + int textY = iconY; + int textWidth = currentButtonRectPos.x() + currentButtonRectPos.width() - textX; + int textHeight = btnOpt.fontMetrics.height(); + + painter->drawText(QRect(textX, textY, textWidth, textHeight), Qt::AlignLeft | Qt::AlignVCenter, buttonText); } + + painter->restore(); } bool FolderStatusDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, diff --git a/src/gui/folderstatusdelegate.h b/src/gui/folderstatusdelegate.h index 4bb2bfe0c0a0d..2faccca0cefd9 100644 --- a/src/gui/folderstatusdelegate.h +++ b/src/gui/folderstatusdelegate.h @@ -52,6 +52,7 @@ class FolderStatusDelegate : public QStyledItemDelegate static QRect addButtonRect(QRect within, Qt::LayoutDirection direction); static QRect errorsListRect(QRect within); static int rootFolderHeightWithoutErrors(const QFontMetrics &fm, const QFontMetrics &aliasFm); + static QRect moreRectPos(const QRect &rectIndex); public slots: void slotStyleChanged(); @@ -63,6 +64,7 @@ public slots: QPersistentModelIndex _pressedIndex; QIcon _iconMore; + QStyleOptionViewItem _newOption; }; } // namespace OCC diff --git a/src/gui/folderstatusmodel.cpp b/src/gui/folderstatusmodel.cpp index 42a27bf6e6d2f..5b55b3cb6cf01 100644 --- a/src/gui/folderstatusmodel.cpp +++ b/src/gui/folderstatusmodel.cpp @@ -44,14 +44,6 @@ FolderStatusModel::FolderStatusModel(QObject *parent) FolderStatusModel::~FolderStatusModel() = default; -static bool sortByFolderHeader(const FolderStatusModel::SubFolderInfo &lhs, const FolderStatusModel::SubFolderInfo &rhs) -{ - return QString::compare(lhs._folder->shortGuiRemotePathOrAppName(), - rhs._folder->shortGuiRemotePathOrAppName(), - Qt::CaseInsensitive) - < 0; -} - void FolderStatusModel::setAccountState(const AccountState *accountState) { connect(accountState->account()->e2e(), &OCC::ClientSideEncryption::initializationFinished, this, &FolderStatusModel::e2eInitializationFinished); @@ -84,9 +76,6 @@ void FolderStatusModel::setAccountState(const AccountState *accountState) connect(folder, &Folder::newBigFolderDiscovered, this, &FolderStatusModel::slotNewBigFolder, Qt::UniqueConnection); } - // Sort by header text - std::sort(_folders.begin(), _folders.end(), sortByFolderHeader); - // Set the root _pathIdx after the sorting for (auto i = 0; i < _folders.size(); ++i) { _folders[i]._pathIdx << i; diff --git a/src/gui/folderstatusview.cpp b/src/gui/folderstatusview.cpp index 86d16e6fc1238..2b95af55dfc21 100644 --- a/src/gui/folderstatusview.cpp +++ b/src/gui/folderstatusview.cpp @@ -6,10 +6,17 @@ #include "folderstatusview.h" #include "folderstatusdelegate.h" +#include +#include +#include +#include +#include + namespace OCC { FolderStatusView::FolderStatusView(QWidget *parent) : QTreeView(parent) { + this->setStyleSheet("QTreeView { border: none; }"); } QModelIndex FolderStatusView::indexAt(const QPoint &point) const @@ -30,4 +37,37 @@ QRect FolderStatusView::visualRect(const QModelIndex &index) const return rect; } +void FolderStatusView::drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const { + Q_UNUSED(painter) + Q_UNUSED(rect) + Q_UNUSED(index) + + // Empty function: This overrides the default behavior to remove the left column + // containing the collapse and expand icons. Instead, this is handled + // in FolderStatusDelegate::paint(). +} + +void FolderStatusView::paintEvent(QPaintEvent *event) +{ + // Paints rounded corners since QTreeView does not support setting this via stylesheets. + QPainter painter(viewport()); + painter.setRenderHint(QPainter::Antialiasing); + + const int radius = 4; + + QRect rect(0, 0, width(), height()); + QPainterPath path; + path.addRoundedRect(rect, radius, radius); + + // Ensure the background color is consistent with the application theme + QPalette palette = this->palette(); + QColor backgroundColor = palette.color(QPalette::Window); + QColor baseColor = palette.color(QPalette::Base); + + painter.fillRect(rect, backgroundColor); + painter.fillPath(path, baseColor); + + QTreeView::paintEvent(event); +} + } // namespace OCC diff --git a/src/gui/folderstatusview.h b/src/gui/folderstatusview.h index 6c66853001cab..a37071936bffd 100644 --- a/src/gui/folderstatusview.h +++ b/src/gui/folderstatusview.h @@ -23,6 +23,10 @@ class FolderStatusView : public QTreeView [[nodiscard]] QModelIndex indexAt(const QPoint &point) const override; [[nodiscard]] QRect visualRect(const QModelIndex &index) const override; + +protected: + void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const override; + void paintEvent(QPaintEvent *event) override; }; } // namespace OCC diff --git a/theme/more.svg b/theme/more.svg index 164a1b70345f8..e2f50c80c7483 100644 --- a/theme/more.svg +++ b/theme/more.svg @@ -1 +1,7 @@ - \ No newline at end of file + + + /svg/icon/action/more/default + + + + \ No newline at end of file From 6bd4e89e9312e8da10b798d6f67f88df9bdd5abf Mon Sep 17 00:00:00 2001 From: memurats Date: Mon, 27 Oct 2025 13:11:47 +0100 Subject: [PATCH 2/2] declare iconSize --- src/gui/folderstatusdelegate.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/folderstatusdelegate.cpp b/src/gui/folderstatusdelegate.cpp index d1ae3012f1b95..265a0561ee4c1 100644 --- a/src/gui/folderstatusdelegate.cpp +++ b/src/gui/folderstatusdelegate.cpp @@ -259,6 +259,8 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & localPathRect.setLeft(nextToIcon); remotePathRect.setLeft(nextToIcon); + const auto iconSize = iconRect.width(); + auto optionsButtonVisualRect = optionsButtonRect(option.rect, option.direction); // NMC Customization