diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index a4ae1f0775dc7..bd441fc5f6018 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -159,7 +159,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); @@ -608,8 +608,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(); @@ -628,7 +629,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 @@ -636,16 +637,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 @@ -668,7 +666,26 @@ 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 { + background-color: palette(window); + color: palette(window-text); + border: 1px solid black; + border-radius: 4px; + padding: 6px; + } + QMenu::item { + background-color: transparent; + 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) @@ -699,16 +716,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())); @@ -749,6 +763,8 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) ac->setDisabled(Theme::instance()->enforceVirtualFilesSyncFolder()); } + const auto highlightColor = palette().highlight().color(); + if (const auto mode = bestAvailableVfsMode(); !Theme::instance()->disableVirtualFilesSyncFolder() && Theme::instance()->showVirtualFilesOption() && @@ -764,40 +780,34 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) } } + menu->setStyleSheet(QString(R"( + QMenu { + background-color: palette(window); + color: palette(window-text); + border: 1px solid black; + border-radius: 4px; + padding: 6px; + } + QMenu::item { + background-color: transparent; + 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 846502fa97902..ae24c6a564caf 100644 --- a/src/gui/folderstatusdelegate.cpp +++ b/src/gui/folderstatusdelegate.cpp @@ -14,6 +14,8 @@ #include #include +#include +#include #include #include #include @@ -57,13 +59,12 @@ QSize FolderStatusDelegate::sizeHint(const QStyleOptionViewItem &option, auto classif = dynamic_cast(index.model())->classify(index); if (classif == FolderStatusModel::AddButton) { - const int margins = aliasFm.height(); // same as 2*aliasMargin of paint + const int margins = aliasFm.height(); QFontMetrics fm(qApp->font("QPushButton")); QStyleOptionButton opt; static_cast(opt) = option; opt.text = addFolderText(); - return QApplication::style()->sizeFromContents( QStyle::CT_PushButton, &opt, fm.size(Qt::TextSingleLine, opt.text)) + - QSize(0, margins); + return QApplication::style()->sizeFromContents( QStyle::CT_PushButton, &opt, fm.size(Qt::TextSingleLine, opt.text)) + QSize(0, margins); } if (classif != FolderStatusModel::RootFolder) { @@ -104,9 +105,28 @@ QSize FolderStatusDelegate::sizeHint(const QStyleOptionViewItem &option, h += margin + btnSize.height(); } + // 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 = 20; + + 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; @@ -125,7 +145,49 @@ int FolderStatusDelegate::rootFolderHeightWithoutErrors(const QFontMetrics &fm, void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (index.data(AddButton).toBool()) { - const_cast(option).showDecorationSelected = false; + return; + } + + const QModelIndex parentIndex = index.parent(); + + { + painter->save(); + + const QRect leftRect(0, option.rect.y(), option.rect.x(), option.rect.height()); + + if (option.state & QStyle::State_MouseOver) { + const QColor hoverColor = QApplication::palette().color(QPalette::Mid); + painter->fillRect(option.rect, hoverColor); + painter->fillRect(leftRect, hoverColor); + } + + if (option.state & QStyle::State_Selected) { + const QColor selectionColor = option.palette.color(QPalette::Highlight); + painter->fillRect(option.rect, selectionColor); + painter->fillRect(leftRect, selectionColor); + } + + const auto *treeView = qobject_cast(option.widget); + if (treeView) { + QSize iconSize(16, 16); + + if (!parentIndex.isValid()) { + iconSize = QSize(24, 24); + } + + const auto iconPath = treeView->isExpanded(index) + ? QStringLiteral(":/client/theme/NMCIcons/collapse-down.svg") + : QStringLiteral(":/client/theme/NMCIcons/collapse-right.svg"); + + const QIcon leftIcon = Theme::createColorAwareIcon(iconPath); + + 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); @@ -145,23 +207,6 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & const auto aliasMargin = aliasFm.height() / 2; const auto margin = subFm.height() / 4; - if (index.data(AddButton).toBool()) { - QStyleOptionButton opt; - static_cast(opt) = option; - if (opt.state & QStyle::State_Enabled && opt.state & QStyle::State_MouseOver && index == _pressedIndex) { - opt.state |= QStyle::State_Sunken; - } else { - opt.state |= QStyle::State_Raised; - } - opt.text = addFolderText(); - opt.rect = addButtonRect(option.rect, option.direction); - painter->save(); - painter->setFont(qApp->font("QPushButton")); - QApplication::style()->drawControl(QStyle::CE_PushButton, &opt, painter, option.widget); - painter->restore(); - return; - } - if (dynamic_cast(index.model())->classify(index) != FolderStatusModel::RootFolder) { return; } @@ -211,14 +256,23 @@ 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); - 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; @@ -331,18 +385,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; @@ -374,8 +433,6 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & painter->restore(); } - painter->restore(); - { QStyleOptionToolButton btnOpt; btnOpt.state = option.state; @@ -383,12 +440,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 c8924471e21a7..1415b163985b6 100644 --- a/src/gui/folderstatusdelegate.h +++ b/src/gui/folderstatusdelegate.h @@ -54,6 +54,7 @@ class FolderStatusDelegate : public QStyledItemDelegate static QRect errorsListRect(QRect within); static QRect sandboxButtonRect(QRect errorBannerRect); static int rootFolderHeightWithoutErrors(const QFontMetrics &fm, const QFontMetrics &aliasFm); + static QRect moreRectPos(const QRect &rectIndex); public slots: void slotStyleChanged(); @@ -65,6 +66,7 @@ public slots: QPersistentModelIndex _pressedIndex; QIcon _iconMore; + QStyleOptionViewItem _newOption; }; } // namespace OCC diff --git a/src/gui/folderstatusmodel.cpp b/src/gui/folderstatusmodel.cpp index 5c3504674f7bb..9e7caa40c9a8f 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 3e06244acf00f..164096433f092 100644 --- a/src/gui/folderstatusview.cpp +++ b/src/gui/folderstatusview.cpp @@ -9,10 +9,17 @@ #include #include +#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 @@ -33,4 +40,28 @@ 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) +{ + QPainter painter(viewport()); + painter.setRenderHint(QPainter::Antialiasing); + + const QRect rect = viewport()->rect(); + QPainterPath path; + path.addRoundedRect(rect, 4, 4); + + painter.fillPath(path, Qt::transparent); + + 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