diff options
author | Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> | 2020-01-02 10:43:34 +0300 |
---|---|---|
committer | Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> | 2020-01-02 10:43:34 +0300 |
commit | 7653c5fa605718618cd769526b40e5a6164dd969 (patch) | |
tree | 37071fdf1a6867ca71f8736793193aa705e74c38 /src/gui | |
parent | 44bfc79caac1bbd8e1187fc84435219ea2af2a9b (diff) | |
parent | bd61cd3142fcc7bad365c4d96616bafb1e88feda (diff) |
Fix merge conflict
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
Diffstat (limited to 'src/gui')
53 files changed, 1814 insertions, 1008 deletions
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 35102e0cc..9b0455a6b 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -37,12 +37,11 @@ set(client_UI_SRCS mnemonicdialog.ui tray/window.qml tray/UserLine.qml + wizard/flow2authwidget.ui wizard/owncloudadvancedsetuppage.ui wizard/owncloudconnectionmethoddialog.ui wizard/owncloudhttpcredspage.ui wizard/owncloudoauthcredspage.ui - wizard/flow2authcredspage.ui - wizard/flow2authwidget.ui wizard/owncloudsetupnocredspage.ui wizard/owncloudwizardresultpage.ui wizard/webview.ui @@ -105,6 +104,7 @@ set(client_SRCS servernotificationhandler.cpp guiutility.cpp elidedlabel.cpp + headerbanner.cpp iconjob.cpp remotewipe.cpp tray/UserModel.cpp @@ -112,6 +112,7 @@ set(client_SRCS creds/httpcredentialsgui.cpp creds/oauth.cpp creds/flow2auth.cpp + creds/keychainchunk.cpp creds/webflowcredentials.cpp creds/webflowcredentialsdialog.cpp wizard/postfixlineedit.cpp @@ -148,8 +149,6 @@ set(updater_SRCS IF( APPLE ) list(APPEND client_SRCS cocoainitializer_mac.mm) - list(APPEND client_SRCS settingsdialogmac.cpp) - list(REMOVE_ITEM client_SRCS settingsdialog.cpp) list(APPEND client_SRCS socketapisocket_mac.mm) list(APPEND client_SRCS systray.mm) @@ -179,17 +178,6 @@ set(3rdparty_SRC ../3rdparty/kmessagewidget/kmessagewidget.cpp ) -if (APPLE) - list(APPEND 3rdparty_SRC - ../3rdparty/qtmacgoodies/src/macpreferenceswindow.mm - ../3rdparty/qtmacgoodies/src/macstandardicon.mm - ../3rdparty/qtmacgoodies/src/macwindow.mm - ) - # We want to access Cocoa specific structures in the code above - # and need the platform plugin interface for that - which is private. - include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS}) -endif() - if(NOT WIN32) list(APPEND 3rdparty_SRC ../3rdparty/qtlockedfile/qtlockedfile_unix.cpp) else() @@ -345,7 +333,6 @@ ENDIF() target_include_directories(${APPLICATION_EXECUTABLE} PRIVATE ${CMAKE_SOURCE_DIR}/src/3rdparty/QProgressIndicator ${CMAKE_SOURCE_DIR}/src/3rdparty/qtlockedfile - ${CMAKE_SOURCE_DIR}/src/3rdparty/qtmacgoodies/src ${CMAKE_SOURCE_DIR}/src/3rdparty/qtsingleapplication ${CMAKE_SOURCE_DIR}/src/3rdparty/kmessagewidget ${CMAKE_CURRENT_BINARY_DIR} diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index 1bbf7ee10..a7fd0f5a1 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -57,10 +57,6 @@ #include "account.h" -#ifdef Q_OS_MAC -#include "settingsdialogmac.h" -#endif - namespace OCC { Q_LOGGING_CATEGORY(lcAccountSettings, "nextcloud.gui.account.settings", QtInfoMsg) @@ -113,13 +109,13 @@ protected: AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) : QWidget(parent) - , ui(new Ui::AccountSettings) + , _ui(new Ui::AccountSettings) , _wasDisabledBefore(false) , _accountState(accountState) , _quotaInfo(accountState) , _menuShown(false) { - ui->setupUi(this); + _ui->setupUi(this); _model = new FolderStatusModel; _model->setAccountState(_accountState); @@ -127,37 +123,40 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) FolderStatusDelegate *delegate = new FolderStatusDelegate; delegate->setParent(this); - ui->_folderList->header()->hide(); - ui->_folderList->setItemDelegate(delegate); - ui->_folderList->setModel(_model); + // Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching) + connect(this, &AccountSettings::styleChanged, delegate, &FolderStatusDelegate::slotStyleChanged); + + _ui->_folderList->header()->hide(); + _ui->_folderList->setItemDelegate(delegate); + _ui->_folderList->setModel(_model); #if defined(Q_OS_MAC) - ui->_folderList->setMinimumWidth(400); + _ui->_folderList->setMinimumWidth(400); #else - ui->_folderList->setMinimumWidth(300); + _ui->_folderList->setMinimumWidth(300); #endif - new ToolTipUpdater(ui->_folderList); + new ToolTipUpdater(_ui->_folderList); auto mouseCursorChanger = new MouseCursorChanger(this); - mouseCursorChanger->folderList = ui->_folderList; + mouseCursorChanger->folderList = _ui->_folderList; mouseCursorChanger->model = _model; - ui->_folderList->setMouseTracking(true); - ui->_folderList->setAttribute(Qt::WA_Hover, true); - ui->_folderList->installEventFilter(mouseCursorChanger); + _ui->_folderList->setMouseTracking(true); + _ui->_folderList->setAttribute(Qt::WA_Hover, true); + _ui->_folderList->installEventFilter(mouseCursorChanger); createAccountToolbox(); connect(AccountManager::instance(), &AccountManager::accountAdded, this, &AccountSettings::slotAccountAdded); connect(this, &AccountSettings::removeAccountFolders, AccountManager::instance(), &AccountManager::removeAccountFolders); - connect(ui->_folderList, &QWidget::customContextMenuRequested, + connect(_ui->_folderList, &QWidget::customContextMenuRequested, this, &AccountSettings::slotCustomContextMenuRequested); - connect(ui->_folderList, &QAbstractItemView::clicked, + connect(_ui->_folderList, &QAbstractItemView::clicked, this, &AccountSettings::slotFolderListClicked); - connect(ui->_folderList, &QTreeView::expanded, this, &AccountSettings::refreshSelectiveSyncStatus); - connect(ui->_folderList, &QTreeView::collapsed, this, &AccountSettings::refreshSelectiveSyncStatus); - connect(ui->selectiveSyncNotification, &QLabel::linkActivated, + connect(_ui->_folderList, &QTreeView::expanded, this, &AccountSettings::refreshSelectiveSyncStatus); + connect(_ui->_folderList, &QTreeView::collapsed, this, &AccountSettings::refreshSelectiveSyncStatus); + connect(_ui->selectiveSyncNotification, &QLabel::linkActivated, this, &AccountSettings::slotLinkActivated); - connect(_model, &FolderStatusModel::suggestExpand, ui->_folderList, &QTreeView::expand); + connect(_model, &FolderStatusModel::suggestExpand, _ui->_folderList, &QTreeView::expand); connect(_model, &FolderStatusModel::dirtyChanged, this, &AccountSettings::refreshSelectiveSyncStatus); refreshSelectiveSyncStatus(); connect(_model, &QAbstractItemModel::rowsInserted, @@ -174,20 +173,21 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) addAction(syncNowWithRemoteDiscovery); - connect(ui->selectiveSyncApply, &QAbstractButton::clicked, _model, &FolderStatusModel::slotApplySelectiveSync); - connect(ui->selectiveSyncCancel, &QAbstractButton::clicked, _model, &FolderStatusModel::resetFolders); - connect(ui->bigFolderApply, &QAbstractButton::clicked, _model, &FolderStatusModel::slotApplySelectiveSync); - connect(ui->bigFolderSyncAll, &QAbstractButton::clicked, _model, &FolderStatusModel::slotSyncAllPendingBigFolders); - connect(ui->bigFolderSyncNone, &QAbstractButton::clicked, _model, &FolderStatusModel::slotSyncNoPendingBigFolders); + connect(_ui->selectiveSyncApply, &QAbstractButton::clicked, _model, &FolderStatusModel::slotApplySelectiveSync); + connect(_ui->selectiveSyncCancel, &QAbstractButton::clicked, _model, &FolderStatusModel::resetFolders); + connect(_ui->bigFolderApply, &QAbstractButton::clicked, _model, &FolderStatusModel::slotApplySelectiveSync); + connect(_ui->bigFolderSyncAll, &QAbstractButton::clicked, _model, &FolderStatusModel::slotSyncAllPendingBigFolders); + connect(_ui->bigFolderSyncNone, &QAbstractButton::clicked, _model, &FolderStatusModel::slotSyncNoPendingBigFolders); connect(FolderMan::instance(), &FolderMan::folderListChanged, _model, &FolderStatusModel::resetFolders); connect(this, &AccountSettings::folderChanged, _model, &FolderStatusModel::resetFolders); - QColor color = palette().highlight().color(); - ui->quotaProgressBar->setStyleSheet(QString::fromLatin1(progressBarStyleC).arg(color.name())); + // quotaProgressBar style now set in customizeStyle() + /*QColor color = palette().highlight().color(); + _ui->quotaProgressBar->setStyleSheet(QString::fromLatin1(progressBarStyleC).arg(color.name()));*/ - ui->connectLabel->setText(tr("No account configured.")); + _ui->connectLabel->setText(tr("No account configured.")); connect(_accountState, &AccountState::stateChanged, this, &AccountSettings::slotAccountStateChanged); slotAccountStateChanged(); @@ -204,8 +204,10 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) { slotNewMnemonicGenerated(); } else { - ui->encryptionMessage->hide(); + _ui->encryptionMessage->hide(); } + + customizeStyle(); } @@ -233,9 +235,9 @@ void AccountSettings::createAccountToolbox() connect(UserModel::instance(), &UserModel::removeAccount, this, &AccountSettings::slotOpenAccountWizard); - ui->_accountToolbox->setText(tr("Account") + QLatin1Char(' ')); - ui->_accountToolbox->setMenu(menu); - ui->_accountToolbox->setPopupMode(QToolButton::InstantPopup); + _ui->_accountToolbox->setText(tr("Account") + QLatin1Char(' ')); + _ui->_accountToolbox->setMenu(menu); + _ui->_accountToolbox->setPopupMode(QToolButton::InstantPopup); slotAccountAdded(_accountState); } @@ -243,14 +245,14 @@ void AccountSettings::createAccountToolbox() void AccountSettings::slotNewMnemonicGenerated() { - ui->encryptionMessage->setText(tr("This account supports end-to-end encryption")); + _ui->encryptionMessage->setText(tr("This account supports end-to-end encryption")); QAction *mnemonic = new QAction(tr("Enable encryption"), this); connect(mnemonic, &QAction::triggered, this, &AccountSettings::requesetMnemonic); - connect(mnemonic, &QAction::triggered, ui->encryptionMessage, &KMessageWidget::hide); + connect(mnemonic, &QAction::triggered, _ui->encryptionMessage, &KMessageWidget::hide); - ui->encryptionMessage->addAction(mnemonic); - ui->encryptionMessage->show(); + _ui->encryptionMessage->addAction(mnemonic); + _ui->encryptionMessage->show(); } void AccountSettings::slotMenuBeforeShow() { @@ -258,7 +260,7 @@ void AccountSettings::slotMenuBeforeShow() { return; } - auto menu = ui->_accountToolbox->menu(); + auto menu = _ui->_accountToolbox->menu(); // We can't check this during the initial creation as there is no account yet then if (_accountState->account()->capabilities().clientSideEncryptionAvaliable()) { @@ -273,7 +275,7 @@ void AccountSettings::slotMenuBeforeShow() { QString AccountSettings::selectedFolderAlias() const { - QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); + QModelIndex selected = _ui->_folderList->selectionModel()->currentIndex(); if (!selected.isValid()) return ""; return _model->data(selected, FolderStatusDelegate::FolderAliasRole).toString(); @@ -286,16 +288,7 @@ void AccountSettings::slotOpenAccountWizard() if (qgetenv("QT_QPA_PLATFORMTHEME") == "appmenu-qt5" || QSystemTrayIcon::isSystemTrayAvailable()) { topLevelWidget()->close(); } -#ifdef Q_OS_MAC - qCDebug(lcAccountSettings) << parent() << topLevelWidget(); - SettingsDialogMac *sd = qobject_cast<SettingsDialogMac *>(topLevelWidget()); - if (sd) { - sd->showActivityPage(); - } else { - qFatal("nope"); - } -#endif OwncloudSetupWizard::runWizard(qApp, SLOT(slotownCloudWizardDone(int)), nullptr); } @@ -311,7 +304,7 @@ void AccountSettings::slotToggleSignInState() void AccountSettings::doExpand() { - ui->_folderList->expandToDepth(0); + _ui->_folderList->expandToDepth(0); } void AccountSettings::slotShowMnemonic(const QString &mnemonic) { @@ -559,7 +552,7 @@ void AccountSettings::slotEditCurrentIgnoredFiles() void AccountSettings::slotEditCurrentLocalIgnoredFiles() { - QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); + QModelIndex selected = _ui->_folderList->selectionModel()->currentIndex(); if (!selected.isValid() || _model->classify(selected) != FolderStatusModel::SubFolder) return; QString fileName = _model->data(selected, FolderStatusDelegate::FolderPathRole).toString(); @@ -631,7 +624,7 @@ void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) { - QTreeView *tv = ui->_folderList; + QTreeView *tv = _ui->_folderList; QModelIndex index = tv->indexAt(pos); if (!index.isValid()) { return; @@ -662,7 +655,7 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) ac = menu->addAction(tr("Edit Ignored Files")); connect(ac, &QAction::triggered, this, &AccountSettings::slotEditCurrentIgnoredFiles); - if (!ui->_folderList->isExpanded(index)) { + if (!_ui->_folderList->isExpanded(index)) { ac = menu->addAction(tr("Choose what to sync")); ac->setEnabled(folderConnected); connect(ac, &QAction::triggered, this, &AccountSettings::doExpand); @@ -701,7 +694,7 @@ void AccountSettings::slotFolderListClicked(const QModelIndex &indx) } if (_model->classify(indx) == FolderStatusModel::RootFolder) { // tries to find if we clicked on the '...' button. - QTreeView *tv = ui->_folderList; + QTreeView *tv = _ui->_folderList; auto pos = tv->mapFromGlobal(QCursor::pos()); if (FolderStatusDelegate::optionsButtonRect(tv->visualRect(indx), layoutDirection()).contains(pos)) { slotCustomContextMenuRequested(pos); @@ -714,8 +707,8 @@ void AccountSettings::slotFolderListClicked(const QModelIndex &indx) // Expand root items on single click if (_accountState && _accountState->state() == AccountState::Connected) { - bool expanded = !(ui->_folderList->isExpanded(indx)); - ui->_folderList->setExpanded(indx, expanded); + bool expanded = !(_ui->_folderList->isExpanded(indx)); + _ui->_folderList->setExpanded(indx, expanded); } } } @@ -797,7 +790,7 @@ void AccountSettings::slotRemoveCurrentFolder() { FolderMan *folderMan = FolderMan::instance(); auto folder = folderMan->folder(selectedFolderAlias()); - QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); + QModelIndex selected = _ui->_folderList->selectionModel()->currentIndex(); if (selected.isValid() && folder) { int row = selected.row(); @@ -839,7 +832,7 @@ void AccountSettings::slotOpenCurrentFolder() void AccountSettings::slotOpenCurrentLocalSubFolder() { - QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); + QModelIndex selected = _ui->_folderList->selectionModel()->currentIndex(); if (!selected.isValid() || _model->classify(selected) != FolderStatusModel::SubFolder) return; QString fileName = _model->data(selected, FolderStatusDelegate::FolderPathRole).toString(); @@ -853,18 +846,21 @@ void AccountSettings::showConnectionLabel(const QString &message, QStringList er "border-width: 1px; border-style: solid; border-color: #aaaaaa;" "border-radius:5px;"); if (errors.isEmpty()) { - ui->connectLabel->setText(message); - ui->connectLabel->setToolTip(QString()); - ui->connectLabel->setStyleSheet(QString()); + QString msg = message; + Theme::replaceLinkColorStringBackgroundAware(msg); + _ui->connectLabel->setText(msg); + _ui->connectLabel->setToolTip(QString()); + _ui->connectLabel->setStyleSheet(QString()); } else { errors.prepend(message); - const QString msg = errors.join(QLatin1String("\n")); + QString msg = errors.join(QLatin1String("\n")); qCDebug(lcAccountSettings) << msg; - ui->connectLabel->setText(msg); - ui->connectLabel->setToolTip(QString()); - ui->connectLabel->setStyleSheet(errStyle); + Theme::replaceLinkColorString(msg, QColor("#c1c8e6")); + _ui->connectLabel->setText(msg); + _ui->connectLabel->setToolTip(QString()); + _ui->connectLabel->setStyleSheet(errStyle); } - ui->accountStatus->setVisible(!message.isEmpty()); + _ui->accountStatus->setVisible(!message.isEmpty()); } void AccountSettings::slotEnableCurrentFolder() @@ -962,29 +958,29 @@ void AccountSettings::slotOpenOC() void AccountSettings::slotUpdateQuota(qint64 total, qint64 used) { if (total > 0) { - ui->quotaProgressBar->setVisible(true); - ui->quotaProgressBar->setEnabled(true); + _ui->quotaProgressBar->setVisible(true); + _ui->quotaProgressBar->setEnabled(true); // workaround the label only accepting ints (which may be only 32 bit wide) const double percent = used / (double)total * 100; const int percentInt = qMin(qRound(percent), 100); - ui->quotaProgressBar->setValue(percentInt); + _ui->quotaProgressBar->setValue(percentInt); QString usedStr = Utility::octetsToString(used); QString totalStr = Utility::octetsToString(total); QString percentStr = Utility::compactFormatDouble(percent, 1); QString toolTip = tr("%1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits.").arg(usedStr, totalStr, percentStr); - ui->quotaInfoLabel->setText(tr("%1 of %2 in use").arg(usedStr, totalStr)); - ui->quotaInfoLabel->setToolTip(toolTip); - ui->quotaProgressBar->setToolTip(toolTip); + _ui->quotaInfoLabel->setText(tr("%1 of %2 in use").arg(usedStr, totalStr)); + _ui->quotaInfoLabel->setToolTip(toolTip); + _ui->quotaProgressBar->setToolTip(toolTip); } else { - ui->quotaProgressBar->setVisible(false); - ui->quotaInfoLabel->setToolTip(QString()); + _ui->quotaProgressBar->setVisible(false); + _ui->quotaInfoLabel->setToolTip(QString()); /* -1 means not computed; -2 means unknown; -3 means unlimited (#3940)*/ if (total == 0 || total == -1) { - ui->quotaInfoLabel->setText(tr("Currently there is no storage usage information available.")); + _ui->quotaInfoLabel->setText(tr("Currently there is no storage usage information available.")); } else { QString usedStr = Utility::octetsToString(used); - ui->quotaInfoLabel->setText(tr("%1 in use").arg(usedStr)); + _ui->quotaInfoLabel->setText(tr("%1 in use").arg(usedStr)); } } } @@ -993,7 +989,7 @@ void AccountSettings::slotAccountStateChanged() { int state = _accountState ? _accountState->state() : AccountState::Disconnected; if (_accountState) { - ui->sslButton->updateAccountState(_accountState); + _ui->sslButton->updateAccountState(_accountState); AccountPtr account = _accountState->account(); QUrl safeUrl(account->url()); safeUrl.setPassword(QString()); // Remove the password from the URL to avoid showing it in the UI @@ -1052,14 +1048,14 @@ void AccountSettings::slotAccountStateChanged() } /* Allow to expand the item if the account is connected. */ - ui->_folderList->setItemsExpandable(state == AccountState::Connected); + _ui->_folderList->setItemsExpandable(state == AccountState::Connected); if (state != AccountState::Connected) { /* check if there are expanded root items, if so, close them */ int i; for (i = 0; i < _model->rowCount(); ++i) { - if (ui->_folderList->isExpanded(_model->index(i))) - ui->_folderList->setExpanded(_model->index(i), false); + if (_ui->_folderList->isExpanded(_model->index(i))) + _ui->_folderList->setExpanded(_model->index(i), false); } } else if (_model->isDirty()) { // If we connect and have pending changes, show the list. @@ -1103,21 +1099,21 @@ void AccountSettings::slotLinkActivated(const QString &link) // Make sure the folder itself is expanded Folder *f = FolderMan::instance()->folder(alias); QModelIndex folderIndx = _model->indexForPath(f, QString()); - if (!ui->_folderList->isExpanded(folderIndx)) { - ui->_folderList->setExpanded(folderIndx, true); + if (!_ui->_folderList->isExpanded(folderIndx)) { + _ui->_folderList->setExpanded(folderIndx, true); } QModelIndex indx = _model->indexForPath(f, myFolder); if (indx.isValid()) { // make sure all the parents are expanded for (auto i = indx.parent(); i.isValid(); i = i.parent()) { - if (!ui->_folderList->isExpanded(i)) { - ui->_folderList->setExpanded(i, true); + if (!_ui->_folderList->isExpanded(i)) { + _ui->_folderList->setExpanded(i, true); } } - ui->_folderList->setSelectionMode(QAbstractItemView::SingleSelection); - ui->_folderList->setCurrentIndex(indx); - ui->_folderList->scrollTo(indx); + _ui->_folderList->setSelectionMode(QAbstractItemView::SingleSelection); + _ui->_folderList->setCurrentIndex(indx); + _ui->_folderList->scrollTo(indx); } else { qCWarning(lcAccountSettings) << "Unable to find a valid index for " << myFolder; } @@ -1126,7 +1122,7 @@ void AccountSettings::slotLinkActivated(const QString &link) AccountSettings::~AccountSettings() { - delete ui; + delete _ui; } void AccountSettings::refreshSelectiveSyncStatus() @@ -1164,8 +1160,8 @@ void AccountSettings::refreshSelectiveSyncStatus() } if (msg.isEmpty()) { - ui->selectiveSyncButtons->setVisible(true); - ui->bigFolderUi->setVisible(false); + _ui->selectiveSyncButtons->setVisible(true); + _ui->bigFolderUi->setVisible(false); } else { ConfigFile cfg; QString info = !cfg.confirmExternalStorage() @@ -1174,27 +1170,27 @@ void AccountSettings::refreshSelectiveSyncStatus() ? tr("There are folders that were not synchronized because they are external storages: ") : tr("There are folders that were not synchronized because they are too big or external storages: "); - ui->selectiveSyncNotification->setText(info + msg); - ui->selectiveSyncButtons->setVisible(false); - ui->bigFolderUi->setVisible(true); + _ui->selectiveSyncNotification->setText(info + msg); + _ui->selectiveSyncButtons->setVisible(false); + _ui->bigFolderUi->setVisible(true); shouldBeVisible = true; } - ui->selectiveSyncApply->setEnabled(_model->isDirty() || !msg.isEmpty()); - bool wasVisible = !ui->selectiveSyncStatus->isHidden(); + _ui->selectiveSyncApply->setEnabled(_model->isDirty() || !msg.isEmpty()); + bool wasVisible = !_ui->selectiveSyncStatus->isHidden(); if (wasVisible != shouldBeVisible) { - QSize hint = ui->selectiveSyncStatus->sizeHint(); + QSize hint = _ui->selectiveSyncStatus->sizeHint(); if (shouldBeVisible) { - ui->selectiveSyncStatus->setMaximumHeight(0); - ui->selectiveSyncStatus->setVisible(true); + _ui->selectiveSyncStatus->setMaximumHeight(0); + _ui->selectiveSyncStatus->setVisible(true); } - auto anim = new QPropertyAnimation(ui->selectiveSyncStatus, "maximumHeight", ui->selectiveSyncStatus); + auto anim = new QPropertyAnimation(_ui->selectiveSyncStatus, "maximumHeight", _ui->selectiveSyncStatus); anim->setEndValue(shouldBeVisible ? hint.height() : 0); anim->start(QAbstractAnimation::DeleteWhenStopped); connect(anim, &QPropertyAnimation::finished, [this, shouldBeVisible]() { - ui->selectiveSyncStatus->setMaximumHeight(QWIDGETSIZE_MAX); + _ui->selectiveSyncStatus->setMaximumHeight(QWIDGETSIZE_MAX); if (!shouldBeVisible) { - ui->selectiveSyncStatus->hide(); + _ui->selectiveSyncStatus->hide(); } }); } @@ -1254,12 +1250,30 @@ bool AccountSettings::event(QEvent *e) // Expand the folder automatically only if there's only one, see #4283 // The 2 is 1 folder + 1 'add folder' button if (_model->rowCount() <= 2) { - ui->_folderList->setExpanded(_model->index(0, 0), true); + _ui->_folderList->setExpanded(_model->index(0, 0), true); } } return QWidget::event(e); } +void AccountSettings::slotStyleChanged() +{ + customizeStyle(); + + // Notify the other widgets (Dark-/Light-Mode switching) + emit styleChanged(); +} + +void AccountSettings::customizeStyle() +{ + QString msg = _ui->connectLabel->text(); + Theme::replaceLinkColorStringBackgroundAware(msg); + _ui->connectLabel->setText(msg); + + QColor color = palette().highlight().color(); + _ui->quotaProgressBar->setStyleSheet(QString::fromLatin1(progressBarStyleC).arg(color.name())); +} + } // namespace OCC #include "accountsettings.moc" diff --git a/src/gui/accountsettings.h b/src/gui/accountsettings.h index e283df26f..886ebed23 100644 --- a/src/gui/accountsettings.h +++ b/src/gui/accountsettings.h @@ -64,11 +64,13 @@ signals: void showIssuesList(AccountState *account); void requesetMnemonic(); void removeAccountFolders(AccountState *account); + void styleChanged(); public slots: void slotOpenOC(); void slotUpdateQuota(qint64, qint64); void slotAccountStateChanged(); + void slotStyleChanged(); AccountState *accountsState() { return _accountState; } @@ -129,11 +131,12 @@ private: bool event(QEvent *) override; void createAccountToolbox(); void openIgnoredFilesDialog(const QString & absFolderPath); + void customizeStyle(); /// Returns the alias of the selected folder, empty string if none QString selectedFolderAlias() const; - Ui::AccountSettings *ui; + Ui::AccountSettings *_ui; FolderStatusModel *_model; QUrl _OCUrl; diff --git a/src/gui/activityitemdelegate.cpp b/src/gui/activityitemdelegate.cpp index 1d49af7d7..2b4231e07 100644 --- a/src/gui/activityitemdelegate.cpp +++ b/src/gui/activityitemdelegate.cpp @@ -15,6 +15,7 @@ */ #include "activityitemdelegate.h" +#include "activitylistmodel.h" #include "folderstatusmodel.h" #include "folderman.h" #include "accountstate.h" @@ -26,6 +27,12 @@ #include <QPainter> #include <QApplication> +#define FIXME_USE_HIGH_DPI_RATIO +#ifdef FIXME_USE_HIGH_DPI_RATIO + // FIXME: Find a better way to calculate the text width on high-dpi displays (Retina). + #include <QDesktopWidget> +#endif + #define HASQT5_11 (QT_VERSION >= QT_VERSION_CHECK(5,11,0)) namespace OCC { @@ -40,6 +47,12 @@ int ActivityItemDelegate::_buttonHeight = 0; const QString ActivityItemDelegate::_remote_share("remote_share"); const QString ActivityItemDelegate::_call("call"); +ActivityItemDelegate::ActivityItemDelegate() + : QStyledItemDelegate() +{ + customizeStyle(); +} + int ActivityItemDelegate::iconHeight() { if (_iconHeight == 0) { @@ -89,10 +102,26 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem & int iconSize = 16; int iconOffset = qRound(fm.height() / 4.0 * 7.0); int offset = 4; + const bool isSelected = (option.state & QStyle::State_Selected); +#ifdef FIXME_USE_HIGH_DPI_RATIO + // FIXME: Find a better way to calculate the text width on high-dpi displays (Retina). + const int device_pixel_ration = QApplication::desktop()->devicePixelRatio(); + int pixel_ratio = (device_pixel_ration > 1 ? device_pixel_ration : 1); +#endif // get the data Activity::Type activityType = qvariant_cast<Activity::Type>(index.data(ActionRole)); - QIcon actionIcon = qvariant_cast<QIcon>(index.data(ActionIconRole)); + QIcon actionIcon; + const ActivityListModel::ActionIcon icn = qvariant_cast<ActivityListModel::ActionIcon>(index.data(ActionIconRole)); + switch(icn.iconType) { + case ActivityListModel::ActivityIconType::iconUseCached: actionIcon = icn.cachedIcon; break; + case ActivityListModel::ActivityIconType::iconActivity: actionIcon = (isSelected ? _iconActivity_sel : _iconActivity); break; + case ActivityListModel::ActivityIconType::iconBell: actionIcon = (isSelected ? _iconBell_sel : _iconBell); break; + case ActivityListModel::ActivityIconType::iconStateError: actionIcon = _iconStateError; break; + case ActivityListModel::ActivityIconType::iconStateWarning: actionIcon = _iconStateWarning; break; + case ActivityListModel::ActivityIconType::iconStateInfo: actionIcon = _iconStateInfo; break; + case ActivityListModel::ActivityIconType::iconStateSync: actionIcon = _iconStateSync; break; + } QString objectType = qvariant_cast<QString>(index.data(ObjectTypeRole)); QString actionText = qvariant_cast<QString>(index.data(ActionTextRole)); QString messageText = qvariant_cast<QString>(index.data(MessageRole)); @@ -116,6 +145,10 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem & actionTextBox.setTop(option.rect.top() + margin + offset/2); actionTextBox.setHeight(fm.height()); actionTextBox.setLeft(actionIconRect.right() + margin); +#ifdef FIXME_USE_HIGH_DPI_RATIO + // FIXME: Find a better way to calculate the text width on high-dpi displays (Retina). + actionTextBoxWidth *= pixel_ratio; +#endif actionTextBox.setRight(actionTextBox.left() + actionTextBoxWidth + margin); // message text rect @@ -138,11 +171,10 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem & // time box rect QRect timeBox = messageTextBox; - QString timeStr = tr("%1").arg(timeText); #if (HASQT5_11) - int timeTextWidth = fm.horizontalAdvance(timeStr); + int timeTextWidth = fm.horizontalAdvance(timeText); #else - int timeTextWidth = fm.width(timeStr); + int timeTextWidth = fm.width(timeText); #endif int timeTop = option.rect.top() + fm.height() + fm.height() + margin + offset/2; if(messageText.isEmpty() || actionText.isEmpty()) @@ -150,6 +182,10 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem & timeBox.setTop(timeTop); timeBox.setHeight(fm.height()); timeBox.setBottom(timeBox.top() + fm.height()); +#ifdef FIXME_USE_HIGH_DPI_RATIO + // FIXME: Find a better way to calculate the text width on high-dpi displays (Retina). + timeTextWidth *= pixel_ratio; +#endif timeBox.setRight(timeBox.left() + timeTextWidth + margin); // buttons - default values @@ -184,9 +220,9 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem & if(activityType == Activity::Type::NotificationType){ // Secondary will be 'Dismiss' or '...' multiple options button - secondaryButton.icon = QIcon(QLatin1String(":/client/resources/close.svg")); + secondaryButton.icon = (isSelected ? _iconClose_sel : _iconClose); if(customList.size() > 1) - secondaryButton.icon = QIcon(QLatin1String(":/client/resources/more.svg")); + secondaryButton.icon = (isSelected ? _iconMore_sel : _iconMore); secondaryButton.iconSize = QSize(iconSize, iconSize); // Primary button will be 'More Information' or 'Accept' @@ -209,7 +245,7 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem & } else if(activityType == Activity::SyncResultType){ // Secondary will be 'open file manager' with the folder icon - secondaryButton.icon = QIcon(QLatin1String(":/client/resources/folder.svg")); + secondaryButton.icon = _iconFolder; secondaryButton.iconSize = QSize(iconSize, iconSize); // Primary button will be 'open browser' @@ -230,7 +266,7 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem & } else if(activityType == Activity::SyncFileItemType){ // Secondary will be 'open file manager' with the folder icon - secondaryButton.icon = QIcon(QLatin1String(":/client/resources/folder.svg")); + secondaryButton.icon = _iconFolder; secondaryButton.iconSize = QSize(iconSize, iconSize); // No primary button on this case @@ -255,7 +291,7 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem & p.setCurrentColorGroup(QPalette::Disabled); // change pen color if the line is selected - if (option.state & QStyle::State_Selected) + if (isSelected) painter->setPen(p.color(QPalette::HighlightedText)); else painter->setPen(p.color(QPalette::Text)); @@ -269,8 +305,15 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem & painter->drawText(actionTextBox, elidedAction); // draw the buttons - if(activityType == Activity::Type::NotificationType || activityType == Activity::Type::SyncResultType) + if(activityType == Activity::Type::NotificationType || activityType == Activity::Type::SyncResultType) { + primaryButton.palette = p; + if (isSelected) + primaryButton.palette.setColor(QPalette::ButtonText, p.color(QPalette::HighlightedText)); + else + primaryButton.palette.setColor(QPalette::ButtonText, p.color(QPalette::Text)); + QApplication::style()->drawControl(QStyle::CE_PushButton, &primaryButton, painter); + } // Since they are errors on local syncing, there is nothing to do in the server if(activityType != Activity::Type::ActivityType) @@ -284,13 +327,13 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem & } // change pen color for the time - if (option.state & QStyle::State_Selected) + if (isSelected) painter->setPen(p.color(QPalette::Disabled, QPalette::HighlightedText)); else painter->setPen(p.color(QPalette::Disabled, QPalette::Text)); // draw the time - const QString elidedTime = fm.elidedText(timeStr, Qt::ElideRight, spaceLeftForText); + const QString elidedTime = fm.elidedText(timeText, Qt::ElideRight, spaceLeftForText); painter->drawText(timeBox, elidedTime); painter->restore(); @@ -333,4 +376,32 @@ bool ActivityItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, return QStyledItemDelegate::editorEvent(event, model, option, index); } +void ActivityItemDelegate::slotStyleChanged() +{ + customizeStyle(); +} + +void ActivityItemDelegate::customizeStyle() +{ + QPalette pal; + pal.setColor(QPalette::Base, QColor(0,0,0)); // use dark background colour to invert icons + + _iconClose = Theme::createColorAwareIcon(QLatin1String(":/client/resources/close.svg")); + _iconClose_sel = Theme::createColorAwareIcon(QLatin1String(":/client/resources/close.svg"), pal); + _iconMore = Theme::createColorAwareIcon(QLatin1String(":/client/resources/more.svg")); + _iconMore_sel = Theme::createColorAwareIcon(QLatin1String(":/client/resources/more.svg"), pal); + + _iconFolder = QIcon(QLatin1String(":/client/resources/folder.svg")); + + _iconActivity = Theme::createColorAwareIcon(QLatin1String(":/client/resources/activity.png")); + _iconActivity_sel = Theme::createColorAwareIcon(QLatin1String(":/client/resources/activity.png"), pal); + _iconBell = Theme::createColorAwareIcon(QLatin1String(":/client/resources/bell.svg")); + _iconBell_sel = Theme::createColorAwareIcon(QLatin1String(":/client/resources/bell.svg"), pal); + + _iconStateError = QIcon(QLatin1String(":/client/resources/state-error.svg")); + _iconStateWarning = QIcon(QLatin1String(":/client/resources/state-warning.svg")); + _iconStateInfo = QIcon(QLatin1String(":/client/resources/state-info.svg")); + _iconStateSync = QIcon(QLatin1String(":/client/resources/state-sync.svg")); +} + } // namespace OCC diff --git a/src/gui/activityitemdelegate.h b/src/gui/activityitemdelegate.h index 49ffca592..908889eee 100644 --- a/src/gui/activityitemdelegate.h +++ b/src/gui/activityitemdelegate.h @@ -43,6 +43,8 @@ public: AccountConnectedRole, SyncFileStatusRole }; + ActivityItemDelegate(); + void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const override; bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, @@ -51,11 +53,16 @@ public: static int rowHeight(); static int iconHeight(); +public slots: + void slotStyleChanged(); + signals: void primaryButtonClickedOnItemView(const QModelIndex &index); void secondaryButtonClickedOnItemView(const QModelIndex &index); private: + void customizeStyle(); + static int _margin; static int _iconHeight; static int _primaryButtonWidth; @@ -65,6 +72,23 @@ private: static int _buttonHeight; static const QString _remote_share; static const QString _call; + + QIcon _iconClose; + QIcon _iconClose_sel; + QIcon _iconMore; + QIcon _iconMore_sel; + + QIcon _iconFolder; + + QIcon _iconActivity; + QIcon _iconActivity_sel; + QIcon _iconBell; + QIcon _iconBell_sel; + + QIcon _iconStateError; + QIcon _iconStateWarning; + QIcon _iconStateInfo; + QIcon _iconStateSync; }; } // namespace OCC diff --git a/src/gui/activitylistmodel.cpp b/src/gui/activitylistmodel.cpp index eab049af7..eeb0cc836 100644 --- a/src/gui/activitylistmodel.cpp +++ b/src/gui/activitylistmodel.cpp @@ -64,10 +64,19 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const case ActivityItemDelegate::PathRole: if(!a._file.isEmpty()){ auto folder = FolderMan::instance()->folder(a._folder); - list = FolderMan::instance()->findFileInLocalFolders(folder->remotePath(), ast->account()); + QString relPath(a._file); + if(folder) relPath.prepend(folder->remotePath()); + list = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account()); if (list.count() > 0) { return QVariant(list.at(0)); } + // File does not exist anymore? Let's try to open its path + if(QFileInfo(relPath).exists()) { + list = FolderMan::instance()->findFileInLocalFolders(QFileInfo(relPath).path(), ast->account()); + if (list.count() > 0) { + return QVariant(list.at(0)); + } + } } return QVariant(); case ActivityItemDelegate::ActionsLinksRole:{ @@ -79,59 +88,60 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const } return customList; } - case ActivityItemDelegate::ActionIconRole: + case ActivityItemDelegate::ActionIconRole:{ + ActionIcon actionIcon; if(a._type == Activity::NotificationType){ QIcon cachedIcon = ServerNotificationHandler::iconCache.value(a._id); - if(!cachedIcon.isNull()) - return cachedIcon; - else return QIcon(QLatin1String(":/client/resources/bell.svg")); + if(!cachedIcon.isNull()) { + actionIcon.iconType = ActivityIconType::iconUseCached; + actionIcon.cachedIcon = cachedIcon; + } else { + actionIcon.iconType = ActivityIconType::iconBell; + } } else if(a._type == Activity::SyncResultType){ - return QIcon(QLatin1String(":/client/resources/state-error.svg")); + actionIcon.iconType = ActivityIconType::iconStateError; } else if(a._type == Activity::SyncFileItemType){ if(a._status == SyncFileItem::NormalError || a._status == SyncFileItem::FatalError || a._status == SyncFileItem::DetailError || a._status == SyncFileItem::BlacklistedError) { - return QIcon(QLatin1String(":/client/resources/state-error.svg")); + actionIcon.iconType = ActivityIconType::iconStateError; } else if(a._status == SyncFileItem::SoftError || a._status == SyncFileItem::Conflict || a._status == SyncFileItem::Restoration || a._status == SyncFileItem::FileLocked){ - return QIcon(QLatin1String(":/client/resources/state-warning.svg")); + actionIcon.iconType = ActivityIconType::iconStateWarning; } else if(a._status == SyncFileItem::FileIgnored){ - return QIcon(QLatin1String(":/client/resources/state-info.svg")); + actionIcon.iconType = ActivityIconType::iconStateInfo; + } else { + actionIcon.iconType = ActivityIconType::iconStateSync; } - return QIcon(QLatin1String(":/client/resources/state-sync.svg")); + } else { + actionIcon.iconType = ActivityIconType::iconActivity; } - return QIcon(QLatin1String(":/client/resources/activity.png")); - break; + QVariant icn; + icn.setValue(actionIcon); + return icn; + } case ActivityItemDelegate::ObjectTypeRole: return a._objectType; - break; case ActivityItemDelegate::ActionRole:{ QVariant type; type.setValue(a._type); return type; - break; } case ActivityItemDelegate::ActionTextRole: return a._subject; - break; case ActivityItemDelegate::MessageRole: return a._message; - break; case ActivityItemDelegate::LinkRole: return a._link; - break; case ActivityItemDelegate::AccountRole: return a._accName; - break; case ActivityItemDelegate::PointInTimeRole: - return Utility::timeAgoInWords(a._dateTime); - break; + return QString("%1 (%2)").arg(a._dateTime.toLocalTime().toString(Qt::DefaultLocaleShortDate), Utility::timeAgoInWords(a._dateTime.toLocalTime())); case ActivityItemDelegate::AccountConnectedRole: return (ast && ast->isConnected()); - break; default: return QVariant(); } @@ -256,7 +266,7 @@ void ActivityListModel::clearNotifications() { } void ActivityListModel::removeActivityFromActivityList(int row) { - Activity activity = _finalList.at(row); + Activity activity = _finalList.at(row); removeActivityFromActivityList(activity); combineActivityLists(); } @@ -294,18 +304,27 @@ void ActivityListModel::combineActivityLists() { ActivityList resultList; - std::sort(_notificationErrorsLists.begin(), _notificationErrorsLists.end()); - resultList.append(_notificationErrorsLists); - resultList.append(_notificationIgnoredFiles); + if(_notificationErrorsLists.count() > 0) { + std::sort(_notificationErrorsLists.begin(), _notificationErrorsLists.end()); + resultList.append(_notificationErrorsLists); + } + if(_listOfIgnoredFiles.size() > 0) + resultList.append(_notificationIgnoredFiles); - std::sort(_notificationLists.begin(), _notificationLists.end()); - resultList.append(_notificationLists); + if(_notificationLists.count() > 0) { + std::sort(_notificationLists.begin(), _notificationLists.end()); + resultList.append(_notificationLists); + } - std::sort(_syncFileItemLists.begin(), _syncFileItemLists.end()); - resultList.append(_syncFileItemLists); + if(_syncFileItemLists.count() > 0) { + std::sort(_syncFileItemLists.begin(), _syncFileItemLists.end()); + resultList.append(_syncFileItemLists); + } - std::sort(_activityLists.begin(), _activityLists.end()); - resultList.append(_activityLists); + if(_activityLists.count() > 0) { + std::sort(_activityLists.begin(), _activityLists.end()); + resultList.append(_activityLists); + } beginResetModel(); _finalList.clear(); diff --git a/src/gui/activitylistmodel.h b/src/gui/activitylistmodel.h index cbb823f1f..1ff352beb 100644 --- a/src/gui/activitylistmodel.h +++ b/src/gui/activitylistmodel.h @@ -38,6 +38,20 @@ class ActivityListModel : public QAbstractListModel { Q_OBJECT public: + enum ActivityIconType { + iconUseCached = 0, + iconActivity, + iconBell, + iconStateError, + iconStateWarning, + iconStateInfo, + iconStateSync + }; + struct ActionIcon { + ActivityIconType iconType; + QIcon cachedIcon; + }; + explicit ActivityListModel(AccountState *accountState, QWidget *parent = nullptr); QVariant data(const QModelIndex &index, int role) const override; @@ -84,4 +98,7 @@ private: int _currentItem = 0; }; } + +Q_DECLARE_METATYPE(OCC::ActivityListModel::ActionIcon) + #endif // ACTIVITYLISTMODEL_H diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index 7ce4471d3..469b0d14e 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -89,6 +89,9 @@ ActivityWidget::ActivityWidget(AccountState *accountState, QWidget *parent) this, &ActivityWidget::addError); _removeTimer.setInterval(1000); + + // Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching) + connect(this, &ActivityWidget::styleChanged, delegate, &ActivityItemDelegate::slotStyleChanged); } ActivityWidget::~ActivityWidget() @@ -176,7 +179,7 @@ void ActivityWidget::slotItemCompleted(const QString &folder, const SyncFileItem Activity activity; activity._type = Activity::SyncFileItemType; //client activity activity._status = item->_status; - activity._dateTime = QDateTime::fromString(QDateTime::currentDateTime().toString(), Qt::ISODate); + activity._dateTime = QDateTime::currentDateTime(); activity._message = item->_originalFile; activity._link = folderInstance->accountState()->account()->url(); activity._accName = folderInstance->accountState()->account()->displayName(); @@ -548,6 +551,12 @@ void ActivityWidget::slotNotifyServerFinished(const QString &reply, int replyCod qCInfo(lcActivity) << "Server Notification reply code" << replyCode << reply; } +void ActivityWidget::slotStyleChanged() +{ + // Notify the other widgets (Dark-/Light-Mode switching) + emit styleChanged(); +} + /* ==================================================================== */ ActivitySettings::ActivitySettings(AccountState *accountState, QWidget *parent) @@ -570,6 +579,9 @@ ActivitySettings::ActivitySettings(AccountState *accountState, QWidget *parent) // connect a model signal to stop the animation connect(_activityWidget, &ActivityWidget::rowsInserted, _progressIndicator, &QProgressIndicator::stopAnimation); connect(_activityWidget, &ActivityWidget::rowsInserted, this, &ActivitySettings::slotDisplayActivities); + + // Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching) + connect(this, &ActivitySettings::styleChanged, _activityWidget, &ActivityWidget::slotStyleChanged); } void ActivitySettings::slotDisplayActivities(){ @@ -628,4 +640,14 @@ bool ActivitySettings::event(QEvent *e) ActivitySettings::~ActivitySettings() { } + +void ActivitySettings::slotStyleChanged() +{ + if(_progressIndicator) + _progressIndicator->setColor(QGuiApplication::palette().color(QPalette::Text)); + + // Notify the other widgets (Dark-/Light-Mode switching) + emit styleChanged(); +} + } diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h index a51ed9346..ee271c62a 100644 --- a/src/gui/activitywidget.h +++ b/src/gui/activitywidget.h @@ -78,12 +78,14 @@ public slots: void addError(const QString &folderAlias, const QString &message, ErrorCategory category); void slotProgressInfo(const QString &folder, const ProgressInfo &progress); void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item); + void slotStyleChanged(); signals: void guiLog(const QString &, const QString &); void rowsInserted(); void hideActivityTab(bool); void sendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row); + void styleChanged(); private slots: void slotBuildNotificationDisplay(const ActivityList &list); @@ -96,6 +98,7 @@ private slots: void slotSecondaryButtonClickedOnListView(const QModelIndex &index); private: + void customizeStyle(); void showLabels(); QString timeString(QDateTime dt, QLocale::FormatType format) const; Ui::ActivityWidget *_ui; @@ -137,6 +140,7 @@ public slots: void slotRefresh(); void slotRemoveAccount(); void setNotificationRefreshInterval(std::chrono::milliseconds interval); + void slotStyleChanged(); private slots: void slotRegularNotificationCheck(); @@ -144,6 +148,7 @@ private slots: signals: void guiLog(const QString &, const QString &); + void styleChanged(); private: bool event(QEvent *e) override; diff --git a/src/gui/application.cpp b/src/gui/application.cpp index 189141595..51af86f92 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -263,6 +263,9 @@ Application::Application(int &argc, char **argv) // Cleanup at Quit. connect(this, &QCoreApplication::aboutToQuit, this, &Application::slotCleanup); + + // Allow other classes to hook into isShowingSettingsDialog() signals (re-auth widgets, for example) + connect(_gui.data(), &ownCloudGui::isShowingSettingsDialog, this, &Application::slotGuiIsShowingSettings); } Application::~Application() @@ -655,5 +658,9 @@ void Application::showSettingsDialog() _gui->slotShowSettings(); } +void Application::slotGuiIsShowingSettings() +{ + emit isShowingSettingsDialog(); +} } // namespace OCC diff --git a/src/gui/application.h b/src/gui/application.h index 77abd7a97..ac303cb4c 100644 --- a/src/gui/application.h +++ b/src/gui/application.h @@ -82,6 +82,7 @@ protected: signals: void folderRemoved(); void folderStateChanged(Folder *); + void isShowingSettingsDialog(); protected slots: void slotParseMessage(const QString &, QObject *); @@ -91,6 +92,7 @@ protected slots: void slotAccountStateAdded(AccountState *accountState); void slotAccountStateRemoved(AccountState *accountState); void slotSystemOnlineConfigurationChanged(QNetworkConfiguration); + void slotGuiIsShowingSettings(); private: void setHelp(); diff --git a/src/gui/creds/flow2auth.cpp b/src/gui/creds/flow2auth.cpp index 3a9db471a..e75a8c9bf 100644 --- a/src/gui/creds/flow2auth.cpp +++ b/src/gui/creds/flow2auth.cpp @@ -14,10 +14,12 @@ */ #include <QDesktopServices> +#include <QApplication> +#include <QClipboard> #include <QTimer> #include <QBuffer> #include "account.h" -#include "creds/flow2auth.h" +#include "flow2auth.h" #include <QJsonObject> #include <QJsonDocument> #include "theme.h" @@ -28,6 +30,17 @@ namespace OCC { Q_LOGGING_CATEGORY(lcFlow2auth, "nextcloud.sync.credentials.flow2auth", QtInfoMsg) + +Flow2Auth::Flow2Auth(Account *account, QObject *parent) + : QObject(parent) + , _account(account) + , _isBusy(false) + , _hasToken(false) +{ + _pollTimer.setInterval(1000); + QObject::connect(&_pollTimer, &QTimer::timeout, this, &Flow2Auth::slotPollTimerTimeout); +} + Flow2Auth::~Flow2Auth() { } @@ -47,7 +60,23 @@ QUrl Flow2Auth::authorisationLink() const void Flow2Auth::openBrowser() { - _pollTimer.stop(); + fetchNewToken(TokenAction::actionOpenBrowser); +} + +void Flow2Auth::copyLinkToClipboard() +{ + fetchNewToken(TokenAction::actionCopyLinkToClipboard); +} + +void Flow2Auth::fetchNewToken(const TokenAction action) +{ + if(_isBusy) + return; + + _isBusy = true; + _hasToken = false; + + emit statusChanged(PollStatus::statusFetchToken, 0); // Step 1: Initiate a login, do an anonymous POST request QUrl url = Utility::concatUrlPath(_account->url().toString(), QLatin1String("/index.php/login/v2")); @@ -59,14 +88,18 @@ void Flow2Auth::openBrowser() auto job = _account->sendRequest("POST", url, req); job->setTimeout(qMin(30 * 1000ll, job->timeoutMsec())); - QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this](QNetworkReply *reply) { + QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this, action](QNetworkReply *reply) { auto jsonData = reply->readAll(); QJsonParseError jsonParseError; QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object(); + QString pollToken, pollEndpoint, loginUrl; - QString pollToken = json.value("poll").toObject().value("token").toString(); - QString pollEndpoint = json.value("poll").toObject().value("endpoint").toString(); - QUrl loginUrl = json["login"].toString(); + if (reply->error() == QNetworkReply::NoError && jsonParseError.error == QJsonParseError::NoError + && !json.isEmpty()) { + pollToken = json.value("poll").toObject().value("token").toString(); + pollEndpoint = json.value("poll").toObject().value("endpoint").toString(); + loginUrl = json["login"].toString(); + } if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError || json.isEmpty() || pollToken.isEmpty() || pollEndpoint.isEmpty() || loginUrl.isEmpty()) { @@ -85,7 +118,9 @@ void Flow2Auth::openBrowser() errorReason = tr("The reply from the server did not contain all expected fields"); } qCWarning(lcFlow2auth) << "Error when getting the loginUrl" << json << errorReason; - emit result(Error); + emit result(Error, errorReason); + _pollTimer.stop(); + _isBusy = false; return; } @@ -99,23 +134,50 @@ void Flow2Auth::openBrowser() ConfigFile cfg; std::chrono::milliseconds polltime = cfg.remotePollInterval(); qCInfo(lcFlow2auth) << "setting remote poll timer interval to" << polltime.count() << "msec"; - _pollTimer.setInterval(polltime.count()); - QObject::connect(&_pollTimer, &QTimer::timeout, this, &Flow2Auth::slotPollTimerTimeout); - _pollTimer.start(); + _secondsInterval = (polltime.count() / 1000); + _secondsLeft = _secondsInterval; + emit statusChanged(PollStatus::statusPollCountdown, _secondsLeft); + + if(!_pollTimer.isActive()) { + _pollTimer.start(); + } - // Try to open Browser - if (!QDesktopServices::openUrl(authorisationLink())) { - // We cannot open the browser, then we claim we don't support Flow2Auth. - // Our UI callee should ask the user to copy and open the link. - emit result(NotSupported, QString()); + switch(action) + { + case actionOpenBrowser: + // Try to open Browser + if (!QDesktopServices::openUrl(authorisationLink())) { + // We cannot open the browser, then we claim we don't support Flow2Auth. + // Our UI callee will ask the user to copy and open the link. + emit result(NotSupported); + } + break; + case actionCopyLinkToClipboard: + QApplication::clipboard()->setText(authorisationLink().toString(QUrl::FullyEncoded)); + emit statusChanged(PollStatus::statusCopyLinkToClipboard, 0); + break; } + + _isBusy = false; + _hasToken = true; }); } void Flow2Auth::slotPollTimerTimeout() { - _pollTimer.stop(); + if(_isBusy || !_hasToken) + return; + + _isBusy = true; + + _secondsLeft--; + if(_secondsLeft > 0) { + emit statusChanged(PollStatus::statusPollCountdown, _secondsLeft); + _isBusy = false; + return; + } + emit statusChanged(PollStatus::statusPollNow, 0); // Step 2: Poll QNetworkRequest req; @@ -132,10 +194,15 @@ void Flow2Auth::slotPollTimerTimeout() auto jsonData = reply->readAll(); QJsonParseError jsonParseError; QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object(); - - QUrl serverUrl = json["server"].toString(); - QString loginName = json["loginName"].toString(); - QString appPassword = json["appPassword"].toString(); + QUrl serverUrl; + QString loginName, appPassword; + + if (reply->error() == QNetworkReply::NoError && jsonParseError.error == QJsonParseError::NoError + && !json.isEmpty()) { + serverUrl = json["server"].toString(); + loginName = json["loginName"].toString(); + appPassword = json["appPassword"].toString(); + } if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError || json.isEmpty() || serverUrl.isEmpty() || loginName.isEmpty() || appPassword.isEmpty()) { @@ -155,26 +222,50 @@ void Flow2Auth::slotPollTimerTimeout() } qCDebug(lcFlow2auth) << "Error when polling for the appPassword" << json << errorReason; + // We get a 404 until authentication is done, so don't show this error in the GUI. + if(reply->error() != QNetworkReply::ContentNotFoundError) + emit result(Error, errorReason); + // Forget sensitive data appPassword.clear(); loginName.clear(); // Failed: poll again - _pollTimer.start(); + _secondsLeft = _secondsInterval; + _isBusy = false; return; } + _pollTimer.stop(); + // Success qCInfo(lcFlow2auth) << "Success getting the appPassword for user: " << loginName << ", server: " << serverUrl.toString(); _account->setUrl(serverUrl); - emit result(LoggedIn, loginName, appPassword); + emit result(LoggedIn, QString(), loginName, appPassword); // Forget sensitive data appPassword.clear(); loginName.clear(); + + _loginUrl.clear(); + _pollToken.clear(); + _pollEndpoint.clear(); + + _isBusy = false; + _hasToken = false; }); } +void Flow2Auth::slotPollNow() +{ + // poll now if we're not already doing so + if(_isBusy || !_hasToken) + return; + + _secondsLeft = 1; + slotPollTimerTimeout(); +} + } // namespace OCC diff --git a/src/gui/creds/flow2auth.h b/src/gui/creds/flow2auth.h index b53834a11..e4deb203e 100644 --- a/src/gui/creds/flow2auth.h +++ b/src/gui/creds/flow2auth.h @@ -25,17 +25,23 @@ namespace OCC { * Job that does the authorization, grants and fetches the access token via Login Flow v2 * * See: https://docs.nextcloud.com/server/latest/developer_manual/client_apis/LoginFlow/index.html#login-flow-v2 - * */ class Flow2Auth : public QObject { Q_OBJECT public: - Flow2Auth(Account *account, QObject *parent) - : QObject(parent) - , _account(account) - { - } + enum TokenAction { + actionOpenBrowser = 1, + actionCopyLinkToClipboard + }; + enum PollStatus { + statusPollCountdown = 1, + statusPollNow, + statusFetchToken, + statusCopyLinkToClipboard + }; + + Flow2Auth(Account *account, QObject *parent); ~Flow2Auth(); enum Result { NotSupported, @@ -44,6 +50,7 @@ public: Q_ENUM(Result); void start(); void openBrowser(); + void copyLinkToClipboard(); QUrl authorisationLink() const; signals: @@ -51,18 +58,29 @@ signals: * The state has changed. * when logged in, appPassword has the value of the app password. */ - void result(Flow2Auth::Result result, const QString &user = QString(), const QString &appPassword = QString()); + void result(Flow2Auth::Result result, const QString &errorString = QString(), + const QString &user = QString(), const QString &appPassword = QString()); + + void statusChanged(const PollStatus status, int secondsLeft); + +public slots: + void slotPollNow(); private slots: void slotPollTimerTimeout(); private: + void fetchNewToken(const TokenAction action); + Account *_account; QUrl _loginUrl; QString _pollToken; QString _pollEndpoint; QTimer _pollTimer; + int _secondsLeft; + int _secondsInterval; + bool _isBusy; + bool _hasToken; }; - } // namespace OCC diff --git a/src/gui/creds/keychainchunk.cpp b/src/gui/creds/keychainchunk.cpp new file mode 100644 index 000000000..ceacd27d7 --- /dev/null +++ b/src/gui/creds/keychainchunk.cpp @@ -0,0 +1,221 @@ +/* + * Copyright (C) by Michael Schuster <michael@nextcloud.com> + * + * 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 "account.h" +#include "keychainchunk.h" +#include "theme.h" +#include "networkjobs.h" +#include "configfile.h" +#include "creds/abstractcredentials.h" + +using namespace QKeychain; + +namespace OCC { + +Q_LOGGING_CATEGORY(lcKeychainChunk, "nextcloud.sync.credentials.keychainchunk", QtInfoMsg) + +namespace KeychainChunk { + +#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK) +static void addSettingsToJob(Account *account, QKeychain::Job *job) +{ + Q_UNUSED(account) + auto settings = ConfigFile::settingsWithGroup(Theme::instance()->appName()); + settings->setParent(job); // make the job parent to make setting deleted properly + job->setSettings(settings.release()); +} +#endif + +/* +* Job +*/ +Job::Job(QObject *parent) + : QObject(parent) +{ + _serviceName = Theme::instance()->appName(); +} + +/* +* WriteJob +*/ +WriteJob::WriteJob(Account *account, const QString &key, const QByteArray &data, QObject *parent) + : Job(parent) +{ + _account = account; + _key = key; + + // Windows workaround: Split the private key into chunks of 2048 bytes, + // to allow 4k (4096 bit) keys to be saved (obey Windows's limits) + _chunkBuffer = data; + _chunkCount = 0; +} + +void WriteJob::start() +{ + slotWriteJobDone(nullptr); +} + +void WriteJob::slotWriteJobDone(QKeychain::Job *incomingJob) +{ + QKeychain::WritePasswordJob *writeJob = static_cast<QKeychain::WritePasswordJob *>(incomingJob); + + // errors? + if (writeJob) { + _error = writeJob->error(); + _errorString = writeJob->errorString(); + + if (writeJob->error() != NoError) { + qCWarning(lcKeychainChunk) << "Error while writing" << writeJob->key() << "chunk" << writeJob->errorString(); + _chunkBuffer.clear(); + } + } + + // write a chunk if there is any in the buffer + if (!_chunkBuffer.isEmpty()) { +#if defined(Q_OS_WIN) + // Windows workaround: Split the data into chunks of 2048 bytes, + // to allow 4k (4096 bit) keys to be saved (obey Windows's limits) + auto chunk = _chunkBuffer.left(KeychainChunk::ChunkSize); + + _chunkBuffer = _chunkBuffer.right(_chunkBuffer.size() - chunk.size()); +#else + // write full data in one chunk on non-Windows, as usual + auto chunk = _chunkBuffer; + + _chunkBuffer.clear(); +#endif + auto index = (_chunkCount++); + + // keep the limit + if (_chunkCount > KeychainChunk::MaxChunks) { + qCWarning(lcKeychainChunk) << "Maximum chunk count exceeded while writing" << writeJob->key() << "chunk" << QString::number(index) << "cutting off after" << QString::number(KeychainChunk::MaxChunks) << "chunks"; + + writeJob->deleteLater(); + + _chunkBuffer.clear(); + + emit finished(this); + return; + } + + const QString kck = AbstractCredentials::keychainKey( + _account->url().toString(), + _key + (index > 0 ? (QString(".") + QString::number(index)) : QString()), + _account->id()); + + QKeychain::WritePasswordJob *job = new QKeychain::WritePasswordJob(_serviceName); +#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK) + addSettingsToJob(_account, job); +#endif + job->setInsecureFallback(_insecureFallback); + connect(job, &QKeychain::Job::finished, this, &KeychainChunk::WriteJob::slotWriteJobDone); + // only add the key's (sub)"index" after the first element, to stay compatible with older versions and non-Windows + job->setKey(kck); + job->setBinaryData(chunk); + job->start(); + + chunk.clear(); + } else { + emit finished(this); + } + + writeJob->deleteLater(); +} + +/* +* ReadJob +*/ +ReadJob::ReadJob(Account *account, const QString &key, const bool &keychainMigration, QObject *parent) + : Job(parent) +{ + _account = account; + _key = key; + + _keychainMigration = keychainMigration; + + _chunkCount = 0; + _chunkBuffer.clear(); +} + +void ReadJob::start() +{ + _chunkCount = 0; + _chunkBuffer.clear(); + + const QString kck = AbstractCredentials::keychainKey( + _account->url().toString(), + _key, + _keychainMigration ? QString() : _account->id()); + + QKeychain::ReadPasswordJob *job = new QKeychain::ReadPasswordJob(_serviceName); +#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK) + addSettingsToJob(_account, job); +#endif + job->setInsecureFallback(_insecureFallback); + job->setKey(kck); + connect(job, &QKeychain::Job::finished, this, &KeychainChunk::ReadJob::slotReadJobDone); + job->start(); +} + +void ReadJob::slotReadJobDone(QKeychain::Job *incomingJob) +{ + // Errors or next chunk? + QKeychain::ReadPasswordJob *readJob = static_cast<QKeychain::ReadPasswordJob *>(incomingJob); + + if (readJob) { + if (readJob->error() == NoError && readJob->binaryData().length() > 0) { + _chunkBuffer.append(readJob->binaryData()); + _chunkCount++; + +#if defined(Q_OS_WIN) + // try to fetch next chunk + if (_chunkCount < KeychainChunk::MaxChunks) { + const QString kck = AbstractCredentials::keychainKey( + _account->url().toString(), + _key + QString(".") + QString::number(_chunkCount), + _keychainMigration ? QString() : _account->id()); + + QKeychain::ReadPasswordJob *job = new QKeychain::ReadPasswordJob(_serviceName); +#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK) + addSettingsToJob(_account, job); +#endif + job->setInsecureFallback(_insecureFallback); + job->setKey(kck); + connect(job, &QKeychain::Job::finished, this, &KeychainChunk::ReadJob::slotReadJobDone); + job->start(); + + readJob->deleteLater(); + return; + } else { + qCWarning(lcKeychainChunk) << "Maximum chunk count for" << readJob->key() << "reached, ignoring after" << KeychainChunk::MaxChunks; + } +#endif + } else { + if (readJob->error() != QKeychain::Error::EntryNotFound || + ((readJob->error() == QKeychain::Error::EntryNotFound) && _chunkCount == 0)) { + _error = readJob->error(); + _errorString = readJob->errorString(); + qCWarning(lcKeychainChunk) << "Unable to read" << readJob->key() << "chunk" << QString::number(_chunkCount) << readJob->errorString(); + } + } + + readJob->deleteLater(); + } + + emit finished(this); +} + +} // namespace KeychainChunk + +} // namespace OCC diff --git a/src/gui/creds/keychainchunk.h b/src/gui/creds/keychainchunk.h new file mode 100644 index 000000000..875ab5037 --- /dev/null +++ b/src/gui/creds/keychainchunk.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) by Michael Schuster <michael@nextcloud.com> + * + * 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 +#ifndef KEYCHAINCHUNK_H +#define KEYCHAINCHUNK_H + +#include <QObject> +#include <keychain.h> +#include "accountfwd.h" + +// We don't support insecure fallback +// #define KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK + +namespace OCC { + +namespace KeychainChunk { + +/* +* Workaround for Windows: +* +* Split the keychain entry's data into chunks of 2048 bytes, +* to allow 4k (4096 bit) keys / large certs to be saved (see limits in webflowcredentials.h) +*/ +static constexpr int ChunkSize = 2048; +static constexpr int MaxChunks = 10; + +/* + * @brief: Abstract base class for KeychainChunk jobs. + */ +class Job : public QObject { + Q_OBJECT +public: + Job(QObject *parent = nullptr); + + const QKeychain::Error error() const { + return _error; + } + const QString errorString() const { + return _errorString; + } + + QByteArray binaryData() const { + return _chunkBuffer; + } + + const bool insecureFallback() const { + return _insecureFallback; + } + +// If we use it but don't support insecure fallback, give us nice compilation errors ;p +#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK) + void setInsecureFallback(const bool &insecureFallback) + { + _insecureFallback = insecureFallback; + } +#endif + +protected: + QString _serviceName; + Account *_account; + QString _key; + bool _insecureFallback = false; + bool _keychainMigration = false; + + QKeychain::Error _error = QKeychain::NoError; + QString _errorString; + + int _chunkCount = 0; + QByteArray _chunkBuffer; +}; // class Job + +/* +* @brief: Simple wrapper class for QKeychain::WritePasswordJob, splits too large keychain entry's data into chunks on Windows +*/ +class WriteJob : public KeychainChunk::Job { + Q_OBJECT +public: + WriteJob(Account *account, const QString &key, const QByteArray &data, QObject *parent = nullptr); + void start(); + +signals: + void finished(KeychainChunk::WriteJob *incomingJob); + +private slots: + void slotWriteJobDone(QKeychain::Job *incomingJob); +}; // class WriteJob + +/* +* @brief: Simple wrapper class for QKeychain::ReadPasswordJob, splits too large keychain entry's data into chunks on Windows +*/ +class ReadJob : public KeychainChunk::Job { + Q_OBJECT +public: + ReadJob(Account *account, const QString &key, const bool &keychainMigration, QObject *parent = nullptr); + void start(); + +signals: + void finished(KeychainChunk::ReadJob *incomingJob); + +private slots: + void slotReadJobDone(QKeychain::Job *incomingJob); +}; // class ReadJob + +} // namespace KeychainChunk + +} // namespace OCC + +#endif // KEYCHAINCHUNK_H diff --git a/src/gui/creds/webflowcredentials.cpp b/src/gui/creds/webflowcredentials.cpp index 008e119f3..4993a0dd0 100644 --- a/src/gui/creds/webflowcredentials.cpp +++ b/src/gui/creds/webflowcredentials.cpp @@ -18,6 +18,7 @@ #include "theme.h" #include "wizard/webview.h" #include "webflowcredentialsdialog.h" +#include "keychainchunk.h" using namespace QKeychain; @@ -75,6 +76,7 @@ private: QPointer<const WebFlowCredentials> _cred; }; +#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK) static void addSettingsToJob(Account *account, QKeychain::Job *job) { Q_UNUSED(account) @@ -82,6 +84,7 @@ static void addSettingsToJob(Account *account, QKeychain::Job *job) settings->setParent(job); // make the job parent to make setting deleted properly job->setSettings(settings.release()); } +#endif WebFlowCredentials::WebFlowCredentials() : _ready(false) @@ -170,6 +173,7 @@ void WebFlowCredentials::askFromUser() { _askDialog->show(); connect(_askDialog, &WebFlowCredentialsDialog::urlCatched, this, &WebFlowCredentials::slotAskFromUserCredentialsProvided); + connect(_askDialog, &WebFlowCredentialsDialog::onClose, this, &WebFlowCredentials::slotAskFromUserCancelled); }); job->start(); @@ -205,10 +209,18 @@ void WebFlowCredentials::slotAskFromUserCredentialsProvided(const QString &user, emit asked(); _askDialog->close(); - delete _askDialog; + _askDialog->deleteLater(); _askDialog = nullptr; } +void WebFlowCredentials::slotAskFromUserCancelled() { + qCDebug(lcWebFlowCredentials()) << "User cancelled reauth!"; + + emit asked(); + + _askDialog->deleteLater(); + _askDialog = nullptr; +} bool WebFlowCredentials::stillValid(QNetworkReply *reply) { if (reply->error() != QNetworkReply::NoError) { @@ -229,86 +241,32 @@ void WebFlowCredentials::persist() { // write cert if there is one if (!_clientSslCertificate.isNull()) { - WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName()); - addSettingsToJob(_account, job); - job->setInsecureFallback(false); - connect(job, &Job::finished, this, &WebFlowCredentials::slotWriteClientCertPEMJobDone); - job->setKey(keychainKey(_account->url().toString(), _user + clientCertificatePEMC, _account->id())); - job->setBinaryData(_clientSslCertificate.toPem()); + auto *job = new KeychainChunk::WriteJob(_account, + _user + clientCertificatePEMC, + _clientSslCertificate.toPem()); + connect(job, &KeychainChunk::WriteJob::finished, this, &WebFlowCredentials::slotWriteClientCertPEMJobDone); job->start(); } else { // no cert, just write credentials - slotWriteClientCertPEMJobDone(); + slotWriteClientCertPEMJobDone(nullptr); } } -void WebFlowCredentials::slotWriteClientCertPEMJobDone() +void WebFlowCredentials::slotWriteClientCertPEMJobDone(KeychainChunk::WriteJob *writeJob) { + if(writeJob) + writeJob->deleteLater(); + // write ssl key if there is one if (!_clientSslKey.isNull()) { - // Windows workaround: Split the private key into chunks of 2048 bytes, - // to allow 4k (4096 bit) keys to be saved (obey Windows's limits) - _clientSslKeyChunkBufferPEM = _clientSslKey.toPem(); - _clientSslKeyChunkCount = 0; - - writeSingleClientKeyChunkPEM(nullptr); - } else { - // no key, just write credentials - slotWriteClientKeyPEMJobDone(); - } -} - -void WebFlowCredentials::writeSingleClientKeyChunkPEM(QKeychain::Job *incomingJob) -{ - // errors? - if (incomingJob) { - WritePasswordJob *writeJob = static_cast<WritePasswordJob *>(incomingJob); - - if (writeJob->error() != NoError) { - qCWarning(lcWebFlowCredentials) << "Error while writing client CA key chunk" << writeJob->errorString(); - - _clientSslKeyChunkBufferPEM.clear(); - } - } - - // write a key chunk if there is any in the buffer - if (!_clientSslKeyChunkBufferPEM.isEmpty()) { -#if defined(Q_OS_WIN) - // Windows workaround: Split the private key into chunks of 2048 bytes, - // to allow 4k (4096 bit) keys to be saved (obey Windows's limits) - auto chunk = _clientSslKeyChunkBufferPEM.left(_clientSslKeyChunkSize); - - _clientSslKeyChunkBufferPEM = _clientSslKeyChunkBufferPEM.right(_clientSslKeyChunkBufferPEM.size() - chunk.size()); -#else - // write full key in one slot on non-Windows, as usual - auto chunk = _clientSslKeyChunkBufferPEM; - - _clientSslKeyChunkBufferPEM.clear(); -#endif - auto index = (_clientSslKeyChunkCount++); - - // keep the limit - if (_clientSslKeyChunkCount > _clientSslKeyMaxChunks) { - qCWarning(lcWebFlowCredentials) << "Maximum client key chunk count exceeded while writing slot" << QString::number(index) << "cutting off after" << QString::number(_clientSslKeyMaxChunks) << "chunks"; - - _clientSslKeyChunkBufferPEM.clear(); - - slotWriteClientKeyPEMJobDone(); - return; - } - - WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName()); - addSettingsToJob(_account, job); - job->setInsecureFallback(false); - connect(job, &Job::finished, this, &WebFlowCredentials::writeSingleClientKeyChunkPEM); - // only add the key's (sub)"index" after the first element, to stay compatible with older versions and non-Windows - job->setKey(keychainKey(_account->url().toString(), _user + clientKeyPEMC + (index > 0 ? (QString(".") + QString::number(index)) : QString()), _account->id())); - job->setBinaryData(chunk); + auto *job = new KeychainChunk::WriteJob(_account, + _user + clientKeyPEMC, + _clientSslKey.toPem()); + connect(job, &KeychainChunk::WriteJob::finished, this, &WebFlowCredentials::slotWriteClientKeyPEMJobDone); job->start(); - - chunk.clear(); } else { - slotWriteClientKeyPEMJobDone(); + // no key, just write credentials + slotWriteClientKeyPEMJobDone(nullptr); } } @@ -331,20 +289,21 @@ void WebFlowCredentials::writeSingleClientCaCertPEM() return; } - WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName()); - addSettingsToJob(_account, job); - job->setInsecureFallback(false); - connect(job, &Job::finished, this, &WebFlowCredentials::slotWriteClientCaCertsPEMJobDone); - job->setKey(keychainKey(_account->url().toString(), _user + clientCaCertificatePEMC + QString::number(index), _account->id())); - job->setBinaryData(cert.toPem()); + auto *job = new KeychainChunk::WriteJob(_account, + _user + clientCaCertificatePEMC + QString::number(index), + cert.toPem()); + connect(job, &KeychainChunk::WriteJob::finished, this, &WebFlowCredentials::slotWriteClientCaCertsPEMJobDone); job->start(); } else { slotWriteClientCaCertsPEMJobDone(nullptr); } } -void WebFlowCredentials::slotWriteClientKeyPEMJobDone() +void WebFlowCredentials::slotWriteClientKeyPEMJobDone(KeychainChunk::WriteJob *writeJob) { + if(writeJob) + writeJob->deleteLater(); + _clientSslCaCertificatesWriteQueue.clear(); // write ca certs if there are any @@ -359,16 +318,16 @@ void WebFlowCredentials::slotWriteClientKeyPEMJobDone() } } -void WebFlowCredentials::slotWriteClientCaCertsPEMJobDone(QKeychain::Job *incomingJob) +void WebFlowCredentials::slotWriteClientCaCertsPEMJobDone(KeychainChunk::WriteJob *writeJob) { // errors / next ca cert? - if (incomingJob && !_clientSslCaCertificates.isEmpty()) { - WritePasswordJob *writeJob = static_cast<WritePasswordJob *>(incomingJob); - + if (writeJob && !_clientSslCaCertificates.isEmpty()) { if (writeJob->error() != NoError) { qCWarning(lcWebFlowCredentials) << "Error while writing client CA cert" << writeJob->errorString(); } + writeJob->deleteLater(); + if (!_clientSslCaCertificatesWriteQueue.isEmpty()) { // next ca cert writeSingleClientCaCertPEM(); @@ -378,7 +337,9 @@ void WebFlowCredentials::slotWriteClientCaCertsPEMJobDone(QKeychain::Job *incomi // done storing ca certs, time for the password WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName()); +#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK) addSettingsToJob(_account, job); +#endif job->setInsecureFallback(false); connect(job, &Job::finished, this, &WebFlowCredentials::slotWriteJobDone); job->setKey(keychainKey(_account->url().toString(), _user, _account->id())); @@ -428,6 +389,10 @@ void WebFlowCredentials::forgetSensitiveData() { DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName()); job->setInsecureFallback(false); job->setKey(kck); + connect(job, &Job::finished, this, [](QKeychain::Job *job) { + DeletePasswordJob *djob = qobject_cast<DeletePasswordJob *>(job); + djob->deleteLater(); + }); job->start(); invalidateToken(); @@ -478,29 +443,23 @@ void WebFlowCredentials::slotFinished(QNetworkReply *reply) { void WebFlowCredentials::fetchFromKeychainHelper() { // Read client cert from keychain - const QString kck = keychainKey( - _account->url().toString(), - _user + clientCertificatePEMC, - _keychainMigration ? QString() : _account->id()); - - ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName()); - addSettingsToJob(_account, job); - job->setInsecureFallback(false); - job->setKey(kck); - connect(job, &Job::finished, this, &WebFlowCredentials::slotReadClientCertPEMJobDone); + auto *job = new KeychainChunk::ReadJob(_account, + _user + clientCertificatePEMC, + _keychainMigration); + connect(job, &KeychainChunk::ReadJob::finished, this, &WebFlowCredentials::slotReadClientCertPEMJobDone); job->start(); } -void WebFlowCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incomingJob) +void WebFlowCredentials::slotReadClientCertPEMJobDone(KeychainChunk::ReadJob *readJob) { #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) - Q_ASSERT(!incomingJob->insecureFallback()); // If insecureFallback is set, the next test would be pointless - if (_retryOnKeyChainError && (incomingJob->error() == QKeychain::NoBackendAvailable - || incomingJob->error() == QKeychain::OtherError)) { + Q_ASSERT(!readJob->insecureFallback()); // If insecureFallback is set, the next test would be pointless + if (_retryOnKeyChainError && (readJob->error() == QKeychain::NoBackendAvailable + || readJob->error() == QKeychain::OtherError)) { // Could be that the backend was not yet available. Wait some extra seconds. // (Issues #4274 and #6522) // (For kwallet, the error is OtherError instead of NoBackendAvailable, maybe a bug in QtKeychain) - qCInfo(lcWebFlowCredentials) << "Backend unavailable (yet?) Retrying in a few seconds." << incomingJob->errorString(); + qCInfo(lcWebFlowCredentials) << "Backend unavailable (yet?) Retrying in a few seconds." << readJob->errorString(); QTimer::singleShot(10000, this, &WebFlowCredentials::fetchFromKeychainHelper); _retryOnKeyChainError = false; return; @@ -509,7 +468,6 @@ void WebFlowCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incomingJo #endif // Store PEM in memory - ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(incomingJob); if (readJob->error() == NoError && readJob->binaryData().length() > 0) { QList<QSslCertificate> sslCertificateList = QSslCertificate::fromData(readJob->binaryData(), QSsl::Pem); if (sslCertificateList.length() >= 1) { @@ -517,79 +475,40 @@ void WebFlowCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incomingJo } } - // Load key too - _clientSslKeyChunkCount = 0; - _clientSslKeyChunkBufferPEM.clear(); - - const QString kck = keychainKey( - _account->url().toString(), - _user + clientKeyPEMC, - _keychainMigration ? QString() : _account->id()); + readJob->deleteLater(); - ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName()); - addSettingsToJob(_account, job); - job->setInsecureFallback(false); - job->setKey(kck); - connect(job, &Job::finished, this, &WebFlowCredentials::slotReadClientKeyPEMJobDone); + // Load key too + auto *job = new KeychainChunk::ReadJob(_account, + _user + clientKeyPEMC, + _keychainMigration); + connect(job, &KeychainChunk::ReadJob::finished, this, &WebFlowCredentials::slotReadClientKeyPEMJobDone); job->start(); } -void WebFlowCredentials::slotReadClientKeyPEMJobDone(QKeychain::Job *incomingJob) +void WebFlowCredentials::slotReadClientKeyPEMJobDone(KeychainChunk::ReadJob *readJob) { - // Errors or next key chunk? - ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(incomingJob); - - if (readJob) { - if (readJob->error() == NoError && readJob->binaryData().length() > 0) { - _clientSslKeyChunkBufferPEM.append(readJob->binaryData()); - _clientSslKeyChunkCount++; - -#if defined(Q_OS_WIN) - // try to fetch next chunk - if (_clientSslKeyChunkCount < _clientSslKeyMaxChunks) { - const QString kck = keychainKey( - _account->url().toString(), - _user + clientKeyPEMC + QString(".") + QString::number(_clientSslKeyChunkCount), - _keychainMigration ? QString() : _account->id()); - - ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName()); - addSettingsToJob(_account, job); - job->setInsecureFallback(false); - job->setKey(kck); - connect(job, &Job::finished, this, &WebFlowCredentials::slotReadClientKeyPEMJobDone); - job->start(); - - return; - } else { - qCWarning(lcWebFlowCredentials) << "Maximum client key chunk count reached, ignoring after" << _clientSslKeyMaxChunks; - } -#endif - } else { - if (readJob->error() != QKeychain::Error::EntryNotFound || - ((readJob->error() == QKeychain::Error::EntryNotFound) && _clientSslKeyChunkCount == 0)) { - qCWarning(lcWebFlowCredentials) << "Unable to read client key chunk slot" << QString::number(_clientSslKeyChunkCount) << readJob->errorString(); - } - } - } - // Store key in memory - if (_clientSslKeyChunkBufferPEM.size() > 0) { + if (readJob->error() == NoError && readJob->binaryData().length() > 0) { + QByteArray clientKeyPEM = readJob->binaryData(); // FIXME Unfortunately Qt has a bug and we can't just use QSsl::Opaque to let it // load whatever we have. So we try until it works. - _clientSslKey = QSslKey(_clientSslKeyChunkBufferPEM, QSsl::Rsa); + _clientSslKey = QSslKey(clientKeyPEM, QSsl::Rsa); if (_clientSslKey.isNull()) { - _clientSslKey = QSslKey(_clientSslKeyChunkBufferPEM, QSsl::Dsa); + _clientSslKey = QSslKey(clientKeyPEM, QSsl::Dsa); } if (_clientSslKey.isNull()) { - _clientSslKey = QSslKey(_clientSslKeyChunkBufferPEM, QSsl::Ec); + _clientSslKey = QSslKey(clientKeyPEM, QSsl::Ec); } if (_clientSslKey.isNull()) { qCWarning(lcWebFlowCredentials) << "Could not load SSL key into Qt!"; } - // clear key chunk buffer, but don't set _clientSslKeyChunkCount to zero because we need it for deleteKeychainEntries - _clientSslKeyChunkBufferPEM.clear(); + clientKeyPEM.clear(); + } else { + qCWarning(lcWebFlowCredentials) << "Unable to read client key" << readJob->errorString(); } + readJob->deleteLater(); + // Start fetching client CA certs _clientSslCaCertificates.clear(); @@ -600,16 +519,10 @@ void WebFlowCredentials::readSingleClientCaCertPEM() { // try to fetch a client ca cert if (_clientSslCaCertificates.count() < _clientSslCaCertificatesMaxCount) { - const QString kck = keychainKey( - _account->url().toString(), - _user + clientCaCertificatePEMC + QString::number(_clientSslCaCertificates.count()), - _keychainMigration ? QString() : _account->id()); - - ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName()); - addSettingsToJob(_account, job); - job->setInsecureFallback(false); - job->setKey(kck); - connect(job, &Job::finished, this, &WebFlowCredentials::slotReadClientCaCertsPEMJobDone); + auto *job = new KeychainChunk::ReadJob(_account, + _user + clientCaCertificatePEMC + QString::number(_clientSslCaCertificates.count()), + _keychainMigration); + connect(job, &KeychainChunk::ReadJob::finished, this, &WebFlowCredentials::slotReadClientCaCertsPEMJobDone); job->start(); } else { qCWarning(lcWebFlowCredentials) << "Maximum client CA cert count exceeded while reading, ignoring after" << _clientSslCaCertificatesMaxCount; @@ -618,10 +531,8 @@ void WebFlowCredentials::readSingleClientCaCertPEM() } } -void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(QKeychain::Job *incomingJob) { - // Store key in memory - ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(incomingJob); - +void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(KeychainChunk::ReadJob *readJob) { + // Store cert in memory if (readJob) { if (readJob->error() == NoError && readJob->binaryData().length() > 0) { QList<QSslCertificate> sslCertificateList = QSslCertificate::fromData(readJob->binaryData(), QSsl::Pem); @@ -629,6 +540,8 @@ void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(QKeychain::Job *incomin _clientSslCaCertificates.append(sslCertificateList.at(0)); } + readJob->deleteLater(); + // try next cert readSingleClientCaCertPEM(); return; @@ -638,6 +551,8 @@ void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(QKeychain::Job *incomin qCWarning(lcWebFlowCredentials) << "Unable to read client CA cert slot" << QString::number(_clientSslCaCertificates.count()) << readJob->errorString(); } } + + readJob->deleteLater(); } // Now fetch the actual server password @@ -647,7 +562,9 @@ void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(QKeychain::Job *incomin _keychainMigration ? QString() : _account->id()); ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName()); +#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK) addSettingsToJob(_account, job); +#endif job->setInsecureFallback(false); job->setKey(kck); connect(job, &Job::finished, this, &WebFlowCredentials::slotReadPasswordJobDone); @@ -655,7 +572,7 @@ void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(QKeychain::Job *incomin } void WebFlowCredentials::slotReadPasswordJobDone(Job *incomingJob) { - QKeychain::ReadPasswordJob *job = static_cast<ReadPasswordJob *>(incomingJob); + QKeychain::ReadPasswordJob *job = qobject_cast<ReadPasswordJob *>(incomingJob); QKeychain::Error error = job->error(); // If we could not find the entry try the old entries @@ -678,6 +595,8 @@ void WebFlowCredentials::slotReadPasswordJobDone(Job *incomingJob) { } emit fetched(); + job->deleteLater(); + // If keychain data was read from legacy location, wipe these entries and store new ones if (_keychainMigration && _ready) { _keychainMigration = false; @@ -688,13 +607,20 @@ void WebFlowCredentials::slotReadPasswordJobDone(Job *incomingJob) { } void WebFlowCredentials::deleteKeychainEntries(bool oldKeychainEntries) { - auto startDeleteJob = [this, oldKeychainEntries](QString user) { + auto startDeleteJob = [this, oldKeychainEntries](QString key) { DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName()); +#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK) addSettingsToJob(_account, job); +#endif job->setInsecureFallback(false); job->setKey(keychainKey(_account->url().toString(), - user, + key, oldKeychainEntries ? QString() : _account->id())); + + connect(job, &Job::finished, this, [](QKeychain::Job *job) { + DeletePasswordJob *djob = qobject_cast<DeletePasswordJob *>(job); + djob->deleteLater(); + }); job->start(); }; @@ -719,9 +645,17 @@ void WebFlowCredentials::deleteKeychainEntries(bool oldKeychainEntries) { } #if defined(Q_OS_WIN) - // also delete key sub-chunks (Windows workaround) - for (auto i = 1; i < _clientSslKeyChunkCount; i++) { - startDeleteJob(_user + clientKeyPEMC + QString(".") + QString::number(i)); + // Also delete key / cert sub-chunks (Windows workaround) + // The first chunk (0) has no suffix, to stay compatible with older versions and non-Windows + for (auto chunk = 1; chunk < KeychainChunk::MaxChunks; chunk++) { + const QString strChunkSuffix = QString(".") + QString::number(chunk); + + startDeleteJob(_user + clientKeyPEMC + strChunkSuffix); + startDeleteJob(_user + clientCertificatePEMC + strChunkSuffix); + + for (auto i = 0; i < _clientSslCaCertificates.count(); i++) { + startDeleteJob(_user + clientCaCertificatePEMC + QString::number(i)); + } } #endif // FIXME MS@2019-12-07 --> @@ -729,4 +663,4 @@ void WebFlowCredentials::deleteKeychainEntries(bool oldKeychainEntries) { // <-- FIXME MS@2019-12-07 } -} +} // namespace OCC diff --git a/src/gui/creds/webflowcredentials.h b/src/gui/creds/webflowcredentials.h index bb09bf57c..4b2414b01 100644 --- a/src/gui/creds/webflowcredentials.h +++ b/src/gui/creds/webflowcredentials.h @@ -19,6 +19,11 @@ namespace QKeychain { namespace OCC { +namespace KeychainChunk { + class ReadJob; + class WriteJob; +} + class WebFlowCredentialsDialog; class WebFlowCredentials : public AbstractCredentials @@ -61,15 +66,16 @@ private slots: void slotFinished(QNetworkReply *reply); void slotAskFromUserCredentialsProvided(const QString &user, const QString &pass, const QString &host); + void slotAskFromUserCancelled(); - void slotReadClientCertPEMJobDone(QKeychain::Job *incomingJob); - void slotReadClientKeyPEMJobDone(QKeychain::Job *incomingJob); - void slotReadClientCaCertsPEMJobDone(QKeychain::Job *incommingJob); + void slotReadClientCertPEMJobDone(KeychainChunk::ReadJob *readJob); + void slotReadClientKeyPEMJobDone(KeychainChunk::ReadJob *readJob); + void slotReadClientCaCertsPEMJobDone(KeychainChunk::ReadJob *readJob); void slotReadPasswordJobDone(QKeychain::Job *incomingJob); - void slotWriteClientCertPEMJobDone(); - void slotWriteClientKeyPEMJobDone(); - void slotWriteClientCaCertsPEMJobDone(QKeychain::Job *incomingJob); + void slotWriteClientCertPEMJobDone(KeychainChunk::WriteJob *writeJob); + void slotWriteClientKeyPEMJobDone(KeychainChunk::WriteJob *writeJob); + void slotWriteClientCaCertsPEMJobDone(KeychainChunk::WriteJob *writeJob); void slotWriteJobDone(QKeychain::Job *); private: @@ -91,19 +97,6 @@ private: static constexpr int _clientSslCaCertificatesMaxCount = 10; QQueue<QSslCertificate> _clientSslCaCertificatesWriteQueue; - /* - * Workaround: ...and this time only on Windows: - * - * Split the private key into chunks of 2048 bytes, - * to allow 4k (4096 bit) keys to be saved (see limits above) - */ - void writeSingleClientKeyChunkPEM(QKeychain::Job *incomingJob); - - static constexpr int _clientSslKeyChunkSize = 2048; - static constexpr int _clientSslKeyMaxChunks = 10; - int _clientSslKeyChunkCount = 0; - QByteArray _clientSslKeyChunkBufferPEM; - protected: /** Reads data from keychain locations * @@ -134,6 +127,6 @@ protected: WebFlowCredentialsDialog *_askDialog; }; -} +} // namespace OCC #endif // WEBFLOWCREDENTIALS_H diff --git a/src/gui/creds/webflowcredentialsdialog.cpp b/src/gui/creds/webflowcredentialsdialog.cpp index 9971f8f34..ea8d29e79 100644 --- a/src/gui/creds/webflowcredentialsdialog.cpp +++ b/src/gui/creds/webflowcredentialsdialog.cpp @@ -4,6 +4,9 @@ #include <QLabel> #include "theme.h" +#include "application.h" +#include "owncloudgui.h" +#include "headerbanner.h" #include "wizard/owncloudwizardcommon.h" #include "wizard/webview.h" #include "wizard/flow2authwidget.h" @@ -19,31 +22,59 @@ WebFlowCredentialsDialog::WebFlowCredentialsDialog(Account *account, bool useFlo setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); _layout = new QVBoxLayout(this); + int spacing = _layout->spacing(); + int margin = _layout->margin(); + _layout->setSpacing(0); + _layout->setMargin(0); + + if(_useFlow2) { + _headerBanner = new HeaderBanner(this); + _layout->addWidget(_headerBanner); + Theme *theme = Theme::instance(); + _headerBanner->setup(tr("Log in"), theme->wizardHeaderLogo(), theme->wizardHeaderBanner(), + Qt::AutoText, QString::fromLatin1("color:#fff;")); + } + + _containerLayout = new QVBoxLayout(this); + _containerLayout->setSpacing(spacing); + _containerLayout->setMargin(margin); - //QString msg = tr("You have been logged out of %1 as user %2, please login again") - // .arg(_account->displayName(), _user); _infoLabel = new QLabel(); - _layout->addWidget(_infoLabel); + _containerLayout->addWidget(_infoLabel); if (_useFlow2) { - _flow2AuthWidget = new Flow2AuthWidget(account); - _layout->addWidget(_flow2AuthWidget); + _flow2AuthWidget = new Flow2AuthWidget(); + _containerLayout->addWidget(_flow2AuthWidget); + + connect(_flow2AuthWidget, &Flow2AuthWidget::authResult, this, &WebFlowCredentialsDialog::slotFlow2AuthResult); + + // Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching) + connect(this, &WebFlowCredentialsDialog::styleChanged, _flow2AuthWidget, &Flow2AuthWidget::slotStyleChanged); - connect(_flow2AuthWidget, &Flow2AuthWidget::urlCatched, this, &WebFlowCredentialsDialog::urlCatched); + // allow Flow2 page to poll on window activation + connect(this, &WebFlowCredentialsDialog::onActivate, _flow2AuthWidget, &Flow2AuthWidget::slotPollNow); + + _flow2AuthWidget->startAuth(account); } else { _webView = new WebView(); - _layout->addWidget(_webView); + _containerLayout->addWidget(_webView); connect(_webView, &WebView::urlCatched, this, &WebFlowCredentialsDialog::urlCatched); } + auto app = static_cast<Application *>(qApp); + connect(app, &Application::isShowingSettingsDialog, this, &WebFlowCredentialsDialog::slotShowSettingsDialog); + _errorLabel = new QLabel(); _errorLabel->hide(); - _layout->addWidget(_errorLabel); + _containerLayout->addWidget(_errorLabel); WizardCommon::initErrorLabel(_errorLabel); + _layout->addLayout(_containerLayout); setLayout(_layout); + + customizeStyle(); } void WebFlowCredentialsDialog::closeEvent(QCloseEvent* e) { @@ -52,11 +83,17 @@ void WebFlowCredentialsDialog::closeEvent(QCloseEvent* e) { if (_webView) { // Force calling WebView::~WebView() earlier so that _profile and _page are // deleted in the correct order. - delete _webView; + _webView->deleteLater(); + _webView = nullptr; } - if (_flow2AuthWidget) - delete _flow2AuthWidget; + if (_flow2AuthWidget) { + _flow2AuthWidget->resetAuth(); + _flow2AuthWidget->deleteLater(); + _flow2AuthWidget = nullptr; + } + + emit onClose(); } void WebFlowCredentialsDialog::setUrl(const QUrl &url) { @@ -69,6 +106,9 @@ void WebFlowCredentialsDialog::setInfo(const QString &msg) { } void WebFlowCredentialsDialog::setError(const QString &error) { + // bring window to top + slotShowSettingsDialog(); + if (_useFlow2 && _flow2AuthWidget) { _flow2AuthWidget->setError(error); return; @@ -82,4 +122,49 @@ void WebFlowCredentialsDialog::setError(const QString &error) { } } +void WebFlowCredentialsDialog::changeEvent(QEvent *e) +{ + switch (e->type()) { + case QEvent::StyleChange: + case QEvent::PaletteChange: + case QEvent::ThemeChange: + customizeStyle(); + + // Notify the other widgets (Dark-/Light-Mode switching) + emit styleChanged(); + break; + case QEvent::ActivationChange: + if(isActiveWindow()) + emit onActivate(); + break; + default: + break; + } + + QDialog::changeEvent(e); } + +void WebFlowCredentialsDialog::customizeStyle() +{ + // HINT: Customize dialog's own style here, if necessary in the future (Dark-/Light-Mode switching) +} + +void WebFlowCredentialsDialog::slotShowSettingsDialog() +{ + // bring window to top but slightly delay, to avoid being hidden behind the SettingsDialog + QTimer::singleShot(100, this, [this] { + ownCloudGui::raiseDialog(this); + }); +} + +void WebFlowCredentialsDialog::slotFlow2AuthResult(Flow2Auth::Result r, const QString &errorString, const QString &user, const QString &appPassword) +{ + if(r == Flow2Auth::LoggedIn) { + emit urlCatched(user, appPassword, QString()); + } else { + // bring window to top + slotShowSettingsDialog(); + } +} + +} // namespace OCC diff --git a/src/gui/creds/webflowcredentialsdialog.h b/src/gui/creds/webflowcredentialsdialog.h index a540f0edb..50ed7088d 100644 --- a/src/gui/creds/webflowcredentialsdialog.h +++ b/src/gui/creds/webflowcredentialsdialog.h @@ -5,12 +5,14 @@ #include <QUrl> #include "accountfwd.h" +#include "creds/flow2auth.h" class QLabel; class QVBoxLayout; namespace OCC { +class HeaderBanner; class WebView; class Flow2AuthWidget; @@ -30,11 +32,21 @@ public: protected: void closeEvent(QCloseEvent * e) override; + void changeEvent(QEvent *) override; + +public slots: + void slotFlow2AuthResult(Flow2Auth::Result, const QString &errorString, const QString &user, const QString &appPassword); + void slotShowSettingsDialog(); signals: void urlCatched(const QString user, const QString pass, const QString host); + void styleChanged(); + void onActivate(); + void onClose(); private: + void customizeStyle(); + bool _useFlow2; Flow2AuthWidget *_flow2AuthWidget; @@ -43,8 +55,10 @@ private: QLabel *_errorLabel; QLabel *_infoLabel; QVBoxLayout *_layout; + QVBoxLayout *_containerLayout; + HeaderBanner *_headerBanner; }; -} +} // namespace OCC #endif // WEBFLOWCREDENTIALSDIALOG_H diff --git a/src/gui/folderstatusdelegate.cpp b/src/gui/folderstatusdelegate.cpp index 337530da9..4b8899a05 100644 --- a/src/gui/folderstatusdelegate.cpp +++ b/src/gui/folderstatusdelegate.cpp @@ -40,7 +40,7 @@ namespace OCC { FolderStatusDelegate::FolderStatusDelegate() : QStyledItemDelegate() { - m_moreIcon = QIcon(QLatin1String(":/client/resources/more.svg")); + customizeStyle(); } QString FolderStatusDelegate::addFolderText() @@ -273,6 +273,11 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & rect.setHeight(texts.count() * subFm.height() + 2 * margin); rect.setRight(option.rect.right() - margin); + // save previous state to not mess up colours with the background (fixes issue: https://github.com/nextcloud/desktop/issues/1237) + auto oldBrush = painter->brush(); + auto oldPen = painter->pen(); + auto oldFont = painter->font(); + painter->setBrush(color); painter->setPen(QColor(0xaa, 0xaa, 0xaa)); painter->drawRoundedRect(QStyle::visualRect(option.direction, option.rect, rect), @@ -290,6 +295,11 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & textRect.translate(0, textRect.height()); } + // restore previous state + painter->setBrush(oldBrush); + painter->setPen(oldPen); + painter->setFont(oldFont); + h = rect.bottom() + margin; }; @@ -349,7 +359,7 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & btnOpt.arrowType = Qt::NoArrow; btnOpt.subControls = QStyle::SC_ToolButton; btnOpt.rect = optionsButtonVisualRect; - btnOpt.icon = m_moreIcon; + btnOpt.icon = _iconMore; int e = QApplication::style()->pixelMetric(QStyle::PM_ButtonIconSize); btnOpt.iconSize = QSize(e,e); QApplication::style()->drawComplexControl(QStyle::CC_ToolButton, &btnOpt, painter); @@ -423,5 +433,14 @@ QRect FolderStatusDelegate::errorsListRect(QRect within) return within; } +void FolderStatusDelegate::slotStyleChanged() +{ + customizeStyle(); +} + +void FolderStatusDelegate::customizeStyle() +{ + _iconMore = Theme::createColorAwareIcon(QLatin1String(":/client/resources/more.svg")); +} } // namespace OCC diff --git a/src/gui/folderstatusdelegate.h b/src/gui/folderstatusdelegate.h index b4902abf0..342be330a 100644 --- a/src/gui/folderstatusdelegate.h +++ b/src/gui/folderstatusdelegate.h @@ -26,7 +26,6 @@ class FolderStatusDelegate : public QStyledItemDelegate { Q_OBJECT public: - QIcon m_moreIcon; FolderStatusDelegate(); enum datarole { FolderAliasRole = Qt::UserRole + 100, @@ -62,9 +61,16 @@ public: static QRect errorsListRect(QRect within); static int rootFolderHeightWithoutErrors(const QFontMetrics &fm, const QFontMetrics &aliasFm); +public slots: + void slotStyleChanged(); + private: + void customizeStyle(); + static QString addFolderText(); QPersistentModelIndex _pressedIndex; + + QIcon _iconMore; }; } // namespace OCC diff --git a/src/gui/generalsettings.cpp b/src/gui/generalsettings.cpp index 62381d36d..632ae43f8 100644 --- a/src/gui/generalsettings.cpp +++ b/src/gui/generalsettings.cpp @@ -57,6 +57,13 @@ GeneralSettings::GeneralSettings(QWidget *parent) connect(_ui->showInExplorerNavigationPaneCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::slotShowInExplorerNavigationPane); + // Rename 'Explorer' appropriately on non-Windows +#ifdef Q_OS_MAC + QString txt = _ui->showInExplorerNavigationPaneCheckBox->text(); + txt.replace(QString::fromLatin1("Explorer"), QString::fromLatin1("Finder")); + _ui->showInExplorerNavigationPaneCheckBox->setText(txt); +#endif + _ui->autostartCheckBox->setChecked(Utility::hasLaunchOnStartup(Theme::instance()->appName())); connect(_ui->autostartCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::slotToggleLaunchOnStartup); @@ -70,7 +77,8 @@ GeneralSettings::GeneralSettings(QWidget *parent) connect(_ui->legalNoticeButton, &QPushButton::clicked, this, &GeneralSettings::slotShowLegalNotice); loadMiscSettings(); - slotUpdateInfo(); + // updater info now set in: customizeStyle + //slotUpdateInfo(); // misc connect(_ui->monoIconsCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::saveMiscSettings); @@ -109,6 +117,8 @@ GeneralSettings::GeneralSettings(QWidget *parent) // accountAdded means the wizard was finished and the wizard might change some options. connect(AccountManager::instance(), &AccountManager::accountAdded, this, &GeneralSettings::loadMiscSettings); + + customizeStyle(); } GeneralSettings::~GeneralSettings() @@ -149,7 +159,11 @@ void GeneralSettings::slotUpdateInfo() connect(updater, &OCUpdater::downloadStateChanged, this, &GeneralSettings::slotUpdateInfo, Qt::UniqueConnection); connect(_ui->restartButton, &QAbstractButton::clicked, updater, &OCUpdater::slotStartInstaller, Qt::UniqueConnection); connect(_ui->restartButton, &QAbstractButton::clicked, qApp, &QApplication::quit, Qt::UniqueConnection); - _ui->updateStateLabel->setText(updater->statusString()); + + QString status = updater->statusString(); + Theme::replaceLinkColorStringBackgroundAware(status); + _ui->updateStateLabel->setText(status); + _ui->restartButton->setVisible(updater->downloadState() == OCUpdater::DownloadComplete); } else { // can't have those infos from sparkle currently @@ -211,4 +225,20 @@ void GeneralSettings::slotShowLegalNotice() delete notice; } +void GeneralSettings::slotStyleChanged() +{ + customizeStyle(); +} + +void GeneralSettings::customizeStyle() +{ + // setup about section + QString about = Theme::instance()->about(); + Theme::replaceLinkColorStringBackgroundAware(about); + _ui->aboutLabel->setText(about); + + // updater info + slotUpdateInfo(); +} + } // namespace OCC diff --git a/src/gui/generalsettings.h b/src/gui/generalsettings.h index 12abf3660..f7a759161 100644 --- a/src/gui/generalsettings.h +++ b/src/gui/generalsettings.h @@ -39,6 +39,9 @@ public: ~GeneralSettings(); QSize sizeHint() const override; +public slots: + void slotStyleChanged(); + private slots: void saveMiscSettings(); void slotToggleLaunchOnStartup(bool); @@ -50,6 +53,8 @@ private slots: void slotShowLegalNotice(); private: + void customizeStyle(); + Ui::GeneralSettings *_ui; QPointer<IgnoreListEditor> _ignoreEditor; QPointer<SyncLogDialog> _syncLogDialog; diff --git a/src/gui/headerbanner.cpp b/src/gui/headerbanner.cpp new file mode 100644 index 000000000..4c425750f --- /dev/null +++ b/src/gui/headerbanner.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (C) by Michael Schuster <michael@nextcloud.com> + * + * 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. + */ + +/**************************************************************************** +** +** Based on Qt sourcecode: +** qt5/qtbase/src/widgets/dialogs/qwizard.cpp +** +** https://code.qt.io/cgit/qt/qtbase.git/tree/src/widgets/dialogs/qwizard.cpp?h=v5.13.0 +** +** Original license: +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "headerbanner.h" + +#include <QVBoxLayout> +#include <QLabel> + +#include <QPainter> +#include <QStyle> +#include <QGuiApplication> + +namespace OCC { + +// These fudge terms were needed a few places to obtain pixel-perfect results +const int GapBetweenLogoAndRightEdge = 5; +const int ModernHeaderTopMargin = 2; + +HeaderBanner::HeaderBanner(QWidget *parent) + : QWidget(parent) +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + setBackgroundRole(QPalette::Base); + titleLabel = new QLabel(this); + titleLabel->setBackgroundRole(QPalette::Base); + logoLabel = new QLabel(this); + QFont font = titleLabel->font(); + font.setBold(true); + titleLabel->setFont(font); + layout = new QGridLayout(this); + layout->setContentsMargins(QMargins()); + layout->setSpacing(0); + layout->setRowMinimumHeight(3, 1); + layout->setRowStretch(4, 1); + layout->setColumnStretch(2, 1); + layout->setColumnMinimumWidth(4, 2 * GapBetweenLogoAndRightEdge); + layout->setColumnMinimumWidth(6, GapBetweenLogoAndRightEdge); + layout->addWidget(titleLabel, 1, 1, 5, 1); + layout->addWidget(logoLabel, 1, 5, 5, 1); +} + +void HeaderBanner::setup(const QString &title, const QPixmap &logo, const QPixmap &banner, + const Qt::TextFormat titleFormat, const QString &styleSheet) +{ + QStyle *style = parentWidget()->style(); + //const int layoutHorizontalSpacing = style->pixelMetric(QStyle::PM_LayoutHorizontalSpacing); + int topLevelMarginLeft = style->pixelMetric(QStyle::PM_LayoutLeftMargin, 0, parentWidget()); + int topLevelMarginRight = style->pixelMetric(QStyle::PM_LayoutRightMargin, 0, parentWidget()); + int topLevelMarginTop = style->pixelMetric(QStyle::PM_LayoutTopMargin, 0, parentWidget()); + //int topLevelMarginBottom = style->pixelMetric(QStyle::PM_LayoutBottomMargin, 0, parentWidget()); + + layout->setRowMinimumHeight(0, ModernHeaderTopMargin); + layout->setRowMinimumHeight(1, topLevelMarginTop - ModernHeaderTopMargin - 1); + layout->setRowMinimumHeight(6, 3); + int minColumnWidth0 = topLevelMarginLeft + topLevelMarginRight; + int minColumnWidth1 = topLevelMarginLeft + topLevelMarginRight + 1; + layout->setColumnMinimumWidth(0, minColumnWidth0); + layout->setColumnMinimumWidth(1, minColumnWidth1); + titleLabel->setTextFormat(titleFormat); + titleLabel->setText(title); + if(!styleSheet.isEmpty()) + titleLabel->setStyleSheet(styleSheet); + logoLabel->setPixmap(logo); + bannerPixmap = banner; + if (bannerPixmap.isNull()) { + QSize size = layout->totalMinimumSize(); + setMinimumSize(size); + setMaximumSize(QWIDGETSIZE_MAX, size.height()); + } else { + setFixedHeight(banner.height() + 2); + } + updateGeometry(); +} + +void HeaderBanner::paintEvent(QPaintEvent * /* event */) +{ + QPainter painter(this); + painter.drawPixmap(0, 0, width(), bannerPixmap.height(), bannerPixmap); + int x = width() - 2; + int y = height() - 2; + const QPalette &pal = QGuiApplication::palette(); + painter.setPen(pal.mid().color()); + painter.drawLine(0, y, x, y); + painter.setPen(pal.base().color()); + painter.drawPoint(x + 1, y); + painter.drawLine(0, y + 1, x + 1, y + 1); +} + +} // namespace OCC diff --git a/src/gui/headerbanner.h b/src/gui/headerbanner.h new file mode 100644 index 000000000..c52d73c1f --- /dev/null +++ b/src/gui/headerbanner.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) by Michael Schuster <michael@nextcloud.com> + * + * 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. + */ + +/**************************************************************************** +** +** Based on Qt sourcecode: +** qt5/qtbase/src/widgets/dialogs/qwizard.cpp +** +** https://code.qt.io/cgit/qt/qtbase.git/tree/src/widgets/dialogs/qwizard.cpp?h=v5.13.0 +** +** Original license: +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWidgets module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef HEADERBANNER_H +#define HEADERBANNER_H + +#include <QWidget> + +class QLabel; +class QGridLayout; +class QPixmap; + +namespace OCC { + +class HeaderBanner : public QWidget +{ + Q_OBJECT +public: + HeaderBanner(QWidget *parent = 0); + + void setup(const QString &title, const QPixmap &logo, const QPixmap &banner, + const Qt::TextFormat titleFormat, const QString &styleSheet); + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + QLabel *titleLabel; + QLabel *logoLabel; + QGridLayout *layout; + QPixmap bannerPixmap; +}; + +} // namespace OCC + +#endif // HEADERBANNER_H diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index c975a162e..27662f715 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -20,12 +20,7 @@ #include "progressdispatcher.h" #include "owncloudsetupwizard.h" #include "sharedialog.h" -#if defined(Q_OS_MAC) -#include "settingsdialogmac.h" -#include "macwindow.h" // qtmacgoodies -#else #include "settingsdialog.h" -#endif #include "logger.h" #include "logbrowser.h" #include "account.h" @@ -68,11 +63,7 @@ const char propertyAccountC[] = "oc_account"; ownCloudGui::ownCloudGui(Application *parent) : QObject(parent) , _tray(nullptr) -#if defined(Q_OS_MAC) - , _settingsDialog(new SettingsDialogMac(this)) -#else , _settingsDialog(new SettingsDialog(this)) -#endif , _logBrowser(nullptr) #ifdef WITH_LIBCLOUDPROVIDERS , _bus(QDBusConnection::sessionBus()) @@ -1090,18 +1081,18 @@ void ownCloudGui::slotShowGuiMessage(const QString &title, const QString &messag void ownCloudGui::slotShowSettings() { if (_settingsDialog.isNull()) { - _settingsDialog = -#if defined(Q_OS_MAC) - new SettingsDialogMac(this); -#else - new SettingsDialog(this); -#endif + _settingsDialog = new SettingsDialog(this); _settingsDialog->setAttribute(Qt::WA_DeleteOnClose, true); _settingsDialog->show(); } raiseDialog(_settingsDialog.data()); } +void ownCloudGui::slotSettingsDialogActivated() +{ + emit isShowingSettingsDialog(); +} + void ownCloudGui::slotShowSyncProtocol() { slotShowSettings(); @@ -1156,10 +1147,6 @@ void ownCloudGui::raiseDialog(QWidget *raiseWidget) raiseWidget->raise(); raiseWidget->activateWindow(); -#if defined(Q_OS_MAC) - // viel hilft viel ;-) - MacWindow::bringToFront(raiseWidget); -#endif #if defined(Q_OS_X11) WId wid = widget->winId(); NETWM::init(); diff --git a/src/gui/owncloudgui.h b/src/gui/owncloudgui.h index 95d078bc6..98af71bb7 100644 --- a/src/gui/owncloudgui.h +++ b/src/gui/owncloudgui.h @@ -34,7 +34,6 @@ namespace OCC { class Folder; class SettingsDialog; -class SettingsDialogMac; class ShareDialog; class Application; class LogBrowser; @@ -71,6 +70,7 @@ public: signals: void setupProxy(); void serverError(int code, const QString &message); + void isShowingSettingsDialog(); public slots: void setupContextMenu(); @@ -94,6 +94,7 @@ public slots: void slotToggleLogBrowser(); void slotOpenOwnCloud(); void slotOpenSettingsDialog(); + void slotSettingsDialogActivated(); void slotHelp(); void slotOpenPath(const QString &path); void slotAccountStateChanged(); @@ -131,11 +132,7 @@ private: void buildNavigationAppsMenu(AccountStatePtr account, QMenu *accountMenu); QPointer<Systray> _tray; -#if defined(Q_OS_MAC) - QPointer<SettingsDialogMac> _settingsDialog; -#else QPointer<SettingsDialog> _settingsDialog; -#endif QPointer<LogBrowser> _logBrowser; // tray's menu QScopedPointer<QMenu> _contextMenu; diff --git a/src/gui/owncloudsetupwizard.cpp b/src/gui/owncloudsetupwizard.cpp index ec2512f85..875dacf55 100644 --- a/src/gui/owncloudsetupwizard.cpp +++ b/src/gui/owncloudsetupwizard.cpp @@ -407,7 +407,8 @@ void OwncloudSetupWizard::slotAuthError() errorMsg = tr("There was an invalid response to an authenticated webdav request"); } - _ocWizard->show(); + // bring wizard to top + _ocWizard->bringToTop(); if (_ocWizard->currentId() == WizardCommon::Page_ShibbolethCreds || _ocWizard->currentId() == WizardCommon::Page_OAuthCreds || _ocWizard->currentId() == WizardCommon::Page_Flow2AuthCreds) { _ocWizard->back(); } diff --git a/src/gui/settingsdialog.cpp b/src/gui/settingsdialog.cpp index da40ffff1..a80de4da5 100644 --- a/src/gui/settingsdialog.cpp +++ b/src/gui/settingsdialog.cpp @@ -56,10 +56,6 @@ namespace OCC { #include "settingsdialogcommon.cpp" -// -// Whenever you change something here check both settingsdialog.cpp and settingsdialogmac.cpp ! -// - SettingsDialog::SettingsDialog(ownCloudGui *gui, QWidget *parent) : QDialog(parent) , _ui(new Ui::SettingsDialog) @@ -108,6 +104,9 @@ SettingsDialog::SettingsDialog(ownCloudGui *gui, QWidget *parent) GeneralSettings *generalSettings = new GeneralSettings; _ui->stack->addWidget(generalSettings); + // Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching) + connect(this, &SettingsDialog::styleChanged, generalSettings, &GeneralSettings::slotStyleChanged); + QAction *networkAction = createColorAwareAction(QLatin1String(":/client/resources/network.png"), tr("Network")); _actionGroup->addAction(networkAction); _toolBar->addAction(networkAction); @@ -128,6 +127,8 @@ SettingsDialog::SettingsDialog(ownCloudGui *gui, QWidget *parent) connect(showLogWindow, &QAction::triggered, gui, &ownCloudGui::slotToggleLogBrowser); addAction(showLogWindow); + connect(this, &SettingsDialog::onActivate, gui, &ownCloudGui::slotSettingsDialogActivated); + customizeStyle(); cfg.restoreGeometry(this); @@ -160,6 +161,13 @@ void SettingsDialog::changeEvent(QEvent *e) case QEvent::PaletteChange: case QEvent::ThemeChange: customizeStyle(); + + // Notify the other widgets (Dark-/Light-Mode switching) + emit styleChanged(); + break; + case QEvent::ActivationChange: + if(isActiveWindow()) + emit onActivate(); break; default: break; @@ -265,6 +273,10 @@ void SettingsDialog::accountAdded(AccountState *s) // Refresh immediatly when getting online connect(s, &AccountState::isConnectedChanged, this, &SettingsDialog::slotRefreshActivityAccountStateSender); + // Connect styleChanged event, to adapt (Dark-/Light-Mode switching) + connect(this, &SettingsDialog::styleChanged, accountSettings, &AccountSettings::slotStyleChanged); + connect(this, &SettingsDialog::styleChanged, _activitySettings[s], &ActivitySettings::slotStyleChanged); + activityAdded(s); slotRefreshActivity(s); } @@ -346,13 +358,13 @@ void SettingsDialog::accountRemoved(AccountState *s) void SettingsDialog::customizeStyle() { QString highlightColor(palette().highlight().color().name()); - QString altBase(palette().alternateBase().color().name()); + QString highlightTextColor(palette().highlightedText().color().name()); QString dark(palette().dark().color().name()); QString background(palette().base().color().name()); - _toolBar->setStyleSheet(QString::fromLatin1(TOOLBAR_CSS).arg(background, dark, highlightColor, altBase)); + _toolBar->setStyleSheet(QString::fromLatin1(TOOLBAR_CSS).arg(background, dark, highlightColor, highlightTextColor)); Q_FOREACH (QAction *a, _actionGroup->actions()) { - QIcon icon = createColorAwareIcon(a->property("iconPath").toString()); + QIcon icon = Theme::createColorAwareIcon(a->property("iconPath").toString(), palette()); a->setIcon(icon); QToolButton *btn = qobject_cast<QToolButton *>(_toolBar->widgetForAction(a)); if (btn) @@ -360,34 +372,6 @@ void SettingsDialog::customizeStyle() } } -static bool isDarkColor(const QColor &color) -{ - // account for different sensitivity of the human eye to certain colors - double treshold = 1.0 - (0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255.0; - return treshold > 0.5; -} - -QIcon SettingsDialog::createColorAwareIcon(const QString &name) -{ - QPalette pal = palette(); - QImage img(name); - QImage inverted(img); - inverted.invertPixels(QImage::InvertRgb); - - QIcon icon; - if (isDarkColor(pal.color(QPalette::Base))) { - icon.addPixmap(QPixmap::fromImage(inverted)); - } else { - icon.addPixmap(QPixmap::fromImage(img)); - } - if (isDarkColor(pal.color(QPalette::HighlightedText))) { - icon.addPixmap(QPixmap::fromImage(img), QIcon::Normal, QIcon::On); - } else { - icon.addPixmap(QPixmap::fromImage(inverted), QIcon::Normal, QIcon::On); - } - return icon; -} - class ToolButtonAction : public QWidgetAction { public: @@ -428,7 +412,7 @@ QAction *SettingsDialog::createActionWithIcon(const QIcon &icon, const QString & QAction *SettingsDialog::createColorAwareAction(const QString &iconPath, const QString &text) { // all buttons must have the same size in order to keep a good layout - QIcon coloredIcon = createColorAwareIcon(iconPath); + QIcon coloredIcon = Theme::createColorAwareIcon(iconPath, palette()); return createActionWithIcon(coloredIcon, text, iconPath); } diff --git a/src/gui/settingsdialog.h b/src/gui/settingsdialog.h index 97d15c03c..f5ec654d9 100644 --- a/src/gui/settingsdialog.h +++ b/src/gui/settingsdialog.h @@ -63,6 +63,10 @@ public slots: void slotAccountAvatarChanged(); void slotAccountDisplayNameChanged(); +signals: + void styleChanged(); + void onActivate(); + protected: void reject() override; void accept() override; @@ -76,7 +80,6 @@ private: void customizeStyle(); void activityAdded(AccountState *); - QIcon createColorAwareIcon(const QString &name); QAction *createColorAwareAction(const QString &iconName, const QString &fileName); QAction *createActionWithIcon(const QIcon &icon, const QString &text, const QString &iconPath = QString()); diff --git a/src/gui/settingsdialogmac.cpp b/src/gui/settingsdialogmac.cpp deleted file mode 100644 index 938dd6b45..000000000 --- a/src/gui/settingsdialogmac.cpp +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (C) by Denis Dzyubenko - * - * 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 "settingsdialogmac.h" - -#include "macstandardicon.h" - -#include "folderman.h" -#include "theme.h" -#include "generalsettings.h" -#include "networksettings.h" -#include "accountsettings.h" -#include "accountstate.h" -#include "creds/abstractcredentials.h" -#include "configfile.h" -#include "progressdispatcher.h" -#include "owncloudgui.h" -#include "activitywidget.h" -#include "accountmanager.h" - -#include <QLabel> -#include <QStandardItemModel> -#include <QPushButton> -#include <QSettings> -#include <QPainter> -#include <QPainterPath> - -namespace OCC { - -#include "settingsdialogcommon.cpp" - - -// Duplicate in settingsdialog.cpp -static QIcon circleMask(const QImage &avatar) -{ - int dim = avatar.width(); - - QPixmap fixedImage(dim, dim); - fixedImage.fill(Qt::transparent); - - QPainter imgPainter(&fixedImage); - QPainterPath clip; - clip.addEllipse(0, 0, dim, dim); - imgPainter.setClipPath(clip); - imgPainter.drawImage(0, 0, avatar); - imgPainter.end(); - - return QIcon(fixedImage); -} - - -// -// Whenever you change something here check both settingsdialog.cpp and settingsdialogmac.cpp ! -// -SettingsDialogMac::SettingsDialogMac(ownCloudGui *gui, QWidget *parent) - : MacPreferencesWindow(parent) - , _gui(gui) -{ - // do not show minimize button. There is no use, and restoring the - // dialog from minimize is broken in MacPreferencesWindow - setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint | Qt::WindowMaximizeButtonHint); - - // Emulate dialog behavior: Escape means close - QAction *closeDialogAction = new QAction(this); - closeDialogAction->setShortcut(QKeySequence(Qt::Key_Escape)); - connect(closeDialogAction, &QAction::triggered, this, &SettingsDialogMac::close); - addAction(closeDialogAction); - // People perceive this as a Window, so also make Ctrl+W work - QAction *closeWindowAction = new QAction(this); - closeWindowAction->setShortcut(QKeySequence("Ctrl+W")); - connect(closeWindowAction, &QAction::triggered, this, &SettingsDialogMac::close); - addAction(closeWindowAction); - // People perceive this as a Window, so also make Ctrl+H work - QAction *hideWindowAction = new QAction(this); - hideWindowAction->setShortcut(QKeySequence("Ctrl+H")); - connect(hideWindowAction, &QAction::triggered, this, &SettingsDialogMac::hide); - addAction(hideWindowAction); - - setObjectName("SettingsMac"); // required as group for saveGeometry call - - setWindowTitle(tr("%1").arg(Theme::instance()->appNameGUI())); - - connect(AccountManager::instance(), &AccountManager::accountAdded, - this, &SettingsDialogMac::accountAdded); - connect(AccountManager::instance(), &AccountManager::accountRemoved, - this, &SettingsDialogMac::accountRemoved); - - _actionsIdx = -1; - foreach (auto ai, AccountManager::instance()->accounts()) { - accountAdded(ai.data()); - } - - QIcon generalIcon = MacStandardIcon::icon(MacStandardIcon::PreferencesGeneral); - GeneralSettings *generalSettings = new GeneralSettings; - addPreferencesPanel(generalIcon, tr("General"), generalSettings); - - QIcon networkIcon = MacStandardIcon::icon(MacStandardIcon::Network); - NetworkSettings *networkSettings = new NetworkSettings; - addPreferencesPanel(networkIcon, tr("Network"), networkSettings); - - QAction *showLogWindow = new QAction(this); - showLogWindow->setShortcut(QKeySequence("F12")); - connect(showLogWindow, &QAction::triggered, gui, &ownCloudGui::slotToggleLogBrowser); - addAction(showLogWindow); - - ConfigFile cfg; - cfg.restoreGeometry(this); -} - -void SettingsDialogMac::closeEvent(QCloseEvent *event) -{ - ConfigFile cfg; - cfg.saveGeometry(this); - MacPreferencesWindow::closeEvent(event); -} - -void SettingsDialogMac::showActivityPage() -{ - // Count backwards (0-based) from the last panel (multiple accounts can be on the left) - setCurrentPanelIndex(preferencePanelCount() - 1 - 2); -} - -void SettingsDialogMac::accountAdded(AccountState *s) -{ - QIcon accountIcon = MacStandardIcon::icon(MacStandardIcon::UserAccounts); - auto accountSettings = new AccountSettings(s, this); - QString displayName = Theme::instance()->multiAccount() ? SettingsDialogCommon::shortDisplayNameForSettings(s->account().data(), 0) : tr("Account"); - - // this adds the panel - nothing to add here just to fix the order - insertPreferencesPanel(++_actionsIdx, accountIcon, displayName, accountSettings); - - connect(accountSettings, &AccountSettings::folderChanged, _gui, &ownCloudGui::slotFoldersChanged); - connect(accountSettings, &AccountSettings::openFolderAlias, _gui, &ownCloudGui::slotFolderOpenAction); - - connect(s->account().data(), &Account::accountChangedAvatar, this, &SettingsDialogMac::slotAccountAvatarChanged); - connect(s->account().data(), &Account::accountChangedDisplayName, this, &SettingsDialogMac::slotAccountDisplayNameChanged); - - // Refresh immediatly when getting online - connect(s, &AccountState::isConnectedChanged, this, &SettingsDialogMac::slotRefreshActivityAccountStateSender); - - // Add activity panel - QIcon activityIcon(QLatin1String(":/client/resources/activity.png")); - _activitySettings[s] = new ActivitySettings(s, this); - insertPreferencesPanel(++_actionsIdx, activityIcon, tr("Activity"), _activitySettings[s]); - connect(_activitySettings[s], SIGNAL(guiLog(QString, QString)), _gui, - SLOT(slotShowOptionalTrayMessage(QString, QString))); - - // if this is not the first account, add separator 2 positions before int the toolbar - if(AccountManager::instance()->accounts().first().data() != s && - AccountManager::instance()->accounts().size() >= 1){ - _separators[s] = insertSeparator(_actionsIdx - 1); - ++_actionsIdx; //we have one more item in the toolbar - } - - ConfigFile cfg; - _activitySettings[s]->setNotificationRefreshInterval(cfg.notificationRefreshInterval()); - - slotRefreshActivity(s); - setCurrentPanelIndex(0); -} - -void SettingsDialogMac::accountRemoved(AccountState *s) -{ - auto list = findChildren<AccountSettings *>(QString()); - foreach (auto p, list) { - if (p->accountsState() == s) { - removePreferencesPanel(p); - - // remove settings panel - if(_activitySettings.contains(s)) - removePreferencesPanel(_activitySettings[s]); - - // remove separator if there is any - if(_separators.contains(s)){ - removeSeparator(_separators[s]); - _separators.remove(s); - } - } - } -} - -void SettingsDialogMac::slotRefreshActivityAccountStateSender() -{ - slotRefreshActivity(qobject_cast<AccountState*>(sender())); -} - -void SettingsDialogMac::slotRefreshActivity(AccountState *accountState) -{ - if (accountState) { - _activitySettings[accountState]->slotRefresh(); - } -} - -void SettingsDialogMac::slotAccountAvatarChanged() -{ - Account *account = static_cast<Account *>(sender()); - auto list = findChildren<AccountSettings *>(QString()); - foreach (auto p, list) { - if (p->accountsState()->account() == account) { - int idx = indexForPanel(p); - QImage pix = account->avatar(); - if (!pix.isNull()) { - setPreferencesPanelIcon(idx, circleMask(pix)); - } - } - } -} - -void SettingsDialogMac::slotAccountDisplayNameChanged() -{ - Account *account = static_cast<Account *>(sender()); - auto list = findChildren<AccountSettings *>(QString()); - foreach (auto p, list) { - if (p->accountsState()->account() == account) { - int idx = indexForPanel(p); - QString displayName = account->displayName(); - if (!displayName.isNull()) { - displayName = Theme::instance()->multiAccount() - ? SettingsDialogCommon::shortDisplayNameForSettings(account, 0) - : tr("Account"); - setPreferencesPanelTitle(idx, displayName); - } - } - } -} - -} - diff --git a/src/gui/settingsdialogmac.h b/src/gui/settingsdialogmac.h deleted file mode 100644 index ef0995b53..000000000 --- a/src/gui/settingsdialogmac.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) by Denis Dzyubenko - * - * 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. - */ - - -#ifndef SETTINGSDIALOGMAC_H -#define SETTINGSDIALOGMAC_H - -#include "progressdispatcher.h" -#include "macpreferenceswindow.h" -#include "owncloudgui.h" - -class QStandardItemModel; -class QListWidgetItem; - -namespace OCC { - -class AccountSettings; -class Application; -class FolderMan; -class ownCloudGui; -class Folder; -class AccountState; -class ActivitySettings; - -/** - * @brief The SettingsDialogMac class - * @ingroup gui - */ -class SettingsDialogMac : public MacPreferencesWindow -{ - Q_OBJECT - -public: - explicit SettingsDialogMac(ownCloudGui *gui, QWidget *parent = 0); - -public slots: - void showActivityPage(); - void slotRefreshActivity(AccountState *accountState); - void slotRefreshActivityAccountStateSender(); - -private slots: - void accountAdded(AccountState *); - void accountRemoved(AccountState *); - void slotAccountAvatarChanged(); - void slotAccountDisplayNameChanged(); - -private: - void closeEvent(QCloseEvent *event); - - QAction *_actionBefore; - int _actionsIdx; - QMap<AccountState *, QAction *> _separators; - - QMap<AccountState *, ActivitySettings *> _activitySettings; - ownCloudGui *_gui; - - int _protocolIdx; -}; -} - -#endif // SETTINGSDIALOGMAC_H -; diff --git a/src/gui/sharedialog.cpp b/src/gui/sharedialog.cpp index 435de4a0c..544e0e723 100644 --- a/src/gui/sharedialog.cpp +++ b/src/gui/sharedialog.cpp @@ -156,6 +156,9 @@ void ShareDialog::addLinkShareWidget(const QSharedPointer<LinkShare> &linkShare) connect(_linkWidgetList.at(index), &ShareLinkWidget::deleteLinkShare, this, &ShareDialog::slotDeleteShare); //connect(_linkWidgetList.at(index), &ShareLinkWidget::resizeRequested, this, &ShareDialog::slotAdjustScrollWidgetSize); + // Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching) + connect(this, &ShareDialog::styleChanged, _linkWidgetList.at(index), &ShareLinkWidget::slotStyleChanged); + _ui->verticalLayout->insertWidget(_linkWidgetList.size()+1, _linkWidgetList.at(index)); _linkWidgetList.at(index)->setupUiOptions(); } @@ -281,6 +284,10 @@ void ShareDialog::showSharingUi() if (userGroupSharing) { _userGroupWidget = new ShareUserGroupWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _privateLinkUrl, this); + + // Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching) + connect(this, &ShareDialog::styleChanged, _userGroupWidget, &ShareUserGroupWidget::slotStyleChanged); + _ui->verticalLayout->insertWidget(1, _userGroupWidget); _userGroupWidget->getShares(); } @@ -334,4 +341,21 @@ void ShareDialog::slotAccountStateChanged(int state) } } } + +void ShareDialog::changeEvent(QEvent *e) +{ + switch (e->type()) { + case QEvent::StyleChange: + case QEvent::PaletteChange: + case QEvent::ThemeChange: + // Notify the other widgets (Dark-/Light-Mode switching) + emit styleChanged(); + break; + default: + break; + } + + QDialog::changeEvent(e); +} + } diff --git a/src/gui/sharedialog.h b/src/gui/sharedialog.h index dbd0ca775..17d156437 100644 --- a/src/gui/sharedialog.h +++ b/src/gui/sharedialog.h @@ -68,6 +68,10 @@ private slots: signals: void toggleAnimation(bool); + void styleChanged(); + +protected: + void changeEvent(QEvent *) override; private: void showSharingUi(); diff --git a/src/gui/sharelinkwidget.cpp b/src/gui/sharelinkwidget.cpp index b201871dc..62ed18da8 100644 --- a/src/gui/sharelinkwidget.cpp +++ b/src/gui/sharelinkwidget.cpp @@ -19,6 +19,7 @@ #include "capabilities.h" #include "guiutility.h" #include "sharemanager.h" +#include "theme.h" #include "QProgressIndicator.h" #include <QBuffer> @@ -256,6 +257,8 @@ void ShareLinkWidget::setupUiOptions() //TO DO //startAnimation(0, height()); + + customizeStyle(); } void ShareLinkWidget::setNote(const QString ¬e) @@ -549,4 +552,29 @@ void ShareLinkWidget::displayError(const QString &errMsg) _ui->errorLabel->setText(errMsg); _ui->errorLabel->show(); } + +void ShareLinkWidget::slotStyleChanged() +{ + customizeStyle(); +} + +void ShareLinkWidget::customizeStyle() +{ + _unshareLinkAction->setIcon(Theme::createColorAwareIcon(":/client/resources/delete.png")); + + _addAnotherLinkAction->setIcon(Theme::createColorAwareIcon(":/client/resources/add.png")); + + _ui->enableShareLink->setIcon(Theme::createColorAwareIcon(":/client/resources/copy.svg")); + + _ui->shareLinkIconLabel->setPixmap(Theme::createColorAwarePixmap(":/client/resources/public.svg")); + + _ui->shareLinkToolButton->setIcon(Theme::createColorAwareIcon(":/client/resources/more.svg")); + + _ui->confirmNote->setIcon(Theme::createColorAwareIcon(":/client/resources/confirm.svg")); + _ui->confirmPassword->setIcon(Theme::createColorAwareIcon(":/client/resources/confirm.svg")); + _ui->confirmExpirationDate->setIcon(Theme::createColorAwareIcon(":/client/resources/confirm.svg")); + + _ui->progressIndicator->setColor(QGuiApplication::palette().color(QPalette::Text)); +} + } diff --git a/src/gui/sharelinkwidget.h b/src/gui/sharelinkwidget.h index 34b2b3c3f..451687879 100644 --- a/src/gui/sharelinkwidget.h +++ b/src/gui/sharelinkwidget.h @@ -65,6 +65,7 @@ public slots: void slotToggleAnimation(bool start); void slotServerError(int code, const QString &message); void slotCreateShareRequiresPassword(const QString &message); + void slotStyleChanged(); private slots: void slotCreateShareLink(bool clicked); @@ -114,6 +115,8 @@ private: void startAnimation(const int start, const int end); + void customizeStyle(); + Ui::ShareLinkWidget *_ui; AccountPtr _account; QString _sharePath; diff --git a/src/gui/shareusergroupwidget.cpp b/src/gui/shareusergroupwidget.cpp index 82a3425f1..0a58d61a5 100644 --- a/src/gui/shareusergroupwidget.cpp +++ b/src/gui/shareusergroupwidget.cpp @@ -26,6 +26,7 @@ #include "thumbnailjob.h" #include "sharee.h" #include "sharemanager.h" +#include "theme.h" #include "QProgressIndicator.h" #include <QBuffer> @@ -114,6 +115,8 @@ ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account, //_ui->shareeHorizontalLayout->addWidget(&_pi_sharee); _parentScrollArea = parentWidget()->findChild<QScrollArea*>("scrollArea"); + + customizeStyle(); } ShareUserGroupWidget::~ShareUserGroupWidget() @@ -214,6 +217,10 @@ void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>> connect(s, &ShareUserLine::resizeRequested, this, &ShareUserGroupWidget::slotAdjustScrollWidgetSize); connect(s, &ShareUserLine::visualDeletionDone, this, &ShareUserGroupWidget::getShares); s->setBackgroundRole(layout->count() % 2 == 0 ? QPalette::Base : QPalette::AlternateBase); + + // Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching) + connect(this, &ShareUserGroupWidget::styleChanged, s, &ShareUserLine::slotStyleChanged); + layout->addWidget(s); x++; @@ -258,7 +265,8 @@ void ShareUserGroupWidget::slotPrivateLinkShare() auto menu = new QMenu(this); menu->setAttribute(Qt::WA_DeleteOnClose); - menu->addAction(QIcon(":/client/resources/copy.svg"), + // this icon is not handled by slotStyleChanged() -> customizeStyle but we can live with that + menu->addAction(Theme::createColorAwareIcon(":/client/resources/copy.svg"), tr("Copy link"), this, SLOT(slotPrivateLinkCopy())); @@ -361,6 +369,25 @@ void ShareUserGroupWidget::slotPrivateLinkEmail() this); } +void ShareUserGroupWidget::slotStyleChanged() +{ + customizeStyle(); + + // Notify the other widgets (ShareUserLine in this case, Dark-/Light-Mode switching) + emit styleChanged(); +} + +void ShareUserGroupWidget::customizeStyle() +{ + _ui->confirmShare->setIcon(Theme::createColorAwareIcon(":/client/resources/confirm.svg")); + + _pi_sharee.setColor(QGuiApplication::palette().color(QPalette::Text)); + + foreach (auto pi, _parentScrollArea->findChildren<QProgressIndicator *>()) { + pi->setColor(QGuiApplication::palette().color(QPalette::Text));; + } +} + ShareUserLine::ShareUserLine(QSharedPointer<Share> share, SharePermissions maxSharingPermissions, bool isFile, @@ -426,8 +453,9 @@ ShareUserLine::ShareUserLine(QSharedPointer<Share> share, _ui->permissionToolButton->setMenu(menu); _ui->permissionToolButton->setPopupMode(QToolButton::InstantPopup); - QIcon icon(QLatin1String(":/client/resources/more.svg")); - _ui->permissionToolButton->setIcon(icon); + // icon now set in: customizeStyle + /*QIcon icon(QLatin1String(":/client/resources/more.svg")); + _ui->permissionToolButton->setIcon(icon);*/ // Set the permissions checkboxes displayPermissions(); @@ -454,6 +482,8 @@ ShareUserLine::ShareUserLine(QSharedPointer<Share> share, } loadAvatar(); + + customizeStyle(); } void ShareUserLine::loadAvatar() @@ -648,4 +678,18 @@ void ShareUserLine::displayPermissions() _permissionDelete->setChecked(perm & SharePermissionDelete); } } + +void ShareUserLine::slotStyleChanged() +{ + customizeStyle(); +} + +void ShareUserLine::customizeStyle() +{ + _ui->permissionToolButton->setIcon(Theme::createColorAwareIcon(":/client/resources/more.svg")); + + QIcon deleteicon = QIcon::fromTheme(QLatin1String("user-trash"),Theme::createColorAwareIcon(QLatin1String(":/client/resources/delete.png"))); + _deleteShareButton->setIcon(deleteicon); +} + } diff --git a/src/gui/shareusergroupwidget.h b/src/gui/shareusergroupwidget.h index a1f537f05..f60edaedb 100644 --- a/src/gui/shareusergroupwidget.h +++ b/src/gui/shareusergroupwidget.h @@ -64,9 +64,11 @@ public: signals: void togglePublicLinkShare(bool); + void styleChanged(); public slots: void getShares(); + void slotStyleChanged(); private slots: void slotSharesFetched(const QList<QSharedPointer<Share>> &shares); @@ -88,6 +90,8 @@ private slots: void slotPrivateLinkEmail(); private: + void customizeStyle(); + Ui::ShareUserGroupWidget *_ui; QScrollArea *_parentScrollArea; AccountPtr _account; @@ -127,6 +131,9 @@ signals: void visualDeletionDone(); void resizeRequested(); +public slots: + void slotStyleChanged(); + private slots: void on_deleteShareButton_clicked(); void slotPermissionsChanged(); @@ -141,6 +148,7 @@ private slots: private: void displayPermissions(); void loadAvatar(); + void customizeStyle(); Ui::ShareUserLine *_ui; QSharedPointer<Share> _share; diff --git a/src/gui/sslbutton.cpp b/src/gui/sslbutton.cpp index 98db319e6..0efdc6f7c 100644 --- a/src/gui/sslbutton.cpp +++ b/src/gui/sslbutton.cpp @@ -142,7 +142,7 @@ QMenu *SslButton::buildCertMenu(QMenu *parent, const QSslCertificate &cert, // create label first QLabel *label = new QLabel(parent); - label->setStyleSheet(QLatin1String("QLabel { padding: 8px; background-color: #fff; }")); + label->setStyleSheet(QLatin1String("QLabel { padding: 8px; }")); label->setTextFormat(Qt::RichText); label->setText(details); diff --git a/src/gui/wizard/flow2authcredspage.cpp b/src/gui/wizard/flow2authcredspage.cpp index 85bcb89dc..d04d9741e 100644 --- a/src/gui/wizard/flow2authcredspage.cpp +++ b/src/gui/wizard/flow2authcredspage.cpp @@ -14,40 +14,39 @@ */ #include <QVariant> -#include <QMenu> -#include <QClipboard> +#include <QVBoxLayout> -#include "wizard/flow2authcredspage.h" +#include "flow2authcredspage.h" #include "theme.h" #include "account.h" #include "cookiejar.h" #include "wizard/owncloudwizardcommon.h" #include "wizard/owncloudwizard.h" +#include "wizard/flow2authwidget.h" #include "creds/credentialsfactory.h" #include "creds/webflowcredentials.h" namespace OCC { Flow2AuthCredsPage::Flow2AuthCredsPage() - : AbstractCredentialsWizardPage() + : AbstractCredentialsWizardPage(), + _flow2AuthWidget(nullptr) { - _ui.setupUi(this); - - Theme *theme = Theme::instance(); - _ui.topLabel->hide(); - _ui.bottomLabel->hide(); - QVariant variant = theme->customMedia(Theme::oCSetupTop); - WizardCommon::setupCustomMedia(variant, _ui.topLabel); - variant = theme->customMedia(Theme::oCSetupBottom); - WizardCommon::setupCustomMedia(variant, _ui.bottomLabel); - - WizardCommon::initErrorLabel(_ui.errorLabel); + _layout = new QVBoxLayout(this); setTitle(WizardCommon::titleTemplate().arg(tr("Connect to %1").arg(Theme::instance()->appNameGUI()))); setSubTitle(WizardCommon::subTitleTemplate().arg(tr("Login in your browser (Login Flow v2)"))); - connect(_ui.openLinkButton, &QCommandLinkButton::clicked, this, &Flow2AuthCredsPage::slotOpenBrowser); - connect(_ui.copyLinkButton, &QCommandLinkButton::clicked, this, &Flow2AuthCredsPage::slotCopyLinkToClipboard); + _flow2AuthWidget = new Flow2AuthWidget(); + _layout->addWidget(_flow2AuthWidget); + + connect(_flow2AuthWidget, &Flow2AuthWidget::authResult, this, &Flow2AuthCredsPage::slotFlow2AuthResult); + + // Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching) + connect(this, &Flow2AuthCredsPage::styleChanged, _flow2AuthWidget, &Flow2AuthWidget::slotStyleChanged); + + // allow Flow2 page to poll on window activation + connect(this, &Flow2AuthCredsPage::pollNow, _flow2AuthWidget, &Flow2AuthWidget::slotPollNow); } void Flow2AuthCredsPage::initializePage() @@ -55,9 +54,9 @@ void Flow2AuthCredsPage::initializePage() OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard()); Q_ASSERT(ocWizard); ocWizard->account()->setCredentials(CredentialsFactory::create("http")); - _asyncAuth.reset(new Flow2Auth(ocWizard->account().data(), this)); - connect(_asyncAuth.data(), &Flow2Auth::result, this, &Flow2AuthCredsPage::asyncAuthResult, Qt::QueuedConnection); - _asyncAuth->start(); + + if(_flow2AuthWidget) + _flow2AuthWidget->startAuth(ocWizard->account().data()); // Don't hide the wizard (avoid user confusion)! //wizard()->hide(); @@ -67,21 +66,20 @@ void OCC::Flow2AuthCredsPage::cleanupPage() { // The next or back button was activated, show the wizard again wizard()->show(); - _asyncAuth.reset(); + if(_flow2AuthWidget) + _flow2AuthWidget->resetAuth(); // Forget sensitive data _appPassword.clear(); _user.clear(); } -void Flow2AuthCredsPage::asyncAuthResult(Flow2Auth::Result r, const QString &user, - const QString &appPassword) +void Flow2AuthCredsPage::slotFlow2AuthResult(Flow2Auth::Result r, const QString &errorString, const QString &user, const QString &appPassword) { switch (r) { case Flow2Auth::NotSupported: { /* Flow2Auth not supported (can't open browser) */ - _ui.errorLabel->setText(tr("Unable to open the Browser, please copy the link to your Browser.")); - _ui.errorLabel->show(); + wizard()->show(); /* Don't fallback to HTTP credentials */ /*OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard()); @@ -91,7 +89,6 @@ void Flow2AuthCredsPage::asyncAuthResult(Flow2Auth::Result r, const QString &use } case Flow2Auth::Error: /* Error while getting the access token. (Timeout, or the server did not accept our client credentials */ - _ui.errorLabel->show(); wizard()->show(); break; case Flow2Auth::LoggedIn: { @@ -113,7 +110,11 @@ int Flow2AuthCredsPage::nextId() const void Flow2AuthCredsPage::setConnected() { - wizard()->show(); + OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard()); + Q_ASSERT(ocWizard); + + // bring wizard to top + ocWizard->bringToTop(); } AbstractCredentials *Flow2AuthCredsPage::getCredentials() const @@ -134,19 +135,14 @@ bool Flow2AuthCredsPage::isComplete() const return false; /* We can never go forward manually */ } -void Flow2AuthCredsPage::slotOpenBrowser() +void Flow2AuthCredsPage::slotPollNow() { - if (_ui.errorLabel) - _ui.errorLabel->hide(); - - if (_asyncAuth) - _asyncAuth->openBrowser(); + emit pollNow(); } -void Flow2AuthCredsPage::slotCopyLinkToClipboard() +void Flow2AuthCredsPage::slotStyleChanged() { - if (_asyncAuth) - QApplication::clipboard()->setText(_asyncAuth->authorisationLink().toString(QUrl::FullyEncoded)); + emit styleChanged(); } } // namespace OCC diff --git a/src/gui/wizard/flow2authcredspage.h b/src/gui/wizard/flow2authcredspage.h index f51d7d7f3..8a59605e0 100644 --- a/src/gui/wizard/flow2authcredspage.h +++ b/src/gui/wizard/flow2authcredspage.h @@ -25,11 +25,12 @@ #include "accountfwd.h" #include "creds/flow2auth.h" -#include "ui_flow2authcredspage.h" - +class QVBoxLayout; +class QProgressIndicator; namespace OCC { +class Flow2AuthWidget; class Flow2AuthCredsPage : public AbstractCredentialsWizardPage { @@ -46,20 +47,22 @@ public: bool isComplete() const override; public Q_SLOTS: - void asyncAuthResult(Flow2Auth::Result, const QString &user, const QString &appPassword); + void slotFlow2AuthResult(Flow2Auth::Result, const QString &errorString, const QString &user, const QString &appPassword); + void slotPollNow(); + void slotStyleChanged(); signals: void connectToOCUrl(const QString &); + void pollNow(); + void styleChanged(); public: QString _user; QString _appPassword; - QScopedPointer<Flow2Auth> _asyncAuth; - Ui_Flow2AuthCredsPage _ui; -protected slots: - void slotOpenBrowser(); - void slotCopyLinkToClipboard(); +private: + Flow2AuthWidget *_flow2AuthWidget; + QVBoxLayout *_layout; }; } // namespace OCC diff --git a/src/gui/wizard/flow2authcredspage.ui b/src/gui/wizard/flow2authcredspage.ui deleted file mode 100644 index 73b72b516..000000000 --- a/src/gui/wizard/flow2authcredspage.ui +++ /dev/null @@ -1,101 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>Flow2AuthCredsPage</class> - <widget class="QWidget" name="Flow2AuthCredsPage"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>424</width> - <height>373</height> - </rect> - </property> - <property name="windowTitle"> - <string>Browser Authentication</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QLabel" name="topLabel"> - <property name="text"> - <string notr="true">TextLabel</string> - </property> - <property name="textFormat"> - <enum>Qt::RichText</enum> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Please switch to your browser to proceed.</string> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="errorLabel"> - <property name="text"> - <string>An error occurred while connecting. Please try again.</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - </widget> - </item> - <item> - <widget class="QCommandLinkButton" name="openLinkButton"> - <property name="text"> - <string>Re-open Browser</string> - </property> - </widget> - </item> - <item> - <widget class="QCommandLinkButton" name="copyLinkButton"> - <property name="font"> - <font> - <family>Segoe UI</family> - <weight>50</weight> - <bold>false</bold> - </font> - </property> - <property name="text"> - <string>Copy link</string> - </property> - </widget> - </item> - <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>127</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QLabel" name="bottomLabel"> - <property name="text"> - <string notr="true">TextLabel</string> - </property> - <property name="textFormat"> - <enum>Qt::RichText</enum> - </property> - </widget> - </item> - </layout> - </widget> - <resources/> - <connections/> -</ui> diff --git a/src/gui/wizard/flow2authwidget.cpp b/src/gui/wizard/flow2authwidget.cpp index c050a54d0..498a9803e 100644 --- a/src/gui/wizard/flow2authwidget.cpp +++ b/src/gui/wizard/flow2authwidget.cpp @@ -14,53 +14,66 @@ #include "flow2authwidget.h" -#include <QDesktopServices> -#include <QProgressBar> -#include <QLoggingCategory> -#include <QLocale> -#include <QMessageBox> - -#include <QMenu> -#include <QClipboard> - #include "common/utility.h" #include "account.h" -#include "theme.h" #include "wizard/owncloudwizardcommon.h" +#include "QProgressIndicator.h" + namespace OCC { Q_LOGGING_CATEGORY(lcFlow2AuthWidget, "gui.wizard.flow2authwidget", QtInfoMsg) -Flow2AuthWidget::Flow2AuthWidget(Account *account, QWidget *parent) - : QWidget(parent), - _account(account), - _ui() +Flow2AuthWidget::Flow2AuthWidget(QWidget *parent) + : QWidget(parent) + , _account(nullptr) + , _ui() + , _progressIndi(new QProgressIndicator(this)) + , _statusUpdateSkipCount(0) { _ui.setupUi(this); - Theme *theme = Theme::instance(); - _ui.topLabel->hide(); - _ui.bottomLabel->hide(); - QVariant variant = theme->customMedia(Theme::oCSetupTop); - WizardCommon::setupCustomMedia(variant, _ui.topLabel); - variant = theme->customMedia(Theme::oCSetupBottom); - WizardCommon::setupCustomMedia(variant, _ui.bottomLabel); - WizardCommon::initErrorLabel(_ui.errorLabel); + _ui.errorLabel->setTextFormat(Qt::RichText); connect(_ui.openLinkButton, &QCommandLinkButton::clicked, this, &Flow2AuthWidget::slotOpenBrowser); connect(_ui.copyLinkButton, &QCommandLinkButton::clicked, this, &Flow2AuthWidget::slotCopyLinkToClipboard); - _asyncAuth.reset(new Flow2Auth(_account, this)); - connect(_asyncAuth.data(), &Flow2Auth::result, this, &Flow2AuthWidget::asyncAuthResult, Qt::QueuedConnection); - _asyncAuth->start(); + _ui.horizontalLayout->addWidget(_progressIndi); + stopSpinner(false); + + customizeStyle(); } -void Flow2AuthWidget::asyncAuthResult(Flow2Auth::Result r, const QString &user, - const QString &appPassword) +void Flow2AuthWidget::startAuth(Account *account) { + Flow2Auth *oldAuth = _asyncAuth.take(); + if(oldAuth) + oldAuth->deleteLater(); + + _statusUpdateSkipCount = 0; + + if(account) { + _account = account; + + _asyncAuth.reset(new Flow2Auth(_account, this)); + connect(_asyncAuth.data(), &Flow2Auth::result, this, &Flow2AuthWidget::slotAuthResult, Qt::QueuedConnection); + connect(_asyncAuth.data(), &Flow2Auth::statusChanged, this, &Flow2AuthWidget::slotStatusChanged); + connect(this, &Flow2AuthWidget::pollNow, _asyncAuth.data(), &Flow2Auth::slotPollNow); + _asyncAuth->start(); + } +} + +void Flow2AuthWidget::resetAuth(Account *account) +{ + startAuth(account); +} + +void Flow2AuthWidget::slotAuthResult(Flow2Auth::Result r, const QString &errorString, const QString &user, const QString &appPassword) +{ + stopSpinner(false); + switch (r) { case Flow2Auth::NotSupported: /* Flow2Auth can't open browser */ @@ -69,15 +82,16 @@ void Flow2AuthWidget::asyncAuthResult(Flow2Auth::Result r, const QString &user, break; case Flow2Auth::Error: /* Error while getting the access token. (Timeout, or the server did not accept our client credentials */ + _ui.errorLabel->setText(errorString); _ui.errorLabel->show(); break; case Flow2Auth::LoggedIn: { - _user = user; - _appPassword = appPassword; - emit urlCatched(_user, _appPassword, QString()); + _ui.errorLabel->hide(); break; } } + + emit authResult(r, errorString, user, appPassword); } void Flow2AuthWidget::setError(const QString &error) { @@ -90,11 +104,8 @@ void Flow2AuthWidget::setError(const QString &error) { } Flow2AuthWidget::~Flow2AuthWidget() { - _asyncAuth.reset(); - // Forget sensitive data - _appPassword.clear(); - _user.clear(); + _asyncAuth.reset(); } void Flow2AuthWidget::slotOpenBrowser() @@ -108,8 +119,79 @@ void Flow2AuthWidget::slotOpenBrowser() void Flow2AuthWidget::slotCopyLinkToClipboard() { + if (_ui.errorLabel) + _ui.errorLabel->hide(); + if (_asyncAuth) - QApplication::clipboard()->setText(_asyncAuth->authorisationLink().toString(QUrl::FullyEncoded)); + _asyncAuth->copyLinkToClipboard(); +} + +void Flow2AuthWidget::slotPollNow() +{ + emit pollNow(); +} + +void Flow2AuthWidget::slotStatusChanged(Flow2Auth::PollStatus status, int secondsLeft) +{ + switch(status) + { + case Flow2Auth::statusPollCountdown: + if(_statusUpdateSkipCount > 0) { + _statusUpdateSkipCount--; + break; + } + _ui.statusLabel->setText(tr("Waiting for authorization") + QString("… (%1)").arg(secondsLeft)); + stopSpinner(true); + break; + case Flow2Auth::statusPollNow: + _statusUpdateSkipCount = 0; + _ui.statusLabel->setText(tr("Polling for authorization") + "…"); + startSpinner(); + break; + case Flow2Auth::statusFetchToken: + _statusUpdateSkipCount = 0; + _ui.statusLabel->setText(tr("Starting authorization") + "…"); + startSpinner(); + break; + case Flow2Auth::statusCopyLinkToClipboard: + _ui.statusLabel->setText(tr("Link copied to clipboard.")); + _statusUpdateSkipCount = 3; + stopSpinner(true); + break; + } +} + +void Flow2AuthWidget::startSpinner() +{ + _ui.horizontalLayout->setEnabled(true); + _ui.statusLabel->setVisible(true); + _progressIndi->setVisible(true); + _progressIndi->startAnimation(); + + _ui.openLinkButton->setEnabled(false); + _ui.copyLinkButton->setEnabled(false); +} + +void Flow2AuthWidget::stopSpinner(bool showStatusLabel) +{ + _ui.horizontalLayout->setEnabled(false); + _ui.statusLabel->setVisible(showStatusLabel); + _progressIndi->setVisible(false); + _progressIndi->stopAnimation(); + + _ui.openLinkButton->setEnabled(_statusUpdateSkipCount == 0); + _ui.copyLinkButton->setEnabled(_statusUpdateSkipCount == 0); +} + +void Flow2AuthWidget::slotStyleChanged() +{ + customizeStyle(); +} + +void Flow2AuthWidget::customizeStyle() +{ + if(_progressIndi) + _progressIndi->setColor(QGuiApplication::palette().color(QPalette::Text)); } } // namespace OCC diff --git a/src/gui/wizard/flow2authwidget.h b/src/gui/wizard/flow2authwidget.h index 7fe1844c1..609791d9b 100644 --- a/src/gui/wizard/flow2authwidget.h +++ b/src/gui/wizard/flow2authwidget.h @@ -22,35 +22,49 @@ #include "ui_flow2authwidget.h" +class QProgressIndicator; + namespace OCC { class Flow2AuthWidget : public QWidget { Q_OBJECT public: - Flow2AuthWidget(Account *account, QWidget *parent = nullptr); + Flow2AuthWidget(QWidget *parent = nullptr); virtual ~Flow2AuthWidget(); + void startAuth(Account *account); + void resetAuth(Account *account = nullptr); void setError(const QString &error); public Q_SLOTS: - void asyncAuthResult(Flow2Auth::Result, const QString &user, const QString &appPassword); + void slotAuthResult(Flow2Auth::Result, const QString &errorString, const QString &user, const QString &appPassword); + void slotPollNow(); + void slotStatusChanged(Flow2Auth::PollStatus status, int secondsLeft); + void slotStyleChanged(); signals: - void urlCatched(const QString user, const QString pass, const QString host); + void authResult(Flow2Auth::Result, const QString &errorString, const QString &user, const QString &appPassword); + void pollNow(); private: Account *_account; - QString _user; - QString _appPassword; QScopedPointer<Flow2Auth> _asyncAuth; Ui_Flow2AuthWidget _ui; protected slots: void slotOpenBrowser(); void slotCopyLinkToClipboard(); + +private: + void startSpinner(); + void stopSpinner(bool showStatusLabel); + void customizeStyle(); + + QProgressIndicator *_progressIndi; + int _statusUpdateSkipCount; }; -} +} // namespace OCC #endif // FLOW2AUTHWIDGET_H diff --git a/src/gui/wizard/flow2authwidget.ui b/src/gui/wizard/flow2authwidget.ui index 2e4eb3b48..57f23186d 100644 --- a/src/gui/wizard/flow2authwidget.ui +++ b/src/gui/wizard/flow2authwidget.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>500</width> - <height>280</height> + <width>580</width> + <height>330</height> </rect> </property> <property name="sizePolicy"> @@ -27,22 +27,6 @@ </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> - <widget class="QLabel" name="topLabel"> - <property name="text"> - <string notr="true">TextLabel</string> - </property> - <property name="textFormat"> - <enum>Qt::RichText</enum> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - </widget> - </item> - <item> <widget class="QLabel" name="label"> <property name="text"> <string>Please switch to your browser to proceed.</string> @@ -73,7 +57,6 @@ <widget class="QCommandLinkButton" name="copyLinkButton"> <property name="font"> <font> - <family>Segoe UI</family> <weight>50</weight> <bold>false</bold> </font> @@ -84,28 +67,44 @@ </widget> </item> <item> - <spacer name="verticalSpacer"> + <spacer name="verticalSpacer_2"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>20</width> - <height>127</height> + <height>20</height> </size> </property> </spacer> </item> <item> - <widget class="QLabel" name="bottomLabel"> + <widget class="QLabel" name="statusLabel"> <property name="text"> <string notr="true">TextLabel</string> </property> - <property name="textFormat"> - <enum>Qt::RichText</enum> + <property name="alignment"> + <set>Qt::AlignCenter</set> </property> </widget> </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"/> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>107</height> + </size> + </property> + </spacer> + </item> </layout> </widget> <resources/> diff --git a/src/gui/wizard/owncloudadvancedsetuppage.cpp b/src/gui/wizard/owncloudadvancedsetuppage.cpp index 7cfb79644..03cfb0661 100644 --- a/src/gui/wizard/owncloudadvancedsetuppage.cpp +++ b/src/gui/wizard/owncloudadvancedsetuppage.cpp @@ -389,4 +389,15 @@ QString OwncloudAdvancedSetupPage::checkLocalSpace(qint64 remoteSize) const return (availableLocalSpace()>remoteSize) ? QString() : tr("There isn't enough free space in the local folder!"); } +void OwncloudAdvancedSetupPage::slotStyleChanged() +{ + customizeStyle(); +} + +void OwncloudAdvancedSetupPage::customizeStyle() +{ + if(_progressIndi) + _progressIndi->setColor(QGuiApplication::palette().color(QPalette::Text)); +} + } // namespace OCC diff --git a/src/gui/wizard/owncloudadvancedsetuppage.h b/src/gui/wizard/owncloudadvancedsetuppage.h index 9ebf9fc70..e3d328a18 100644 --- a/src/gui/wizard/owncloudadvancedsetuppage.h +++ b/src/gui/wizard/owncloudadvancedsetuppage.h @@ -51,6 +51,7 @@ signals: public slots: void setErrorString(const QString &); + void slotStyleChanged(); private slots: void slotSelectFolder(); @@ -67,6 +68,7 @@ private: QUrl serverUrl() const; qint64 availableLocalSpace() const; QString checkLocalSpace(qint64 remoteSize) const; + void customizeStyle(); Ui_OwncloudAdvancedSetupPage _ui; bool _checking; diff --git a/src/gui/wizard/owncloudhttpcredspage.cpp b/src/gui/wizard/owncloudhttpcredspage.cpp index 265d3bf3c..092bf789d 100644 --- a/src/gui/wizard/owncloudhttpcredspage.cpp +++ b/src/gui/wizard/owncloudhttpcredspage.cpp @@ -194,5 +194,15 @@ AbstractCredentials *OwncloudHttpCredsPage::getCredentials() const return new HttpCredentialsGui(_ui.leUsername->text(), _ui.lePassword->text(), _ocWizard->_clientSslCertificate, _ocWizard->_clientSslKey); } +void OwncloudHttpCredsPage::slotStyleChanged() +{ + customizeStyle(); +} + +void OwncloudHttpCredsPage::customizeStyle() +{ + if(_progressIndi) + _progressIndi->setColor(QGuiApplication::palette().color(QPalette::Text)); +} } // namespace OCC diff --git a/src/gui/wizard/owncloudhttpcredspage.h b/src/gui/wizard/owncloudhttpcredspage.h index 83fc55ad4..adc46117d 100644 --- a/src/gui/wizard/owncloudhttpcredspage.h +++ b/src/gui/wizard/owncloudhttpcredspage.h @@ -46,10 +46,14 @@ public: Q_SIGNALS: void connectToOCUrl(const QString &); +public slots: + void slotStyleChanged(); + private: void startSpinner(); void stopSpinner(); void setupCustomization(); + void customizeStyle(); Ui_OwncloudHttpCredsPage _ui; bool _connected; diff --git a/src/gui/wizard/owncloudsetuppage.cpp b/src/gui/wizard/owncloudsetuppage.cpp index fb2499c86..84f160aaf 100644 --- a/src/gui/wizard/owncloudsetuppage.cpp +++ b/src/gui/wizard/owncloudsetuppage.cpp @@ -89,29 +89,15 @@ OwncloudSetupPage::OwncloudSetupPage(QWidget *parent) connect(_ui.nextButton, &QPushButton::clicked, _ui.slideShow, &SlideShow::nextSlide); connect(_ui.prevButton, &QPushButton::clicked, _ui.slideShow, &SlideShow::prevSlide); - auto widgetBgLightness = OwncloudSetupPage::palette().color(OwncloudSetupPage::backgroundRole()).lightness(); - bool widgetHasDarkBg = - (widgetBgLightness >= 125) - ? false - : true; - _ui.nextButton->setIcon(theme->uiThemeIcon(QString("control-next.svg"), widgetHasDarkBg)); - _ui.prevButton->setIcon(theme->uiThemeIcon(QString("control-prev.svg"), widgetHasDarkBg)); - - // QPushButtons are a mess when it comes to consistent background coloring without stylesheets, - // so we do it here even though this is an exceptional styling method here - _ui.createAccountButton->setStyleSheet("QPushButton {background-color: #0082C9; color: white}"); - _ui.slideShow->startShow(); - - QPalette pal = _ui.slideShow->palette(); - pal.setColor(QPalette::WindowText, theme->wizardHeaderBackgroundColor()); - _ui.slideShow->setPalette(pal); #else _ui.createAccountButton->hide(); _ui.loginButton->hide(); _ui.installLink->hide(); _ui.slideShow->hide(); #endif + + customizeStyle(); } void OwncloudSetupPage::setServerUrl(const QString &newUrl) @@ -434,4 +420,31 @@ OwncloudSetupPage::~OwncloudSetupPage() { } +void OwncloudSetupPage::slotStyleChanged() +{ + customizeStyle(); +} + +void OwncloudSetupPage::customizeStyle() +{ +#ifdef WITH_PROVIDERS + Theme *theme = Theme::instance(); + + bool widgetHasDarkBg = Theme::isDarkColor(QGuiApplication::palette().base().color()); + _ui.nextButton->setIcon(theme->uiThemeIcon(QString("control-next.svg"), widgetHasDarkBg)); + _ui.prevButton->setIcon(theme->uiThemeIcon(QString("control-prev.svg"), widgetHasDarkBg)); + + // QPushButtons are a mess when it comes to consistent background coloring without stylesheets, + // so we do it here even though this is an exceptional styling method here + _ui.createAccountButton->setStyleSheet("QPushButton {background-color: #0082C9; color: white}"); + + QPalette pal = _ui.slideShow->palette(); + pal.setColor(QPalette::WindowText, theme->wizardHeaderBackgroundColor()); + _ui.slideShow->setPalette(pal); +#endif + + if(_progressIndi) + _progressIndi->setColor(QGuiApplication::palette().color(QPalette::Text)); +} + } // namespace OCC diff --git a/src/gui/wizard/owncloudsetuppage.h b/src/gui/wizard/owncloudsetuppage.h index 0bcea0450..6e7ed93b0 100644 --- a/src/gui/wizard/owncloudsetuppage.h +++ b/src/gui/wizard/owncloudsetuppage.h @@ -62,6 +62,7 @@ public slots: void startSpinner(); void stopSpinner(); void slotCertificateAccepted(); + void slotStyleChanged(); protected slots: void slotUrlChanged(const QString &); @@ -78,6 +79,7 @@ signals: private: bool urlHasChanged(); + void customizeStyle(); Ui_OwncloudSetupPage _ui; diff --git a/src/gui/wizard/owncloudwizard.cpp b/src/gui/wizard/owncloudwizard.cpp index e4dda0fbd..2076136ff 100644 --- a/src/gui/wizard/owncloudwizard.cpp +++ b/src/gui/wizard/owncloudwizard.cpp @@ -16,6 +16,7 @@ #include "account.h" #include "configfile.h" #include "theme.h" +#include "owncloudgui.h" #include "wizard/owncloudwizard.h" #include "wizard/owncloudsetuppage.h" @@ -100,6 +101,17 @@ OwncloudWizard::OwncloudWizard(QWidget *parent) setTitleFormat(Qt::RichText); setSubTitleFormat(Qt::RichText); setButtonText(QWizard::CustomButton1, tr("Skip folders configuration")); + + + // Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching) + connect(this, &OwncloudWizard::styleChanged, _setupPage, &OwncloudSetupPage::slotStyleChanged); + connect(this, &OwncloudWizard::styleChanged, _advancedSetupPage, &OwncloudAdvancedSetupPage::slotStyleChanged); + connect(this, &OwncloudWizard::styleChanged, _flow2CredsPage, &Flow2AuthCredsPage::slotStyleChanged); + + customizeStyle(); + + // allow Flow2 page to poll on window activation + connect(this, &OwncloudWizard::onActivate, _flow2CredsPage, &Flow2AuthCredsPage::slotPollNow); } void OwncloudWizard::setAccount(AccountPtr account) @@ -277,4 +289,37 @@ AbstractCredentials *OwncloudWizard::getCredentials() const return nullptr; } +void OwncloudWizard::changeEvent(QEvent *e) +{ + switch (e->type()) { + case QEvent::StyleChange: + case QEvent::PaletteChange: + case QEvent::ThemeChange: + customizeStyle(); + + // Notify the other widgets (Dark-/Light-Mode switching) + emit styleChanged(); + break; + case QEvent::ActivationChange: + if(isActiveWindow()) + emit onActivate(); + break; + default: + break; + } + + QWizard::changeEvent(e); +} + +void OwncloudWizard::customizeStyle() +{ + // HINT: Customize wizard's own style here, if necessary in the future (Dark-/Light-Mode switching) +} + +void OwncloudWizard::bringToTop() +{ + // bring wizard to top + ownCloudGui::raiseDialog(this); +} + } // end namespace diff --git a/src/gui/wizard/owncloudwizard.h b/src/gui/wizard/owncloudwizard.h index c1d290d4b..3cbf89f71 100644 --- a/src/gui/wizard/owncloudwizard.h +++ b/src/gui/wizard/owncloudwizard.h @@ -74,6 +74,8 @@ public: void displayError(const QString &, bool retryHTTPonly); AbstractCredentials *getCredentials() const; + void bringToTop(); + // FIXME: Can those be local variables? // Set from the OwncloudSetupPage, later used from OwncloudHttpCredsPage QSslKey _clientSslKey; @@ -96,8 +98,15 @@ signals: void basicSetupFinished(int); void skipFolderConfiguration(); void needCertificate(); + void styleChanged(); + void onActivate(); + +protected: + void changeEvent(QEvent *) override; private: + void customizeStyle(); + AccountPtr _account; OwncloudSetupPage *_setupPage; OwncloudHttpCredsPage *_httpCredsPage; |