1/*
2 * Copyright (C) by Daniel Molkentin <danimo@owncloud.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * for more details.
13 */
14
15
16#include "accountsettings.h"
17#include "ui_accountsettings.h"
18
19#include "theme.h"
20#include "folderman.h"
21#include "folderwizard.h"
22#include "folderstatusmodel.h"
23#include "folderstatusdelegate.h"
24#include "common/utility.h"
25#include "application.h"
26#include "configfile.h"
27#include "account.h"
28#include "accountstate.h"
29#include "quotainfo.h"
30#include "accountmanager.h"
31#include "owncloudsetupwizard.h"
32#include "creds/abstractcredentials.h"
33#include "creds/httpcredentialsgui.h"
34#include "tooltipupdater.h"
35#include "filesystem.h"
36
37#include <math.h>
38
39#include <QDesktopServices>
40#include <QDir>
41#include <QListWidgetItem>
42#include <QMessageBox>
43#include <QAction>
44#include <QVBoxLayout>
45#include <QTreeView>
46#include <QKeySequence>
47#include <QIcon>
48#include <QVariant>
49#include <QToolTip>
50#include <qstringlistmodel.h>
51#include <qpropertyanimation.h>
52
53#include "account.h"
54
55#ifdef Q_OS_MAC
56#include "settingsdialogmac.h"
57#endif
58
59namespace OCC {
60
61Q_LOGGING_CATEGORY(lcAccountSettings, "gui.account.settings", QtInfoMsg)
62
63static const char progressBarStyleC[] =
64 "QProgressBar {"
65 "border: 1px solid grey;"
66 "border-radius: 5px;"
67 "text-align: center;"
68 "}"
69 "QProgressBar::chunk {"
70 "background-color: %1; width: 1px;"
71 "}";
72
73/**
74 * Adjusts the mouse cursor based on the region it is on over the folder tree view.
75 *
76 * Used to show that one can click the red error list box by changing the cursor
77 * to the pointing hand.
78 */
79class MouseCursorChanger : public QObject
80{
81 Q_OBJECT
82public:
83 MouseCursorChanger(QObject *parent)
84 : QObject(parent)
85 {
86 }
87
88 QTreeView *folderList;
89 FolderStatusModel *model;
90
91protected:
92 bool eventFilter(QObject *watched, QEvent *event) override
93 {
94 if (event->type() == QEvent::HoverMove) {
95 Qt::CursorShape shape = Qt::ArrowCursor;
96 auto pos = folderList->mapFromGlobal(QCursor::pos());
97 auto index = folderList->indexAt(pos);
98 if (model->classify(index) == FolderStatusModel::RootFolder
99 && (FolderStatusDelegate::errorsListRect(folderList->visualRect(index), index).contains(pos)
100 || FolderStatusDelegate::optionsButtonRect(folderList->visualRect(index),folderList->layoutDirection()).contains(pos))) {
101 shape = Qt::PointingHandCursor;
102 }
103 folderList->setCursor(shape);
104 }
105 return QObject::eventFilter(watched, event);
106 }
107};
108
109AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
110 : QWidget(parent)
111 , ui(new Ui::AccountSettings)
112 , _wasDisabledBefore(false)
113 , _accountState(accountState)
114 , _quotaInfo(accountState)
115{
116 ui->setupUi(this);
117
118 _model = new FolderStatusModel;
119 _model->setAccountState(_accountState);
120 _model->setParent(this);
121 FolderStatusDelegate *delegate = new FolderStatusDelegate;
122 delegate->setParent(this);
123
124 ui->_folderList->header()->hide();
125 ui->_folderList->setItemDelegate(delegate);
126 ui->_folderList->setModel(_model);
127#if defined(Q_OS_MAC)
128 ui->_folderList->setMinimumWidth(400);
129#else
130 ui->_folderList->setMinimumWidth(300);
131#endif
132 new ToolTipUpdater(ui->_folderList);
133
134 auto mouseCursorChanger = new MouseCursorChanger(this);
135 mouseCursorChanger->folderList = ui->_folderList;
136 mouseCursorChanger->model = _model;
137 ui->_folderList->setMouseTracking(true);
138 ui->_folderList->setAttribute(Qt::WA_Hover, true);
139 ui->_folderList->installEventFilter(mouseCursorChanger);
140
141 createAccountToolbox();
142 connect(AccountManager::instance(), &AccountManager::accountAdded,
143 this, &AccountSettings::slotAccountAdded);
144 connect(ui->_folderList, &QWidget::customContextMenuRequested,
145 this, &AccountSettings::slotCustomContextMenuRequested);
146 connect(ui->_folderList, &QAbstractItemView::clicked,
147 this, &AccountSettings::slotFolderListClicked);
148 connect(ui->_folderList, &QTreeView::expanded, this, &AccountSettings::refreshSelectiveSyncStatus);
149 connect(ui->_folderList, &QTreeView::collapsed, this, &AccountSettings::refreshSelectiveSyncStatus);
150 connect(ui->selectiveSyncNotification, &QLabel::linkActivated,
151 this, &AccountSettings::slotLinkActivated);
152 connect(_model, &FolderStatusModel::suggestExpand, ui->_folderList, &QTreeView::expand);
153 connect(_model, &FolderStatusModel::dirtyChanged, this, &AccountSettings::refreshSelectiveSyncStatus);
154 refreshSelectiveSyncStatus();
155 connect(_model, &QAbstractItemModel::rowsInserted,
156 this, &AccountSettings::refreshSelectiveSyncStatus);
157
158 QAction *syncNowAction = new QAction(this);
159 syncNowAction->setShortcut(QKeySequence(Qt::Key_F6));
160 connect(syncNowAction, &QAction::triggered, this, &AccountSettings::slotScheduleCurrentFolder);
161 addAction(syncNowAction);
162
163 QAction *syncNowWithRemoteDiscovery = new QAction(this);
164 syncNowWithRemoteDiscovery->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_F6));
165 connect(syncNowWithRemoteDiscovery, &QAction::triggered, this, &AccountSettings::slotScheduleCurrentFolderForceRemoteDiscovery);
166 addAction(syncNowWithRemoteDiscovery);
167
168
169 connect(ui->selectiveSyncApply, &QAbstractButton::clicked, _model, &FolderStatusModel::slotApplySelectiveSync);
170 connect(ui->selectiveSyncCancel, &QAbstractButton::clicked, _model, &FolderStatusModel::resetFolders);
171 connect(ui->bigFolderApply, &QAbstractButton::clicked, _model, &FolderStatusModel::slotApplySelectiveSync);
172 connect(ui->bigFolderSyncAll, &QAbstractButton::clicked, _model, &FolderStatusModel::slotSyncAllPendingBigFolders);
173 connect(ui->bigFolderSyncNone, &QAbstractButton::clicked, _model, &FolderStatusModel::slotSyncNoPendingBigFolders);
174
175 connect(FolderMan::instance(), &FolderMan::folderListChanged, _model, &FolderStatusModel::resetFolders);
176 connect(this, &AccountSettings::folderChanged, _model, &FolderStatusModel::resetFolders);
177
178
179 QColor color = palette().highlight().color();
180 ui->quotaProgressBar->setStyleSheet(QString::fromLatin1(progressBarStyleC).arg(color.name()));
181
182 ui->connectLabel->setText(tr("No account configured."));
183
184 connect(_accountState, &AccountState::stateChanged, this, &AccountSettings::slotAccountStateChanged);
185 slotAccountStateChanged();
186
187 connect(&_quotaInfo, &QuotaInfo::quotaUpdated,
188 this, &AccountSettings::slotUpdateQuota);
189}
190
191
192void AccountSettings::createAccountToolbox()
193{
194 QMenu *menu = new QMenu();
195 _addAccountAction = new QAction(tr("Add new"), this);
196 menu->addAction(_addAccountAction);
197 connect(_addAccountAction, &QAction::triggered, this, &AccountSettings::slotOpenAccountWizard);
198
199 _toggleSignInOutAction = new QAction(tr("Log out"), this);
200 connect(_toggleSignInOutAction, &QAction::triggered, this, &AccountSettings::slotToggleSignInState);
201 menu->addAction(_toggleSignInOutAction);
202
203 QAction *action = new QAction(tr("Remove"), this);
204 menu->addAction(action);
205 connect(action, &QAction::triggered, this, &AccountSettings::slotDeleteAccount);
206
207 ui->_accountToolbox->setText(tr("Account") + QLatin1Char(' '));
208 ui->_accountToolbox->setMenu(menu);
209 ui->_accountToolbox->setPopupMode(QToolButton::InstantPopup);
210
211 slotAccountAdded(_accountState);
212}
213
214QString AccountSettings::selectedFolderAlias() const
215{
216 QModelIndex selected = ui->_folderList->selectionModel()->currentIndex();
217 if (!selected.isValid())
218 return "";
219 return _model->data(selected, FolderStatusDelegate::FolderAliasRole).toString();
220}
221
222void AccountSettings::slotOpenAccountWizard()
223{
224 // We can't call isSystemTrayAvailable with appmenu-qt5 because it breaks the systemtray
225 // (issue #4693, #4944)
226 if (qgetenv("QT_QPA_PLATFORMTHEME") == "appmenu-qt5" || QSystemTrayIcon::isSystemTrayAvailable()) {
227 topLevelWidget()->close();
228 }
229#ifdef Q_OS_MAC
230 qCDebug(lcAccountSettings) << parent() << topLevelWidget();
231 SettingsDialogMac *sd = qobject_cast<SettingsDialogMac *>(topLevelWidget());
232
233 if (sd) {
234 sd->showActivityPage();
235 } else {
236 qFatal("nope");
237 }
238#endif
239 OwncloudSetupWizard::runWizard(qApp, SLOT(slotownCloudWizardDone(int)), 0);
240}
241
242void AccountSettings::slotToggleSignInState()
243{
244 if (_accountState->isSignedOut()) {
245 _accountState->account()->resetRejectedCertificates();
246 _accountState->signIn();
247 } else {
248 _accountState->signOutByUi();
249 }
250}
251
252void AccountSettings::doExpand()
253{
254 ui->_folderList->expandToDepth(0);
255}
256
257void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
258{
259 QTreeView *tv = ui->_folderList;
260 QModelIndex index = tv->indexAt(pos);
261 if (!index.isValid()) {
262 return;
263 }
264
265 if (_model->classify(index) == FolderStatusModel::SubFolder) {
266 QMenu *menu = new QMenu(tv);
267 menu->setAttribute(Qt::WA_DeleteOnClose);
268
269 QAction *ac = menu->addAction(tr("Open folder"));
270 connect(ac, &QAction::triggered, this, &AccountSettings::slotOpenCurrentLocalSubFolder);
271
272 QString fileName = _model->data(index, FolderStatusDelegate::FolderPathRole).toString();
273 if (!QFile::exists(fileName)) {
274 ac->setEnabled(false);
275 }
276
277 menu->popup(tv->mapToGlobal(pos));
278 return;
279 }
280
281 if (_model->classify(index) != FolderStatusModel::RootFolder) {
282 return;
283 }
284
285 tv->setCurrentIndex(index);
286 QString alias = _model->data(index, FolderStatusDelegate::FolderAliasRole).toString();
287 bool folderPaused = _model->data(index, FolderStatusDelegate::FolderSyncPaused).toBool();
288 bool folderConnected = _model->data(index, FolderStatusDelegate::FolderAccountConnected).toBool();
289 auto folderMan = FolderMan::instance();
290
291 QMenu *menu = new QMenu(tv);
292
293 menu->setAttribute(Qt::WA_DeleteOnClose);
294
295 QAction *ac = menu->addAction(tr("Open folder"));
296 connect(ac, &QAction::triggered, this, &AccountSettings::slotOpenCurrentFolder);
297
298 if (!ui->_folderList->isExpanded(index)) {
299 ac = menu->addAction(tr("Choose what to sync"));
300 ac->setEnabled(folderConnected);
301 connect(ac, &QAction::triggered, this, &AccountSettings::doExpand);
302 }
303
304 if (!folderPaused) {
305 ac = menu->addAction(tr("Force sync now"));
306 if (folderMan->currentSyncFolder() == folderMan->folder(alias)) {
307 ac->setText(tr("Restart sync"));
308 }
309 ac->setEnabled(folderConnected);
310 connect(ac, &QAction::triggered, this, &AccountSettings::slotForceSyncCurrentFolder);
311 }
312
313 ac = menu->addAction(folderPaused ? tr("Resume sync") : tr("Pause sync"));
314 connect(ac, &QAction::triggered, this, &AccountSettings::slotEnableCurrentFolder);
315
316 ac = menu->addAction(tr("Remove folder sync connection"));
317 connect(ac, &QAction::triggered, this, &AccountSettings::slotRemoveCurrentFolder);
318 menu->popup(tv->mapToGlobal(pos));
319}
320
321void AccountSettings::slotFolderListClicked(const QModelIndex &indx)
322{
323 if (indx.data(FolderStatusDelegate::AddButton).toBool()) {
324 // "Add Folder Sync Connection"
325 if (indx.flags() & Qt::ItemIsEnabled) {
326 slotAddFolder();
327 } else {
328 QToolTip::showText(
329 QCursor::pos(),
330 _model->data(indx, Qt::ToolTipRole).toString(),
331 this);
332 }
333 return;
334 }
335 if (_model->classify(indx) == FolderStatusModel::RootFolder) {
336 // tries to find if we clicked on the '...' button.
337 QTreeView *tv = ui->_folderList;
338 auto pos = tv->mapFromGlobal(QCursor::pos());
339 if (FolderStatusDelegate::optionsButtonRect(tv->visualRect(indx), layoutDirection()).contains(pos)) {
340 slotCustomContextMenuRequested(pos);
341 return;
342 }
343 if (FolderStatusDelegate::errorsListRect(tv->visualRect(indx), indx).contains(pos)) {
344 emit showIssuesList(_model->data(indx, FolderStatusDelegate::FolderAliasRole).toString());
345 return;
346 }
347
348 // Expand root items on single click
349 if (_accountState && _accountState->state() == AccountState::Connected) {
350 bool expanded = !(ui->_folderList->isExpanded(indx));
351 ui->_folderList->setExpanded(indx, expanded);
352 }
353 }
354}
355
356void AccountSettings::slotAddFolder()
357{
358 FolderMan *folderMan = FolderMan::instance();
359 folderMan->setSyncEnabled(false); // do not start more syncs.
360
361 FolderWizard *folderWizard = new FolderWizard(_accountState->account(), this);
362
363 connect(folderWizard, &QDialog::accepted, this, &AccountSettings::slotFolderWizardAccepted);
364 connect(folderWizard, &QDialog::rejected, this, &AccountSettings::slotFolderWizardRejected);
365 folderWizard->open();
366}
367
368
369void AccountSettings::slotFolderWizardAccepted()
370{
371 FolderWizard *folderWizard = qobject_cast<FolderWizard *>(sender());
372 FolderMan *folderMan = FolderMan::instance();
373
374 qCInfo(lcAccountSettings) << "Folder wizard completed";
375
376 FolderDefinition definition;
377 definition.localPath = FolderDefinition::prepareLocalPath(
378 folderWizard->field(QLatin1String("sourceFolder")).toString());
379 definition.targetPath = FolderDefinition::prepareTargetPath(
380 folderWizard->property("targetPath").toString());
381 definition.useVirtualFiles = folderWizard->property("useVirtualFiles").toBool();
382
383 {
384 QDir dir(definition.localPath);
385 if (!dir.exists()) {
386 qCInfo(lcAccountSettings) << "Creating folder" << definition.localPath;
387 if (!dir.mkpath(".")) {
388 QMessageBox::warning(this, tr("Folder creation failed"),
389 tr("<p>Could not create local folder <i>%1</i>.")
390 .arg(QDir::toNativeSeparators(definition.localPath)));
391 return;
392 }
393 }
394 FileSystem::setFolderMinimumPermissions(definition.localPath);
395 Utility::setupFavLink(definition.localPath);
396 }
397
398 /* take the value from the definition of already existing folders. All folders have
399 * the same setting so far.
400 * The default is to not sync hidden files
401 */
402 definition.ignoreHiddenFiles = folderMan->ignoreHiddenFiles();
403
404 if (folderMan->navigationPaneHelper().showInExplorerNavigationPane())
405 definition.navigationPaneClsid = QUuid::createUuid();
406
407 auto selectiveSyncBlackList = folderWizard->property("selectiveSyncBlackList").toStringList();
408
409 folderMan->setSyncEnabled(true);
410
411 Folder *f = folderMan->addFolder(_accountState, definition);
412 if (f) {
413 f->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, selectiveSyncBlackList);
414
415 // The user already accepted the selective sync dialog. everything is in the white list
416 f->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList,
417 QStringList() << QLatin1String("/"));
418 folderMan->scheduleAllFolders();
419 emit folderChanged();
420 }
421}
422
423void AccountSettings::slotFolderWizardRejected()
424{
425 qCInfo(lcAccountSettings) << "Folder wizard cancelled";
426 FolderMan *folderMan = FolderMan::instance();
427 folderMan->setSyncEnabled(true);
428}
429
430void AccountSettings::slotRemoveCurrentFolder()
431{
432 FolderMan *folderMan = FolderMan::instance();
433 auto folder = folderMan->folder(selectedFolderAlias());
434 QModelIndex selected = ui->_folderList->selectionModel()->currentIndex();
435 if (selected.isValid() && folder) {
436 int row = selected.row();
437
438 qCInfo(lcAccountSettings) << "Remove Folder alias " << folder->alias();
439 QString shortGuiLocalPath = folder->shortGuiLocalPath();
440
441 QMessageBox messageBox(QMessageBox::Question,
442 tr("Confirm Folder Sync Connection Removal"),
443 tr("<p>Do you really want to stop syncing the folder <i>%1</i>?</p>"
444 "<p><b>Note:</b> This will <b>not</b> delete any files.</p>")
445 .arg(shortGuiLocalPath),
446 QMessageBox::NoButton,
447 this);
448 QPushButton *yesButton =
449 messageBox.addButton(tr("Remove Folder Sync Connection"), QMessageBox::YesRole);
450 messageBox.addButton(tr("Cancel"), QMessageBox::NoRole);
451
452 messageBox.exec();
453 if (messageBox.clickedButton() != yesButton) {
454 return;
455 }
456
457 folderMan->removeFolder(folder);
458 _model->removeRow(row);
459
460 // single folder fix to show add-button and hide remove-button
461
462 emit folderChanged();
463 }
464}
465
466void AccountSettings::slotOpenCurrentFolder()
467{
468 auto alias = selectedFolderAlias();
469 if (!alias.isEmpty()) {
470 emit openFolderAlias(alias);
471 }
472}
473
474void AccountSettings::slotOpenCurrentLocalSubFolder()
475{
476 QModelIndex selected = ui->_folderList->selectionModel()->currentIndex();
477 if (!selected.isValid() || _model->classify(selected) != FolderStatusModel::SubFolder)
478 return;
479 QString fileName = _model->data(selected, FolderStatusDelegate::FolderPathRole).toString();
480 QUrl url = QUrl::fromLocalFile(fileName);
481 QDesktopServices::openUrl(url);
482}
483
484void AccountSettings::showConnectionLabel(const QString &message, QStringList errors)
485{
486 const QString errStyle = QLatin1String("color:#ffffff; background-color:#bb4d4d;padding:5px;"
487 "border-width: 1px; border-style: solid; border-color: #aaaaaa;"
488 "border-radius:5px;");
489 if (errors.isEmpty()) {
490 ui->connectLabel->setText(message);
491 ui->connectLabel->setToolTip(QString());
492 ui->connectLabel->setStyleSheet(QString());
493 } else {
494 errors.prepend(message);
495 const QString msg = errors.join(QLatin1String("\n"));
496 qCDebug(lcAccountSettings) << msg;
497 ui->connectLabel->setText(msg);
498 ui->connectLabel->setToolTip(QString());
499 ui->connectLabel->setStyleSheet(errStyle);
500 }
501 ui->accountStatus->setVisible(!message.isEmpty());
502}
503
504void AccountSettings::slotEnableCurrentFolder()
505{
506 auto alias = selectedFolderAlias();
507
508 if (!alias.isEmpty()) {
509 FolderMan *folderMan = FolderMan::instance();
510
511 qCInfo(lcAccountSettings) << "Application: enable folder with alias " << alias;
512 bool terminate = false;
513 bool currentlyPaused = false;
514
515 // this sets the folder status to disabled but does not interrupt it.
516 Folder *f = folderMan->folder(alias);
517 if (!f) {
518 return;
519 }
520 currentlyPaused = f->syncPaused();
521 if (!currentlyPaused) {
522 // check if a sync is still running and if so, ask if we should terminate.
523 if (f->isBusy()) { // its still running
524#if defined(Q_OS_MAC)
525 QWidget *parent = this;
526 Qt::WindowFlags flags = Qt::Sheet;
527#else
528 QWidget *parent = 0;
529 Qt::WindowFlags flags = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint; // default flags
530#endif
531 QMessageBox msgbox(QMessageBox::Question, tr("Sync Running"),
532 tr("The syncing operation is running.<br/>Do you want to terminate it?"),
533 QMessageBox::Yes | QMessageBox::No, parent, flags);
534 msgbox.setDefaultButton(QMessageBox::Yes);
535 int reply = msgbox.exec();
536 if (reply == QMessageBox::Yes)
537 terminate = true;
538 else
539 return; // do nothing
540 }
541 }
542
543 // message box can return at any time while the thread keeps running,
544 // so better check again after the user has responded.
545 if (f->isBusy() && terminate) {
546 f->slotTerminateSync();
547 }
548 f->setSyncPaused(!currentlyPaused);
549
550 // keep state for the icon setting.
551 if (currentlyPaused)
552 _wasDisabledBefore = true;
553
554 _model->slotUpdateFolderState(f);
555 }
556}
557
558void AccountSettings::slotScheduleCurrentFolder()
559{
560 FolderMan *folderMan = FolderMan::instance();
561 if (auto folder = folderMan->folder(selectedFolderAlias())) {
562 folderMan->scheduleFolder(folder);
563 }
564}
565
566void AccountSettings::slotScheduleCurrentFolderForceRemoteDiscovery()
567{
568 FolderMan *folderMan = FolderMan::instance();
569 if (auto folder = folderMan->folder(selectedFolderAlias())) {
570 folder->journalDb()->forceRemoteDiscoveryNextSync();
571 folderMan->scheduleFolder(folder);
572 }
573}
574
575void AccountSettings::slotForceSyncCurrentFolder()
576{
577 FolderMan *folderMan = FolderMan::instance();
578 if (auto selectedFolder = folderMan->folder(selectedFolderAlias())) {
579 // Terminate and reschedule any running sync
580 if (Folder *current = folderMan->currentSyncFolder()) {
581 folderMan->terminateSyncProcess();
582 folderMan->scheduleFolder(current);
583 }
584
585 // Insert the selected folder at the front of the queue
586 folderMan->scheduleFolderNext(selectedFolder);
587 }
588}
589
590void AccountSettings::slotOpenOC()
591{
592 if (_OCUrl.isValid())
593 QDesktopServices::openUrl(_OCUrl);
594}
595
596void AccountSettings::slotUpdateQuota(qint64 total, qint64 used)
597{
598 if (total > 0) {
599 ui->quotaProgressBar->setVisible(true);
600 ui->quotaProgressBar->setEnabled(true);
601 // workaround the label only accepting ints (which may be only 32 bit wide)
602 const double percent = used / (double)total * 100;
603 const int percentInt = qMin(qRound(percent), 100);
604 ui->quotaProgressBar->setValue(percentInt);
605 QString usedStr = Utility::octetsToString(used);
606 QString totalStr = Utility::octetsToString(total);
607 QString percentStr = Utility::compactFormatDouble(percent, 1);
608 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);
609 ui->quotaInfoLabel->setText(tr("%1 of %2 in use").arg(usedStr, totalStr));
610 ui->quotaInfoLabel->setToolTip(toolTip);
611 ui->quotaProgressBar->setToolTip(toolTip);
612 } else {
613 ui->quotaProgressBar->setVisible(false);
614 ui->quotaInfoLabel->setToolTip(QString());
615
616 /* -1 means not computed; -2 means unknown; -3 means unlimited (#3940)*/
617 if (total == 0 || total == -1) {
618 ui->quotaInfoLabel->setText(tr("Currently there is no storage usage information available."));
619 } else {
620 QString usedStr = Utility::octetsToString(used);
621 ui->quotaInfoLabel->setText(tr("%1 in use").arg(usedStr));
622 }
623 }
624}
625
626void AccountSettings::slotAccountStateChanged()
627{
628 int state = _accountState ? _accountState->state() : AccountState::Disconnected;
629 if (_accountState) {
630 ui->sslButton->updateAccountState(_accountState);
631 AccountPtr account = _accountState->account();
632 QUrl safeUrl(account->url());
633 safeUrl.setPassword(QString()); // Remove the password from the URL to avoid showing it in the UI
634 FolderMan *folderMan = FolderMan::instance();
635 foreach (Folder *folder, folderMan->map().values()) {
636 _model->slotUpdateFolderState(folder);
637 }
638
639 QString server = QString::fromLatin1("<a href=\"%1\">%2</a>")
640 .arg(Utility::escape(account->url().toString()),
641 Utility::escape(safeUrl.toString()));
642 QString serverWithUser = server;
643 if (AbstractCredentials *cred = account->credentials()) {
644 QString user = account->davDisplayName();
645 if (user.isEmpty()) {
646 user = cred->user();
647 }
648 serverWithUser = tr("%1 as <i>%2</i>").arg(server, Utility::escape(user));
649 }
650
651 if (state == AccountState::Connected) {
652 QStringList errors;
653 if (account->serverVersionUnsupported()) {
654 errors << tr("The server version %1 is old and unsupported! Proceed at your own risk.").arg(account->serverVersion());
655 }
656 showConnectionLabel(tr("Connected to %1.").arg(serverWithUser), errors);
657 } else if (state == AccountState::ServiceUnavailable) {
658 showConnectionLabel(tr("Server %1 is temporarily unavailable.").arg(server));
659 } else if (state == AccountState::MaintenanceMode) {
660 showConnectionLabel(tr("Server %1 is currently in maintenance mode.").arg(server));
661 } else if (state == AccountState::SignedOut) {
662 showConnectionLabel(tr("Signed out from %1.").arg(serverWithUser));
663 } else if (state == AccountState::AskingCredentials) {
664 QUrl url;
665 if (auto cred = qobject_cast<HttpCredentialsGui *>(account->credentials())) {
666 connect(cred, &HttpCredentialsGui::authorisationLinkChanged,
667 this, &AccountSettings::slotAccountStateChanged, Qt::UniqueConnection);
668 url = cred->authorisationLink();
669 }
670 if (url.isValid()) {
671 showConnectionLabel(tr("Obtaining authorization from the browser. "
672 "<a href='%1'>Click here</a> to re-open the browser.")
673 .arg(url.toString(QUrl::FullyEncoded)));
674 } else {
675 showConnectionLabel(tr("Connecting to %1...").arg(serverWithUser));
676 }
677 } else {
678 showConnectionLabel(tr("No connection to %1 at %2.")
679 .arg(Utility::escape(Theme::instance()->appNameGUI()), server),
680 _accountState->connectionErrors());
681 }
682 } else {
683 // ownCloud is not yet configured.
684 showConnectionLabel(tr("No %1 connection configured.")
685 .arg(Utility::escape(Theme::instance()->appNameGUI())));
686 }
687
688 /* Allow to expand the item if the account is connected. */
689 ui->_folderList->setItemsExpandable(state == AccountState::Connected);
690
691 if (state != AccountState::Connected) {
692 /* check if there are expanded root items, if so, close them */
693 int i;
694 for (i = 0; i < _model->rowCount(); ++i) {
695 if (ui->_folderList->isExpanded(_model->index(i)))
696 ui->_folderList->setExpanded(_model->index(i), false);
697 }
698 }
699
700 // Disabling expansion of folders might require hiding the selective
701 // sync user interface buttons.
702 refreshSelectiveSyncStatus();
703
704 /* set the correct label for the Account toolbox button */
705 if (_accountState) {
706 if (_accountState->isSignedOut()) {
707 _toggleSignInOutAction->setText(tr("Log in"));
708 } else {
709 _toggleSignInOutAction->setText(tr("Log out"));
710 }
711 }
712}
713
714void AccountSettings::slotLinkActivated(const QString &link)
715{
716 // Parse folder alias and filename from the link, calculate the index
717 // and select it if it exists.
718 const QStringList li = link.split(QLatin1String("?folder="));
719 if (li.count() > 1) {
720 QString myFolder = li[0];
721 const QString alias = li[1];
722 if (myFolder.endsWith(QLatin1Char('/')))
723 myFolder.chop(1);
724
725 // Make sure the folder itself is expanded
726 Folder *f = FolderMan::instance()->folder(alias);
727 QModelIndex folderIndx = _model->indexForPath(f, QString());
728 if (!ui->_folderList->isExpanded(folderIndx)) {
729 ui->_folderList->setExpanded(folderIndx, true);
730 }
731
732 QModelIndex indx = _model->indexForPath(f, myFolder);
733 if (indx.isValid()) {
734 // make sure all the parents are expanded
735 for (auto i = indx.parent(); i.isValid(); i = i.parent()) {
736 if (!ui->_folderList->isExpanded(i)) {
737 ui->_folderList->setExpanded(i, true);
738 }
739 }
740 ui->_folderList->setSelectionMode(QAbstractItemView::SingleSelection);
741 ui->_folderList->setCurrentIndex(indx);
742 ui->_folderList->scrollTo(indx);
743 } else {
744 qCWarning(lcAccountSettings) << "Unable to find a valid index for " << myFolder;
745 }
746 }
747}
748
749AccountSettings::~AccountSettings()
750{
751 delete ui;
752}
753
754void AccountSettings::refreshSelectiveSyncStatus()
755{
756 QString msg;
757 int cnt = 0;
758 foreach (Folder *folder, FolderMan::instance()->map().values()) {
759 if (folder->accountState() != _accountState) {
760 continue;
761 }
762
763 bool ok;
764 auto undecidedList = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList, &ok);
765 QString p;
766 foreach (const auto &it, undecidedList) {
767 // FIXME: add the folder alias in a hoover hint.
768 // folder->alias() + QLatin1String("/")
769 if (cnt++) {
770 msg += QLatin1String(", ");
771 }
772 QString myFolder = (it);
773 if (myFolder.endsWith('/')) {
774 myFolder.chop(1);
775 }
776 QModelIndex theIndx = _model->indexForPath(folder, myFolder);
777 if (theIndx.isValid()) {
778 msg += QString::fromLatin1("<a href=\"%1?folder=%2\">%1</a>")
779 .arg(Utility::escape(myFolder), Utility::escape(folder->alias()));
780 } else {
781 msg += myFolder; // no link because we do not know the index yet.
782 }
783 }
784 }
785
786 // Some selective sync ui (either normal editing or big folder) will show
787 // if this variable ends up true.
788 bool shouldBeVisible = false;
789
790 if (msg.isEmpty()) {
791 // Show the ui if the model is dirty only
792 shouldBeVisible = _model->isDirty() && _accountState->isConnected();
793
794 ui->selectiveSyncButtons->setVisible(true);
795 ui->bigFolderUi->setVisible(false);
796 } else {
797 // There's a reason the big folder ui should be shown
798 shouldBeVisible = _accountState->isConnected();
799
800 ConfigFile cfg;
801 QString info = !cfg.confirmExternalStorage()
802 ? tr("There are folders that were not synchronized because they are too big: ")
803 : !cfg.newBigFolderSizeLimit().first
804 ? tr("There are folders that were not synchronized because they are external storages: ")
805 : tr("There are folders that were not synchronized because they are too big or external storages: ");
806
807 ui->selectiveSyncNotification->setText(info + msg);
808 ui->selectiveSyncButtons->setVisible(false);
809 ui->bigFolderUi->setVisible(true);
810 }
811
812 ui->selectiveSyncApply->setEnabled(_model->isDirty() || !msg.isEmpty());
813 bool wasVisible = !ui->selectiveSyncStatus->isHidden();
814 if (wasVisible != shouldBeVisible) {
815 QSize hint = ui->selectiveSyncStatus->sizeHint();
816 if (shouldBeVisible) {
817 ui->selectiveSyncStatus->setMaximumHeight(0);
818 ui->selectiveSyncStatus->setVisible(true);
819 doExpand();
820 }
821 auto anim = new QPropertyAnimation(ui->selectiveSyncStatus, "maximumHeight", ui->selectiveSyncStatus);
822 anim->setEndValue(shouldBeVisible ? hint.height() : 0);
823 anim->start(QAbstractAnimation::DeleteWhenStopped);
824 connect(anim, &QPropertyAnimation::finished, [this, shouldBeVisible]() {
825 ui->selectiveSyncStatus->setMaximumHeight(QWIDGETSIZE_MAX);
826 if (!shouldBeVisible) {
827 ui->selectiveSyncStatus->hide();
828 }
829 });
830 }
831}
832
833void AccountSettings::slotAccountAdded(AccountState *)
834{
835 // if the theme is limited to single account, the button must hide if
836 // there is already one account.
837 int s = AccountManager::instance()->accounts().size();
838 if (s > 0 && !Theme::instance()->multiAccount()) {
839 _addAccountAction->setVisible(false);
840 } else {
841 _addAccountAction->setVisible(true);
842 }
843}
844
845void AccountSettings::slotDeleteAccount()
846{
847 // Deleting the account potentially deletes 'this', so
848 // the QMessageBox should be destroyed before that happens.
849 {
850 QMessageBox messageBox(QMessageBox::Question,
851 tr("Confirm Account Removal"),
852 tr("<p>Do you really want to remove the connection to the account <i>%1</i>?</p>"
853 "<p><b>Note:</b> This will <b>not</b> delete any files.</p>")
854 .arg(_accountState->account()->displayName()),
855 QMessageBox::NoButton,
856 this);
857 QPushButton *yesButton =
858 messageBox.addButton(tr("Remove connection"), QMessageBox::YesRole);
859 messageBox.addButton(tr("Cancel"), QMessageBox::NoRole);
860
861 messageBox.exec();
862 if (messageBox.clickedButton() != yesButton) {
863 return;
864 }
865 }
866
867 // Else it might access during destruction. This should be better handled by it having a QSharedPointer
868 _model->setAccountState(0);
869
870 auto manager = AccountManager::instance();
871 manager->deleteAccount(_accountState);
872 manager->save();
873
874 // IMPORTANT: "this" is deleted from this point on. We should probably remove this synchronous
875 // .exec() QMessageBox magic above as it recurses into the event loop.
876}
877
878bool AccountSettings::event(QEvent *e)
879{
880 if (e->type() == QEvent::Hide || e->type() == QEvent::Show) {
881 _quotaInfo.setActive(isVisible());
882 }
883 if (e->type() == QEvent::Show) {
884 // Expand the folder automatically only if there's only one, see #4283
885 // The 2 is 1 folder + 1 'add folder' button
886 if (_model->rowCount() <= 2) {
887 ui->_folderList->setExpanded(_model->index(0, 0), true);
888 }
889 }
890 return QWidget::event(e);
891}
892
893} // namespace OCC
894
895#include "accountsettings.moc"
896