Fix merge conflict

Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
This commit is contained in:
Dominique Fuchs 2020-01-02 08:43:34 +01:00
commit 7653c5fa60
111 changed files with 12962 additions and 14000 deletions

3
.gitmodules vendored
View file

@ -1,6 +1,3 @@
[submodule "src/3rdparty/qtmacgoodies"]
path = src/3rdparty/qtmacgoodies
url = https://github.com/camilasan/qtmacgoodies.git
[submodule "binary"]
path = binary
url = git://github.com/owncloud/owncloud-client-binary.git

View file

@ -198,7 +198,7 @@ X-GNOME-Autostart-Delay=3
# Translations
Icon[de]=@APPLICATION_ICON_NAME@
Name[de]=@APPLICATION_NAME@ Client zur Desktop-Synchronisation
Comment[de]=@APPLICATION_NAME@ Client zur Desktop-Synchronisation
GenericName[de]=Synchronisationsordner
Icon[de_DE]=@APPLICATION_ICON_NAME@
Name[de_DE]=@APPLICATION_NAME@ Client zur Desktop-Synchronisation
Comment[de_DE]=@APPLICATION_NAME@ Client zur Desktop-Synchronisation
GenericName[de_DE]=Synchronisationsordner

View file

@ -198,4 +198,7 @@ X-GNOME-Autostart-Delay=3
# Translations
Icon[el]=@APPLICATION_ICON_NAME@
Name[el]=@APPLICATION_NAME@ πρόγραμμα συγχρονισμού
Comment[el]=@APPLICATION_NAME@ πρόγραμμα συγχρονισμού
GenericName[el]=Συγχρονισμός φακέλου

@ -1 +0,0 @@
Subproject commit b59d091b3e6b98e7219cf636f7d21fb267242c27

View file

@ -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}

View file

@ -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"

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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();

View file

@ -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

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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

View file

@ -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();

View file

@ -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);
// 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());
if(!_pollTimer.isActive()) {
_pollTimer.start();
}
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;
QString loginName, appPassword;
QUrl serverUrl = json["server"].toString();
QString loginName = json["loginName"].toString();
QString appPassword = json["appPassword"].toString();
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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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);
auto *job = new KeychainChunk::WriteJob(_account,
_user + clientKeyPEMC,
_clientSslKey.toPem());
connect(job, &KeychainChunk::WriteJob::finished, this, &WebFlowCredentials::slotWriteClientKeyPEMJobDone);
job->start();
} 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);
job->start();
chunk.clear();
} else {
slotWriteClientKeyPEMJobDone();
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
}
}
readJob->deleteLater();
// Load key too
_clientSslKeyChunkCount = 0;
_clientSslKeyChunkBufferPEM.clear();
const QString kck = keychainKey(
_account->url().toString(),
_user + clientKeyPEMC,
_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);
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

View file

@ -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

View file

@ -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::urlCatched, this, &WebFlowCredentialsDialog::urlCatched);
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);
// 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;

146
src/gui/headerbanner.cpp Normal file
View file

@ -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

93
src/gui/headerbanner.h Normal file
View file

@ -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

View file

@ -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();

View file

@ -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;

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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());

View file

@ -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);
}
}
}
}
}

View file

@ -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
;

View file

@ -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);
}
}

View file

@ -68,6 +68,10 @@ private slots:
signals:
void toggleAnimation(bool);
void styleChanged();
protected:
void changeEvent(QEvent *) override;
private:
void showSharingUi();

View file

@ -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 &note)
@ -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));
}
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -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">
@ -26,22 +26,6 @@
<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">
@ -73,7 +57,6 @@
<widget class="QCommandLinkButton" name="copyLinkButton">
<property name="font">
<font>
<family>Segoe UI</family>
<weight>50</weight>
<bold>false</bold>
</font>
@ -83,6 +66,32 @@
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="statusLabel">
<property name="text">
<string notr="true">TextLabel</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout"/>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
@ -91,21 +100,11 @@
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>127</height>
<height>107</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/>

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -565,4 +565,81 @@ QString Theme::versionSwitchOutput() const
return helpText;
}
bool Theme::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;
}
QColor Theme::getBackgroundAwareLinkColor(const QColor &backgroundColor)
{
return QColor((isDarkColor(backgroundColor) ? QColor("#6193dc") : QGuiApplication::palette().color(QPalette::Link)));
}
QColor Theme::getBackgroundAwareLinkColor()
{
return getBackgroundAwareLinkColor(QGuiApplication::palette().base().color());
}
void Theme::replaceLinkColorStringBackgroundAware(QString &linkString, const QColor &backgroundColor)
{
replaceLinkColorString(linkString, getBackgroundAwareLinkColor(backgroundColor));
}
void Theme::replaceLinkColorStringBackgroundAware(QString &linkString)
{
replaceLinkColorStringBackgroundAware(linkString, QGuiApplication::palette().color(QPalette::Base));
}
void Theme::replaceLinkColorString(QString &linkString, const QColor &newColor)
{
linkString.replace(QRegularExpression("(<a href|<a style='color:#([a-zA-Z0-9]{6});' href)"), QString::fromLatin1("<a style='color:%1;' href").arg(newColor.name()));
}
QIcon Theme::createColorAwareIcon(const QString &name, const QPalette &palette)
{
QImage img(name);
QImage inverted(img);
inverted.invertPixels(QImage::InvertRgb);
QIcon icon;
if (Theme::isDarkColor(palette.color(QPalette::Base))) {
icon.addPixmap(QPixmap::fromImage(inverted));
} else {
icon.addPixmap(QPixmap::fromImage(img));
}
if (Theme::isDarkColor(palette.color(QPalette::HighlightedText))) {
icon.addPixmap(QPixmap::fromImage(img), QIcon::Normal, QIcon::On);
} else {
icon.addPixmap(QPixmap::fromImage(inverted), QIcon::Normal, QIcon::On);
}
return icon;
}
QIcon Theme::createColorAwareIcon(const QString &name)
{
return createColorAwareIcon(name, QGuiApplication::palette());
}
QPixmap Theme::createColorAwarePixmap(const QString &name, const QPalette &palette)
{
QImage img(name);
QImage inverted(img);
inverted.invertPixels(QImage::InvertRgb);
QPixmap pixmap;
if (Theme::isDarkColor(palette.color(QPalette::Base))) {
pixmap = QPixmap::fromImage(inverted);
} else {
pixmap = QPixmap::fromImage(img);
}
return pixmap;
}
QPixmap Theme::createColorAwarePixmap(const QString &name)
{
return createColorAwarePixmap(name, QGuiApplication::palette());
}
} // end namespace client

