From 8181ac0522644f7bbfd14bc9939c6d307a3add01 Mon Sep 17 00:00:00 2001 From: Hannah von Reth Date: Tue, 20 Feb 2024 09:52:55 +0100 Subject: [PATCH] wip qml --- CMakeLists.txt | 2 +- src/gui/CMakeLists.txt | 16 +- src/gui/accountsettings.cpp | 49 ++++- src/gui/accountsettings.h | 9 +- src/gui/accountsettings.ui | 27 ++- src/gui/folderstatusdelegate.cpp | 321 ------------------------------- src/gui/folderstatusdelegate.h | 57 ------ src/gui/folderstatusmodel.cpp | 94 +++++---- src/gui/folderstatusmodel.h | 14 +- src/gui/main.cpp | 6 + src/gui/models/models.h | 3 + src/gui/owncloudgui.cpp | 2 + src/gui/qml/tree.qml | 113 +++++++++++ src/libsync/graphapi/space.cpp | 26 +-- src/resources/resources.cpp | 16 +- src/resources/resources.h | 2 + 16 files changed, 297 insertions(+), 460 deletions(-) delete mode 100644 src/gui/folderstatusdelegate.cpp delete mode 100644 src/gui/folderstatusdelegate.h create mode 100644 src/gui/qml/tree.qml diff --git a/CMakeLists.txt b/CMakeLists.txt index 460d15ef64e..aa174625de0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) find_package(QT 6.2 NAMES Qt6 COMPONENTS Core REQUIRED) -find_package(Qt6 COMPONENTS Core Concurrent Network Widgets Xml REQUIRED) +find_package(Qt6 COMPONENTS Core Concurrent Network Widgets Xml Quick QuickWidgets REQUIRED) find_package(Qt6LinguistTools REQUIRED) get_target_property (QT_QMAKE_EXECUTABLE Qt::qmake IMPORTED_LOCATION) message(STATUS "Using Qt ${QT_VERSION} (${QT_QMAKE_EXECUTABLE})") diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index d6809f9c14d..0045779ff75 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -1,4 +1,5 @@ include(ECMAddAppIcon) +include(ECMQmlModule) find_package(KDSingleApplication-qt6 1.0.0 REQUIRED) @@ -37,7 +38,6 @@ set(client_SRCS folder.cpp folderman.cpp folderstatusmodel.cpp - folderstatusdelegate.cpp folderwatcher.cpp generalsettings.cpp ignorelisteditor.cpp @@ -102,15 +102,25 @@ add_subdirectory(loginrequireddialog) add_library(owncloudCore STATIC ${final_src}) set_target_properties(owncloudCore PROPERTIES AUTOUIC ON AUTORCC ON) +# for the generated qml module +target_include_directories(owncloudCore PRIVATE models) target_link_libraries(owncloudCore PUBLIC - Qt::Widgets Qt::Network Qt::Xml + Qt::Widgets Qt::Network Qt::Xml Qt::Quick Qt::QuickWidgets newwizard folderwizard spaces loginrequireddialog libsync Qt6Keychain::Qt6Keychain ) apply_common_target_settings(owncloudCore) +ecm_add_qml_module (owncloudCore + URI org.ownCloud.qmlcomponents + VERSION 1.0 + NAMESPACE OCC + # TODO: main.cpp: qml_register_types_org_ownCloud_qmlcomponents + QT_NO_PLUGIN + QML_FILES qml/tree.qml +) add_subdirectory(spaces) @@ -159,7 +169,7 @@ set_target_properties(owncloud PROPERTIES AUTORCC ON ) apply_common_target_settings(owncloud) -target_link_libraries(owncloud owncloudCore owncloudResources KDAB::kdsingleapplication ) +target_link_libraries(owncloud PUBLIC owncloudCore owncloudResources KDAB::kdsingleapplication ) MESSAGE(STATUS "OWNCLOUD_SIDEBAR_ICONS: ${APPLICATION_ICON_NAME}: ${OWNCLOUD_SIDEBAR_ICONS}") diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index 3a2f7069092..82519c5b608 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -54,6 +54,8 @@ #include #include +#include + namespace { constexpr auto modalWidgetStretchedMarginC = 50; @@ -64,6 +66,19 @@ namespace OCC { Q_LOGGING_CATEGORY(lcAccountSettings, "gui.account.settings", QtInfoMsg) +class NetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory +{ +public: + NetworkAccessManagerFactory(Account *acc) + : _acc(acc) + { + } + + inline QNetworkAccessManager *create(QObject *parent) override { return _acc->accessManager(); } + + Account *_acc; +}; + AccountSettings::AccountSettings(const AccountStatePtr &accountState, QWidget *parent) : QWidget(parent) , ui(new Ui::AccountSettings) @@ -84,14 +99,31 @@ AccountSettings::AccountSettings(const AccountStatePtr &accountState, QWidget *p _sortModel = weightedModel; - ui->_folderList->setModel(_sortModel); + const QUrl src = QUrl::fromLocalFile(QStringLiteral("C:\\CraftRoot\\download\\git\\owncloud\\owncloud-client\\src\\gui\\qml\\tree.qml")); + // const QUrl src = QUrl::fromLocalFile(QStringLiteral(":/qt/qml/org/ownCloud/qmlcomponents/qml/tree.qml")); + ui->quickWidget->rootContext()->setContextProperty(QStringLiteral("ctx"), this); + ui->quickWidget->setSource(src); + ui->quickWidget->engine()->setNetworkAccessManagerFactory(new NetworkAccessManagerFactory(_accountState->account().get())); + ui->quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); + for (const auto &e : ui->quickWidget->errors()) { + qDebug() << "aaaaaaaaaaa" << e; + } - ui->_folderList->setItemDelegate(_delegate); + connect(ui->reload, &QPushButton::clicked, this, [src, this] { + ui->quickWidget->setSource(QUrl()); + ui->quickWidget->engine()->clearComponentCache(); + ui->quickWidget->setSource(src); + for (const auto &e : ui->quickWidget->errors()) { + qDebug() << "aaaaaaaaaaa" << e; + } + }); createAccountToolbox(); +#if 0 connect(ui->_folderList, &QWidget::customContextMenuRequested, this, &AccountSettings::slotCustomContextMenuRequested); connect(ui->_folderList, &QAbstractItemView::clicked, this, &AccountSettings::slotFolderListClicked); +#endif QAction *syncNowAction = new QAction(this); connect(syncNowAction, &QAction::triggered, this, &AccountSettings::slotScheduleCurrentFolder); addAction(syncNowAction); @@ -157,8 +189,11 @@ void AccountSettings::createAccountToolbox() Folder *AccountSettings::selectedFolder() const { + return {}; +#if 0 const QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); return _model->folder(_sortModel->mapToSource(selected)); +#endif } void AccountSettings::slotToggleSignInState() @@ -172,6 +207,7 @@ void AccountSettings::slotToggleSignInState() void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) { +#if 0 auto *tv = ui->_folderList; QModelIndex index = tv->indexAt(pos); if (!index.isValid()) { @@ -278,10 +314,12 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) } else { menu->deleteLater(); } +#endif } void AccountSettings::slotFolderListClicked(const QModelIndex &indx) { +#if 0 // tries to find if we clicked on the '...' button. auto *tv = ui->_folderList; const auto pos = tv->mapFromGlobal(QCursor::pos()); @@ -294,6 +332,7 @@ void AccountSettings::slotFolderListClicked(const QModelIndex &indx) emit showIssuesList(); return; } +#endif } void AccountSettings::showSelectiveSyncDialog(Folder *folder) @@ -352,6 +391,7 @@ void AccountSettings::slotFolderWizardAccepted() void AccountSettings::slotRemoveCurrentFolder() { +#if 0 auto folder = selectedFolder(); QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); if (selected.isValid() && folder) { @@ -382,10 +422,12 @@ void AccountSettings::slotRemoveCurrentFolder() }); messageBox->open(); } +#endif } void AccountSettings::slotEnableVfsCurrentFolder() { +#if 0 QPointer folder = selectedFolder(); QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); if (!selected.isValid() || !folder) { @@ -405,10 +447,12 @@ void AccountSettings::slotEnableVfsCurrentFolder() ui->_folderList->doItemsLayout(); } +#endif } void AccountSettings::slotDisableVfsCurrentFolder() { +#if 0 QPointer folder = selectedFolder(); QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); if (!selected.isValid() || !folder) @@ -440,6 +484,7 @@ void AccountSettings::slotDisableVfsCurrentFolder() ui->_folderList->doItemsLayout(); }); msgBox->open(); +#endif } void AccountSettings::showConnectionLabel(const QString &message, QStringList errors) diff --git a/src/gui/accountsettings.h b/src/gui/accountsettings.h index 8c28a7bb396..9eb6dec02d3 100644 --- a/src/gui/accountsettings.h +++ b/src/gui/accountsettings.h @@ -15,17 +15,19 @@ #ifndef ACCOUNTSETTINGS_H #define ACCOUNTSETTINGS_H -#include #include "folder.h" #include "loginrequireddialog.h" #include "owncloudgui.h" #include "progressdispatcher.h" + +#include +#include + class QModelIndex; class QNetworkReply; class QLabel; -class QSortFilterProxyModel; namespace OCC { class AccountModalWidget; @@ -49,6 +51,7 @@ class AccountSettings : public QWidget { Q_OBJECT Q_PROPERTY(AccountStatePtr accountState MEMBER _accountState) + Q_PROPERTY(QSortFilterProxyModel *model MEMBER _sortModel) public: enum class ModalWidgetSizePolicy { Minimum = QSizePolicy::Minimum, Expanding = QSizePolicy::Expanding }; @@ -62,6 +65,8 @@ class AccountSettings : public QWidget void addModalLegacyDialog(QWidget *widget, ModalWidgetSizePolicy sizePolicy); void addModalWidget(AccountModalWidget *widget); + auto model() { return _sortModel; } + signals: void folderChanged(); void showIssuesList(); diff --git a/src/gui/accountsettings.ui b/src/gui/accountsettings.ui index 0ec52091d8a..7fb06eee83f 100644 --- a/src/gui/accountsettings.ui +++ b/src/gui/accountsettings.ui @@ -147,20 +147,7 @@ - - - - 0 - 5 - - - - Qt::CustomContextMenu - - - QAbstractItemView::NoEditTriggers - - + @@ -190,9 +177,21 @@ + + + + PushButton + + + + + QQuickWidget + QWidget +
QtQuickWidgets/QQuickWidget
+
QProgressIndicator QWidget diff --git a/src/gui/folderstatusdelegate.cpp b/src/gui/folderstatusdelegate.cpp deleted file mode 100644 index ef2cc6b0ff6..00000000000 --- a/src/gui/folderstatusdelegate.cpp +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Olivier Goffart - * - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -#include "folderstatusdelegate.h" -#include "folderstatusmodel.h" - -#include "folderman.h" -#include "accountstate.h" -#include "theme.h" -#include "account.h" -#include "guiutility.h" - -#include "resources/resources.h" - -#include -#include - -namespace { -const int barHeightC = 7; -} - -namespace OCC { - -FolderStatusDelegate::FolderStatusDelegate(QObject *parent) - : QStyledItemDelegate(parent) -{ -} - -// allocate each item size in listview. -QSize FolderStatusDelegate::sizeHint(const QStyleOptionViewItem &option, - const QModelIndex &index) const -{ - const_cast(this)->updateFont(option.font); - QFontMetricsF fm(_font); - - // calc height - qreal h = rootFolderHeightWithoutErrors() + _margin; - // this already includes the bottom margin - - // add some space for the message boxes. - for (auto column : {FolderStatusModel::Columns::FolderConflictMsg, FolderStatusModel::Columns::FolderErrorMsg}) { - auto msgs = index.siblingAtColumn(static_cast(column)).data().toStringList(); - if (!msgs.isEmpty()) { - h += _margin + 2 * _margin + msgs.count() * fm.height(); - } - } - - return QSize(0, h); -} - -qreal FolderStatusDelegate::rootFolderHeightWithoutErrors() const -{ - if (!_ready) { - return {}; - } - const QFontMetricsF fm(_font); - const QFontMetricsF aliasFm(_aliasFont); - qreal h = _aliasMargin; // margin to top - h += aliasFm.height(); // alias - h += _margin; // between alias and local path - h += fm.height(); // sync text - - // quota or progress bar - h += _margin; - h += fm.height(); // quota or progress bar - h += _margin; - h += fm.height(); // possible progress string - return h; -} - -void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const -{ - if (index.column() != 0) { - return; - } - - const_cast(this)->updateFont(option.font); - const auto textAlign = Qt::AlignLeft; - - const QFont errorFont = _font; - const QFont progressFont = [progressFont = _font]() mutable { - progressFont.setPointSize(progressFont.pointSize() - 2); - return progressFont; - }(); - - const QFontMetricsF subFm(_font); - const QFontMetricsF aliasFm(_aliasFont); - - painter->save(); - - const QString statusIconName = index.siblingAtColumn(static_cast(FolderStatusModel::Columns::FolderStatusIconRole)).data().toString(); - const QString aliasText = qvariant_cast(index.siblingAtColumn(static_cast(FolderStatusModel::Columns::HeaderRole)).data()); - const QStringList conflictTexts = qvariant_cast(index.siblingAtColumn(static_cast(FolderStatusModel::Columns::FolderConflictMsg)).data()); - const QStringList errorTexts = qvariant_cast(index.siblingAtColumn(static_cast(FolderStatusModel::Columns::FolderErrorMsg)).data()); - const QIcon spaceImage = index.siblingAtColumn(static_cast(FolderStatusModel::Columns::FolderImage)).data().value(); - - const int overallPercent = qvariant_cast(index.siblingAtColumn(static_cast(FolderStatusModel::Columns::SyncProgressOverallPercent)).data()); - const QString overallString = qvariant_cast(index.siblingAtColumn(static_cast(FolderStatusModel::Columns::SyncProgressOverallString)).data()); - const QString itemString = qvariant_cast(index.siblingAtColumn(static_cast(FolderStatusModel::Columns::SyncProgressItemString)).data()); - const int warningCount = qvariant_cast(index.siblingAtColumn(static_cast(FolderStatusModel::Columns::WarningCount)).data()); - const bool syncOngoing = qvariant_cast(index.siblingAtColumn(static_cast(FolderStatusModel::Columns::SyncRunning)).data()); - - const QString syncText = qvariant_cast(index.siblingAtColumn(static_cast(FolderStatusModel::Columns::FolderSyncText)).data()); - const bool showProgess = !overallString.isEmpty() || !itemString.isEmpty(); - - const auto iconState = - index.siblingAtColumn(static_cast(FolderStatusModel::Columns::FolderAccountConnected)).data().toBool() ? QIcon::Normal : QIcon::Disabled; - - const auto statusRect = QRectF{option.rect}.adjusted(0, 0, 0, rootFolderHeightWithoutErrors() - option.rect.height()); - const auto iconRect = - QRectF{statusRect.topLeft(), QSizeF{statusRect.height(), statusRect.height()}}.marginsRemoved({_aliasMargin, _aliasMargin, _aliasMargin, _aliasMargin}); - - // the rectangle next to the icon which will contain the strings - const auto infoRect = QRectF{iconRect.topRight(), QSizeF{statusRect.width() - iconRect.width(), iconRect.height()}}.marginsRemoved({_aliasMargin, 0, 0, 0}); - const auto aliasRect = QRectF{infoRect.topLeft(), QSizeF{infoRect.width(), aliasFm.height()}}; - - const auto optionsButtonRect = this->computeOptionsButtonRect(option.rect); - const auto marginOffset = QPointF{0, _margin}; - const auto localPathRect = QRectF{aliasRect.bottomLeft() + marginOffset, QSizeF{aliasRect.width(), subFm.height()}}; - const auto quotaTextRect = [&] { - QRectF rect{localPathRect.bottomLeft() + marginOffset, QSizeF{aliasRect.width(), subFm.height()}}; - rect.setRight(optionsButtonRect.left() - _margin); - return rect; - }(); - - - { - const auto iconVisualRect = QStyle::visualRect(option.direction, option.rect, iconRect.toRect()); - spaceImage.paint(painter, iconVisualRect, Qt::AlignCenter, iconState); - // paint the overlay in NormalState, on mac os disabled icons have an alpha channel, drawing semi transparent icons on top of each other... - Theme::instance()->themeIcon(QStringLiteral("states/%1").arg(statusIconName)).paint(painter, iconVisualRect, Qt::AlignCenter, QIcon::Normal); - } - - // only show the warning icon if the sync is running. Otherwise its - // encoded in the status icon. - if (warningCount > 0 && syncOngoing) { - Resources::getCoreIcon(QStringLiteral("warning")) - .paint(painter, QStyle::visualRect(option.direction, option.rect, QRectF{iconRect.bottomLeft() - QPointF(0, 17), QSizeF{16, 16}}.toRect()), - Qt::AlignCenter, iconState); - } - - auto palette = option.palette; - - if (qApp->style()->inherits("QWindowsVistaStyle")) { - // Hack: Windows Vista's light blue is not contrasting enough for white - - // (code from QWindowsVistaStyle::drawControl for CE_ItemViewItem) - palette.setColor(QPalette::All, QPalette::HighlightedText, palette.color(QPalette::Active, QPalette::Text)); - palette.setColor(QPalette::All, QPalette::Highlight, palette.base().color().darker(108)); - } - - - QPalette::ColorGroup cg = option.state & QStyle::State_Enabled - ? QPalette::Normal - : QPalette::Disabled; - if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) - cg = QPalette::Inactive; - - if (option.state & QStyle::State_Selected) { - painter->setPen(palette.color(cg, QPalette::HighlightedText)); - } else { - painter->setPen(palette.color(cg, QPalette::Text)); - } - painter->setFont(_aliasFont); - painter->drawText( - QStyle::visualRect(option.direction, option.rect, aliasRect.toRect()), textAlign, aliasFm.elidedText(aliasText, Qt::ElideRight, aliasRect.width())); - - painter->setFont(_font); - painter->drawText(QStyle::visualRect(option.direction, option.rect, localPathRect.toRect()), textAlign, - subFm.elidedText(syncText, Qt::ElideRight, localPathRect.width())); - - if (!showProgess) { - const auto totalQuota = index.siblingAtColumn(static_cast(FolderStatusModel::Columns::QuotaTotal)).data().value(); - // only draw a bar if we have a quota set - if (totalQuota > 0) { - const auto usedQuota = index.siblingAtColumn(static_cast(FolderStatusModel::Columns::QuotaUsed)).data().value(); - painter->setFont(_font); - painter->drawText(QStyle::visualRect(option.direction, option.rect, quotaTextRect.toRect()), Qt::AlignLeft | Qt::AlignVCenter, - subFm.elidedText( - tr("%1 of %2 in use").arg(Utility::octetsToString(usedQuota), Utility::octetsToString(totalQuota)), Qt::ElideRight, quotaTextRect.width())); - } - } else { - painter->save(); - - const auto pogressRect = quotaTextRect.marginsAdded({0, 0, 0, barHeightC + _margin + subFm.height()}); - // Overall Progress Bar. - const auto pBRect = QRectF{pogressRect.topLeft(), QSizeF{pogressRect.width() - 2 * _margin, barHeightC}}; - - QStyleOptionProgressBar pBarOpt; - - pBarOpt.state = option.state | QStyle::State_Horizontal; - pBarOpt.minimum = 0; - pBarOpt.maximum = 100; - pBarOpt.progress = overallPercent; - pBarOpt.rect = QStyle::visualRect(option.direction, option.rect, pBRect.toRect()); - QApplication::style()->drawControl(QStyle::CE_ProgressBar, &pBarOpt, painter, option.widget); - - // Overall Progress Text - const QRectF overallProgressRect = {pBRect.bottomLeft() + marginOffset, QSizeF{pogressRect.width(), subFm.height()}}; - painter->setFont(progressFont); - - painter->drawText(QStyle::visualRect(option.direction, option.rect, overallProgressRect.toRect()), Qt::AlignLeft | Qt::AlignVCenter, overallString); - - painter->restore(); - } - - // paint an error overlay if there is an error string or conflict string - auto drawTextBox = [&, pos = option.rect.top() + rootFolderHeightWithoutErrors() + _margin](const QStringList &texts, QColor color) mutable { - QRectF rect = quotaTextRect; - rect.setLeft(iconRect.left()); - rect.setTop(pos); - rect.setHeight(texts.count() * subFm.height() + 2 * _margin); - rect.setRight(option.rect.right() - _margin); - - painter->save(); - painter->setBrush(color); - painter->setPen(QColor(0xaa, 0xaa, 0xaa)); - painter->drawRoundedRect(QStyle::visualRect(option.direction, option.rect, rect.toRect()), 4, 4); - painter->setPen(Qt::white); - painter->setFont(errorFont); - QRect textRect(rect.left() + _margin, rect.top() + _margin, rect.width() - 2 * _margin, subFm.height()); - - for (const auto &eText : texts) { - painter->drawText(QStyle::visualRect(option.direction, option.rect, textRect), textAlign, subFm.elidedText(eText, Qt::ElideLeft, textRect.width())); - textRect.translate(0, textRect.height()); - } - painter->restore(); - - pos = rect.bottom() + _margin; - }; - - if (!conflictTexts.isEmpty()) { - drawTextBox(conflictTexts, QColor(0xba, 0xba, 0x4d)); - } - if (!errorTexts.isEmpty()) { - drawTextBox(errorTexts, QColor(0xbb, 0x4d, 0x4d)); - } - { - // was saved before we fetched the data from the model - painter->restore(); - QStyleOptionToolButton btnOpt; - btnOpt.state = option.state; - btnOpt.state &= ~(QStyle::State_Selected | QStyle::State_HasFocus); - btnOpt.state |= QStyle::State_Raised; - btnOpt.arrowType = Qt::NoArrow; - btnOpt.subControls = QStyle::SC_ToolButton; - btnOpt.rect = QStyle::visualRect(option.direction, option.rect, optionsButtonRect.toRect()); - btnOpt.icon = Resources::getCoreIcon(QStringLiteral("more")); - int e = QApplication::style()->pixelMetric(QStyle::PM_ButtonIconSize); - btnOpt.iconSize = QSize(e,e); - QApplication::style()->drawComplexControl(QStyle::CC_ToolButton, &btnOpt, painter); - } -} - -QRectF FolderStatusDelegate::computeOptionsButtonRect(QRectF within) const -{ - if (!_ready) { - return {}; - } - within.setHeight(FolderStatusDelegate::rootFolderHeightWithoutErrors()); - - QStyleOptionToolButton opt; - int e = QApplication::style()->pixelMetric(QStyle::PM_ButtonIconSize); - opt.rect.setSize(QSize(e,e)); - QSizeF size = QApplication::style()->sizeFromContents(QStyle::CT_ToolButton, &opt, opt.rect.size()); - - return {{within.right() - size.width() - QApplication::style()->pixelMetric(QStyle::PM_LayoutRightMargin), - within.top() + within.height() / 2 - size.height() / 2}, - size}; -} - -QRectF FolderStatusDelegate::errorsListRect(QRectF within, const QModelIndex &index) const -{ - if (!_ready) { - return {}; - } - const QFontMetrics fm(_font); - within.setTop(within.top() + FolderStatusDelegate::rootFolderHeightWithoutErrors() + _margin); - qreal h = 0; - for (auto column : { FolderStatusModel::Columns::FolderConflictMsg, FolderStatusModel::Columns::FolderErrorMsg }) { - const auto msgs = index.siblingAtColumn(static_cast(column)).data().toStringList(); - if (!msgs.isEmpty()) { - h += _margin + 2 * _margin + msgs.count() * fm.height() + _margin; - } - } - within.setHeight(h); - return within; -} - -void FolderStatusDelegate::updateFont(const QFont &font) -{ - if (!_ready || _font != font) { - _ready = true; - _aliasFont = [&]() { - auto aliasFont = font; - aliasFont.setBold(true); - aliasFont.setPointSizeF(font.pointSizeF() + 2); - return aliasFont; - }(); - - _margin = QFontMetricsF(_font).height() / 4.0; - _aliasMargin = QFontMetricsF(_aliasFont).height() / 2.0; - } -} - - -} // namespace OCC diff --git a/src/gui/folderstatusdelegate.h b/src/gui/folderstatusdelegate.h deleted file mode 100644 index db7027eb2af..00000000000 --- a/src/gui/folderstatusdelegate.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Olivier Goffart - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -#pragma once -#include - -namespace OCC { - -/** - * @brief The FolderStatusDelegate class - * @ingroup gui - */ -class FolderStatusDelegate : public QStyledItemDelegate -{ - Q_OBJECT -public: - FolderStatusDelegate(QObject *parent); - - void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; - QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const override; - - - /** - * return the position of the option button within the item - */ - QRectF computeOptionsButtonRect(QRectF within) const; - QRectF errorsListRect(QRectF within, const QModelIndex &) const; - qreal rootFolderHeightWithoutErrors() const; - -private: - static QString addFolderText(bool useSapces); - - // a workaround for a design flaw of the class - // we need to know the actual font for most computations - // the font is only set in paint and sizeHint - void updateFont(const QFont &font); - - QFont _aliasFont; - QFont _font; - qreal _margin = 0; - qreal _aliasMargin = 0; - bool _ready = false; -}; - -} // namespace OCC diff --git a/src/gui/folderstatusmodel.cpp b/src/gui/folderstatusmodel.cpp index 05a2e054e2c..a63461de25c 100644 --- a/src/gui/folderstatusmodel.cpp +++ b/src/gui/folderstatusmodel.cpp @@ -89,7 +89,7 @@ namespace { QString _progressString; QString _overallSyncString; int _warningCount = 0; - int _overallPercent = 0; + float _overallPercent = -1; }; SubFolderInfo(Folder *folder) : _folder(folder) @@ -279,33 +279,29 @@ QVariant FolderStatusModel::data(const QModelIndex &index, int role) const return nullptr; }; - switch (role) { - case Qt::DisplayRole: - switch (column) { - case Columns::FolderPathRole: - return f->shortGuiLocalPath(); - case Columns::FolderSecondPathRole: - return f->remotePath(); - case Columns::FolderConflictMsg: - return (f->syncResult().hasUnresolvedConflicts()) - ? QStringList(tr("There are unresolved conflicts. Click for details.")) - : QStringList(); - case Columns::FolderErrorMsg: { - auto errors = f->syncResult().errorStrings(); - const auto legacyError = FolderMan::instance()->unsupportedConfiguration(f->path()); - if (!legacyError) { - // the error message might contain new lines, the delegate only expect multiple single line values - errors.append(legacyError.error().split(QLatin1Char('\n'))); - } - if (f->isReady() && f->virtualFilesEnabled() && f->vfs().mode() == Vfs::Mode::WithSuffix) { - errors.append({ - tr("The suffix VFS plugin is deprecated and will be removed in the 7.0 release."), - tr("Please use the context menu and select \"Disable virtual file support\" to ensure future access to your synced files."), - tr("You are going to lose access to your sync folder if you do not do so!"), - }); - } - return errors; + switch (static_cast(role)) { + case Columns::FolderPathRole: + return f->shortGuiLocalPath(); + case Columns::FolderSecondPathRole: + return f->remotePath(); + case Columns::FolderConflictMsg: + return (f->syncResult().hasUnresolvedConflicts()) ? QStringList(tr("There are unresolved conflicts. Click for details.")) : QStringList(); + case Columns::FolderErrorMsg: { + auto errors = f->syncResult().errorStrings(); + const auto legacyError = FolderMan::instance()->unsupportedConfiguration(f->path()); + if (!legacyError) { + // the error message might contain new lines, the delegate only expect multiple single line values + errors.append(legacyError.error().split(QLatin1Char('\n'))); } + if (f->isReady() && f->virtualFilesEnabled() && f->vfs().mode() == Vfs::Mode::WithSuffix) { + errors.append({ + tr("The suffix VFS plugin is deprecated and will be removed in the 7.0 release."), + tr("Please use the context menu and select \"Disable virtual file support\" to ensure future access to your synced files."), + tr("You are going to lose access to your sync folder if you do not do so!"), + }); + } + return errors; + } case Columns::SyncRunning: return f->syncResult().status() == SyncResult::SyncRunning; case Columns::HeaderRole: { @@ -319,6 +315,11 @@ QVariant FolderStatusModel::data(const QModelIndex &index, int role) const return space->image(); } return Resources::getCoreIcon(QStringLiteral("folder-sync")); + case Columns::FolderImageUrl: + if (auto *space = getSpace()) { + return space->imageUrl(); + } + return Resources::getCoreIconUrl(QStringLiteral("folder-sync")); case Columns::FolderSyncPaused: return f->syncPaused(); case Columns::FolderAccountConnected: @@ -337,7 +338,7 @@ QVariant FolderStatusModel::data(const QModelIndex &index, int role) const case Columns::WarningCount: return progress._warningCount; case Columns::SyncProgressOverallPercent: - return progress._overallPercent; + return progress._overallPercent == -1 ? progress._overallPercent : progress._overallPercent / 100; case Columns::SyncProgressOverallString: return progress._overallSyncString; case Columns::FolderSyncText: { @@ -362,24 +363,22 @@ QVariant FolderStatusModel::data(const QModelIndex &index, int role) const } else { return QVariant::fromValue(getQuotaOc10(_accountState, column)); } + case Columns::ToolTipRole: { + if (!progress.isNull()) { + return progress._progressString; + } + if (accountConnected) { + return tr("%1\n%2").arg(Utility::enumToDisplayName(f->syncResult().status()), QDir::toNativeSeparators(folderInfo->_folder->path())); + } else { + return tr("Signed out\n%1").arg(QDir::toNativeSeparators(folderInfo->_folder->path())); + } case Columns::IsUsingSpaces: // handled before [[fallthrough]]; case Columns::ColumnCount: Q_UNREACHABLE(); break; + } break; } - break; - case Qt::ToolTipRole: { - if (!progress.isNull()) { - return progress._progressString; - } - if (accountConnected) { - return tr("%1\n%2").arg(Utility::enumToDisplayName(f->syncResult().status()), QDir::toNativeSeparators(folderInfo->_folder->path())); - } else { - return tr("Signed out\n%1").arg(QDir::toNativeSeparators(folderInfo->_folder->path())); - } - } - } return QVariant(); } @@ -391,7 +390,7 @@ Folder *FolderStatusModel::folder(const QModelIndex &index) const int FolderStatusModel::columnCount(const QModelIndex &) const { - return static_cast(Columns::ColumnCount); + return static_cast(1); } int FolderStatusModel::rowCount(const QModelIndex &parent) const @@ -400,6 +399,19 @@ int FolderStatusModel::rowCount(const QModelIndex &parent) const return static_cast(_folders.size()); } +QHash FolderStatusModel::roleNames() const +{ + return { + {static_cast(Columns::HeaderRole), "displayName"}, + {static_cast(Columns::FolderPathRole), "subTitle"}, + {static_cast(Columns::FolderImageUrl), "imageUrl"}, + {static_cast(Columns::SyncProgressOverallPercent), "progress"}, + {static_cast(Columns::SyncProgressOverallString), "overallText"}, + {static_cast(Columns::SyncProgressItemString), "itemText"}, + {static_cast(Columns::FolderSyncText), "descriptionText"}, + }; +} + void FolderStatusModel::slotUpdateFolderState(Folder *folder) { if (!folder) diff --git a/src/gui/folderstatusmodel.h b/src/gui/folderstatusmodel.h index 55352a11f1d..504b0f3bee7 100644 --- a/src/gui/folderstatusmodel.h +++ b/src/gui/folderstatusmodel.h @@ -19,10 +19,11 @@ #include "progressdispatcher.h" #include -#include -#include #include +#include #include +#include +#include class QNetworkReply; @@ -43,9 +44,11 @@ namespace { class FolderStatusModel : public QAbstractTableModel { Q_OBJECT + QML_ELEMENT public: enum class Columns { - HeaderRole, // must be 0 as it is also used from the default delegate + ToolTipRole = Qt::ToolTipRole, + HeaderRole = Qt::UserRole + 1, // must be 0 as it is also used from the default delegate FolderPathRole, // for a SubFolder it's the complete path FolderSecondPathRole, FolderConflictMsg, @@ -71,6 +74,8 @@ class FolderStatusModel : public QAbstractTableModel QuotaUsed, QuotaTotal, + FolderImageUrl, + ColumnCount }; Q_ENUMS(Columns); @@ -84,6 +89,8 @@ class FolderStatusModel : public QAbstractTableModel int columnCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QHash roleNames() const; + public slots: void slotUpdateFolderState(Folder *); void resetFolders(); @@ -100,5 +107,4 @@ private slots: }; } // namespace OCC - #endif // FOLDERSTATUSMODEL_H diff --git a/src/gui/main.cpp b/src/gui/main.cpp index ed53147c4cc..5f91cf1f573 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #ifdef Q_OS_WIN @@ -361,9 +362,14 @@ QString setupTranslations(QApplication *app) return displayLanguage; } } // Anonymous namespace +namespace OCC { +// TODO: QT_NO_PLUGIN +extern void qml_register_types_org_ownCloud_qmlcomponents(); +} int main(int argc, char **argv) { + OCC::qml_register_types_org_ownCloud_qmlcomponents(); return RestartManager([](int argc, char **argv) { // when called with --cmd we run the cmd client in a sub process and forward everything if (argc > 1 && argv[1] == QByteArrayLiteral("--cmd")) { diff --git a/src/gui/models/models.h b/src/gui/models/models.h index 90d99db15e0..9842450cbb8 100644 --- a/src/gui/models/models.h +++ b/src/gui/models/models.h @@ -18,6 +18,8 @@ #include #include +#include + class QSortFilterProxyModel; class QMenu; @@ -25,6 +27,7 @@ namespace OCC { namespace Models { Q_NAMESPACE + QML_ELEMENT enum DataRoles { UnderlyingDataRole = Qt::UserRole + 100, diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 6b93c9d2464..1e288a6a127 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -927,6 +927,8 @@ void ownCloudGui::runNewAccountWizard() } case ConnectionValidator::ClientUnsupported: break; + case ConnectionValidator::SslError: + break; default: Q_UNREACHABLE(); } diff --git a/src/gui/qml/tree.qml b/src/gui/qml/tree.qml new file mode 100644 index 00000000000..53facd14e48 --- /dev/null +++ b/src/gui/qml/tree.qml @@ -0,0 +1,113 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + + +import org.ownCloud.qmlcomponents 1.0 + +Item { + + ListView { + id: view + anchors.fill: parent + model: ctx.model + + delegate: Rectangle { + id: delegate + height: 150 + width: view.width + + required property string displayName + required property string descriptionText + required property url imageUrl + required property double progress + required property string overallText + required property string itemText + + RowLayout { + anchors.fill: parent + anchors.margins: 10 + spacing: 6 + + Image{ + id: image + Layout.maximumHeight: 128 + Layout.maximumWidth: 128 + Layout.alignment: Qt.AlignHCenter + antialiasing: true + + source: imageUrl + } + + ColumnLayout { + spacing: 6 + Layout.alignment: Qt.AlignHCenter + + Text { + Layout.fillWidth: true + id: title + text: delegate.displayName + font.bold: true + font.pointSize: 15 + elide: Text.ElideLeft + verticalAlignment: Text.AlignHCenter + } + + Text { + Layout.fillWidth: true + id: subtitle + text: delegate.descriptionText + elide: Text.ElideRight + verticalAlignment: Text.AlignHCenter + } + + ColumnLayout { + spacing: 6 + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + + id: progress + visible: false + + ProgressBar { + Layout.fillWidth: true + Layout.maximumHeight: 10 + value: delegate.progress + visible: true + onValueChanged: { + console.log(value) + if (value != -1) { + progress.visible = true + } + } + } + + Text { + Layout.fillWidth: true + id: progressText + text: delegate.overallText + elide: Text.ElideLeft + verticalAlignment: Text.AlignHCenter + } + Text { + Layout.fillWidth: true + id: progressText2 + text: delegate.itemText + elide: Text.ElideMiddle + verticalAlignment: Text.AlignHCenter + } + } + } + } + MouseArea { + anchors.fill: parent + onClicked: { + console.log(delegate.modeIndex) + } + } + + + + } + } +} diff --git a/src/libsync/graphapi/space.cpp b/src/libsync/graphapi/space.cpp index 261a512b63d..828b5a3106c 100644 --- a/src/libsync/graphapi/space.cpp +++ b/src/libsync/graphapi/space.cpp @@ -48,14 +48,19 @@ void Space::setDrive(const OpenAPI::OAIDrive &drive) { _drive = drive; if (!imageUrl().isEmpty()) { - auto job = _spaceManager->account()->resourcesCache()->makeGetJob(imageUrl(), {}, this); - connect(job, &SimpleNetworkJob::finishedSignal, this, [job, this] { - if (job->httpStatusCode() == 200) { - _image = job->asIcon(); - Q_EMIT _spaceManager->spaceChanged(this); - } - }); - job->start(); + if (imageUrl().scheme() == u"qrc") { + _image = QIcon(QLatin1Char(':') + imageUrl().path()); + Q_EMIT _spaceManager->spaceChanged(this); + } else { + auto job = _spaceManager->account()->resourcesCache()->makeGetJob(imageUrl(), {}, this); + connect(job, &SimpleNetworkJob::finishedSignal, this, [job, this] { + if (job->httpStatusCode() == 200) { + _image = job->asIcon(); + Q_EMIT _spaceManager->spaceChanged(this); + } + }); + job->start(); + } } } QString Space::displayName() const @@ -89,13 +94,10 @@ QUrl Space::imageUrl() const { const auto &special = _drive.getSpecial(); const auto img = std::find_if(special.cbegin(), special.cend(), [](const auto &it) { return it.getSpecialFolder().getName() == QLatin1String("image"); }); - return img == special.cend() ? QUrl() : QUrl(img->getWebDavUrl()); + return img == special.cend() ? Resources::getCoreIconUrl(QStringLiteral("folder-sync")) : QUrl(img->getWebDavUrl()); } QIcon Space::image() const { - if (_image.isNull()) { - return Resources::getCoreIcon(QStringLiteral("folder-sync")); - } return _image; } diff --git a/src/resources/resources.cpp b/src/resources/resources.cpp index 6147e9316c5..db39902d51d 100644 --- a/src/resources/resources.cpp +++ b/src/resources/resources.cpp @@ -14,6 +14,8 @@ #include "resources.h" +#include +#include #include using namespace OCC; @@ -26,14 +28,22 @@ bool OCC::Resources::isUsingDarkTheme() return forceDark || QPalette().base().color().lightnessF() <= 0.5; } -QIcon OCC::Resources::getCoreIcon(const QString &icon_name) +QUrl Resources::getCoreIconUrl(const QString &icon_name) { if (icon_name.isEmpty()) { return {}; } const QString theme = Resources::isUsingDarkTheme() ? QStringLiteral("dark") : QStringLiteral("light"); - const QString path = QStringLiteral(":/client/resources/%1/%2").arg(theme, icon_name); - const QIcon icon(path); + return QUrl(QStringLiteral("qrc:/client/resources/%1/%2").arg(theme, icon_name)); +} + +QIcon OCC::Resources::getCoreIcon(const QString &icon_name) +{ + if (icon_name.isEmpty()) { + return {}; + } + // QIcon doesn't like qrc:// urls... + const QIcon icon(QLatin1Char(':') + getCoreIconUrl(icon_name).path()); // were we able to load the file? Q_ASSERT(icon.actualSize({100, 100}).isValid()); return icon; diff --git a/src/resources/resources.h b/src/resources/resources.h index f0ae77d15fb..e2aff659495 100644 --- a/src/resources/resources.h +++ b/src/resources/resources.h @@ -16,6 +16,7 @@ #include "resources/owncloudresources.h" #include +#include namespace OCC::Resources { /** @@ -24,5 +25,6 @@ namespace OCC::Resources { */ bool OWNCLOUDRESOURCES_EXPORT isUsingDarkTheme(); +QUrl OWNCLOUDRESOURCES_EXPORT getCoreIconUrl(const QString &icon_name); QIcon OWNCLOUDRESOURCES_EXPORT getCoreIcon(const QString &icon_name); }