View file

@ -24,6 +24,7 @@ class QObject;
class QPixmap;
class QColor;
class QPaintDevice;
class QPalette;
namespace OCC {
@ -355,6 +356,96 @@ public:
* (actually 2019/09/13 only systray theming).
*/
virtual QIcon uiThemeIcon(const QString &iconName, bool uiHasDarkBg) const;
/**
* @brief Perform a calculation to check if a colour is dark or light and accounts for different sensitivity of the human eye.
*
* @return True if the specified colour is dark.
*
* 2019/12/08: Moved here from SettingsDialog.
*/
static bool isDarkColor(const QColor &color);
/**
* @brief Return the colour to be used for HTML links (e.g. used in QLabel), based on the current app palette or given colour (Dark-/Light-Mode switching).
*
* @return Background-aware colour for HTML links, based on the current app palette or given colour.
*
* 2019/12/08: Implemented for the Dark Mode on macOS, because the app palette can not account for that (Qt 5.12.5).
*/
static QColor getBackgroundAwareLinkColor(const QColor &backgroundColor);
/**
* @brief Return the colour to be used for HTML links (e.g. used in QLabel), based on the current app palette (Dark-/Light-Mode switching).
*
* @return Background-aware colour for HTML links, based on the current app palette.
*
* 2019/12/08: Implemented for the Dark Mode on macOS, because the app palette can not account for that (Qt 5.12.5).
*/
static QColor getBackgroundAwareLinkColor();
/**
* @brief Appends a CSS-style colour value to all HTML link tags in a given string, based on the current app palette or given colour (Dark-/Light-Mode switching).
*
* 2019/12/08: Implemented for the Dark Mode on macOS, because the app palette can not account for that (Qt 5.12.5).
*
* This way we also avoid having certain strings re-translated on Transifex.
*/
static void replaceLinkColorStringBackgroundAware(QString &linkString, const QColor &backgroundColor);
/**
* @brief Appends a CSS-style colour value to all HTML link tags in a given string, based on the current app palette (Dark-/Light-Mode switching).
*
* 2019/12/08: Implemented for the Dark Mode on macOS, because the app palette can not account for that (Qt 5.12.5).
*
* This way we also avoid having certain strings re-translated on Transifex.
*/
static void replaceLinkColorStringBackgroundAware(QString &linkString);
/**
* @brief Appends a CSS-style colour value to all HTML link tags in a given string, as specified by newColor.
*
* 2019/12/19: Implemented for the Dark Mode on macOS, because the app palette can not account for that (Qt 5.12.5).
*
* This way we also avoid having certain strings re-translated on Transifex.
*/
static void replaceLinkColorString(QString &linkString, const QColor &newColor);
/**
* @brief Creates a colour-aware icon based on the specified palette's base colour.
*
* @return QIcon, colour-aware (inverted on dark backgrounds).
*
* 2019/12/09: Moved here from SettingsDialog.
*/
static QIcon createColorAwareIcon(const QString &name, const QPalette &palette);
/**
* @brief Creates a colour-aware icon based on the app palette's base colour (Dark-/Light-Mode switching).
*
* @return QIcon, colour-aware (inverted on dark backgrounds).
*
* 2019/12/09: Moved here from SettingsDialog.
*/
static QIcon createColorAwareIcon(const QString &name);
/**
* @brief Creates a colour-aware pixmap based on the specified palette's base colour.
*
* @return QPixmap, colour-aware (inverted on dark backgrounds).
*
* 2019/12/09: Adapted from createColorAwareIcon.
*/
static QPixmap createColorAwarePixmap(const QString &name, const QPalette &palette);
/**
* @brief Creates a colour-aware pixmap based on the app palette's base colour (Dark-/Light-Mode switching).
*
* @return QPixmap, colour-aware (inverted on dark backgrounds).
*
* 2019/12/09: Adapted from createColorAwareIcon.
*/
static QPixmap createColorAwarePixmap(const QString &name);
protected:
#ifndef TOKEN_AUTH_ONLY

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more