From 426d2338d9106b3d3d28211890838af0f143c80b Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Fri, 27 Mar 2015 11:46:03 +0100 Subject: [PATCH] Settings: New UI that intergate the selective sync within the account settings --- src/gui/accountsettings.cpp | 258 +++++++---------- src/gui/accountsettings.h | 17 +- src/gui/accountsettings.ui | 221 ++++++++------- src/gui/folderstatusmodel.cpp | 487 +++++++++++++++++++++++++++++++- src/gui/folderstatusmodel.h | 72 ++++- src/gui/generalsettings.cpp | 26 ++ src/gui/generalsettings.h | 7 +- src/gui/generalsettings.ui | 67 ++++- src/gui/selectivesyncdialog.cpp | 2 - 9 files changed, 864 insertions(+), 293 deletions(-) diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index f46cf80b1..5eab1b01b 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -21,13 +21,10 @@ #include "folderstatusmodel.h" #include "utility.h" #include "application.h" -#include "owncloudsetupwizard.h" #include "configfile.h" -#include "ignorelisteditor.h" #include "account.h" #include "accountstate.h" #include "quotainfo.h" -#include "selectivesyncdialog.h" #include "creds/abstractcredentials.h" #include @@ -37,9 +34,13 @@ #include #include #include +#include +#include #include #include #include +#include +#include #include "account.h" @@ -64,10 +65,12 @@ AccountSettings::AccountSettings(QWidget *parent) : ui->setupUi(this); _model = new FolderStatusModel; + _model->setAccount(_accountState->account()); _model->setParent(this); FolderStatusDelegate *delegate = new FolderStatusDelegate; delegate->setParent(this); + ui->_folderList->header()->hide(); ui->_folderList->setItemDelegate( delegate ); ui->_folderList->setModel( _model ); #if defined(Q_OS_MAC) @@ -75,12 +78,13 @@ AccountSettings::AccountSettings(QWidget *parent) : #else ui->_folderList->setMinimumWidth( 300 ); #endif - ui->_folderList->setEditTriggers( QAbstractItemView::NoEditTriggers ); + connect(ui->_folderList, SIGNAL(customContextMenuRequested(QPoint)), + this, SLOT(slotCustomContextMenuRequested(QPoint))); - ui->_buttonRemove->setEnabled(false); - ui->_buttonEnable->setEnabled(false); - ui->_buttonSelectiveSync->setEnabled(false); - ui->_buttonAdd->setEnabled(true); + connect(ui->_folderList, SIGNAL(expanded(QModelIndex)) , this, SLOT(refreshSelectiveSyncStatus())); + connect(ui->_folderList, SIGNAL(collapsed(QModelIndex)) , this, SLOT(refreshSelectiveSyncStatus())); + connect(_model, SIGNAL(dirtyChanged()), this, SLOT(refreshSelectiveSyncStatus())); + ui->selectiveSyncStatus->hide(); QAction *resetFolderAction = new QAction(this); resetFolderAction->setShortcut(QKeySequence(Qt::Key_F5)); @@ -92,35 +96,59 @@ AccountSettings::AccountSettings(QWidget *parent) : connect(syncNowAction, SIGNAL(triggered()), SLOT(slotSyncCurrentFolderNow())); addAction(syncNowAction); - connect(ui->_buttonRemove, SIGNAL(clicked()), this, SLOT(slotRemoveCurrentFolder())); - connect(ui->_buttonEnable, SIGNAL(clicked()), this, SLOT(slotEnableCurrentFolder())); - connect(ui->_buttonAdd, SIGNAL(clicked()), this, SLOT(slotAddFolder())); - connect(ui->_buttonSelectiveSync, SIGNAL(clicked()), this, SLOT(slotSelectiveSync())); - connect(ui->modifyAccountButton, SIGNAL(clicked()), SLOT(slotOpenAccountWizard())); - connect(ui->ignoredFilesButton, SIGNAL(clicked()), SLOT(slotIgnoreFilesEditor()));; - connect(ui->_folderList, SIGNAL(clicked(QModelIndex)), SLOT(slotFolderActivated(QModelIndex))); connect(ui->_folderList, SIGNAL(doubleClicked(QModelIndex)),SLOT(slotDoubleClicked(QModelIndex))); + connect(ui->selectiveSyncApply, SIGNAL(clicked()), _model, SLOT(slotApplySelectiveSync())); + connect(ui->selectiveSyncCancel, SIGNAL(clicked()), _model, SLOT(resetFolders())); + connect(FolderMan::instance(), SIGNAL(folderListLoaded(Folder::Map)), _model, SLOT(resetFolders())); + connect(this, SIGNAL(folderChanged()), _model, SLOT(resetFolders())); + + QColor color = palette().highlight().color(); ui->quotaProgressBar->setStyleSheet(QString::fromLatin1(progressBarStyleC).arg(color.name())); ui->connectLabel->setWordWrap(true); ui->connectLabel->setOpenExternalLinks(true); - ui->quotaLabel->setWordWrap(true); + QFont smallFont = ui->quotaInfoLabel->font(); + smallFont.setPointSize(smallFont.pointSize() * 0.8); + ui->quotaInfoLabel->setFont(smallFont); + + _quotaLabel = new QLabel(ui->quotaProgressBar); + (new QVBoxLayout(ui->quotaProgressBar))->addWidget(_quotaLabel); ui->connectLabel->setText(tr("No account configured.")); - ui->_buttonAdd->setEnabled(false); connect(AccountStateManager::instance(), SIGNAL(accountStateAdded(AccountState*)), this, SLOT(slotAccountStateChanged(AccountState*))); slotAccountStateChanged(AccountStateManager::instance()->accountState()); - - FolderMan *folderMan = FolderMan::instance(); - connect(folderMan, SIGNAL(folderListLoaded(Folder::Map)), - this, SLOT(setFolderList(Folder::Map))); - setFolderList(FolderMan::instance()->map()); } +void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) +{ + QTreeView *tv = ui->_folderList; + QModelIndex index = tv->indexAt(pos); + if (!index.isValid()) { + return; + } + + QString alias = _model->data( index, FolderStatusDelegate::FolderAliasRole ).toString(); + if (alias.isEmpty()) { + return; + } + + tv->setCurrentIndex(index); + bool folderPaused = _model->data( index, FolderStatusDelegate::FolderSyncPaused).toBool(); + + QMenu *menu = new QMenu(tv); + menu->setAttribute(Qt::WA_DeleteOnClose); + connect(menu->addAction(tr("Remove folder")), SIGNAL(triggered(bool)), + this, SLOT(slotRemoveCurrentFolder())); + connect(menu->addAction(folderPaused ? tr("Resume") : tr("Pause")), SIGNAL(triggered(bool)), + this, SLOT(slotEnableCurrentFolder())); + menu->exec(tv->mapToGlobal(pos)); +} + + void AccountSettings::slotAccountStateChanged(AccountState *newAccountState) { if (_accountState) { @@ -140,40 +168,16 @@ void AccountSettings::slotAccountStateChanged(AccountState *newAccountState) this, SLOT(slotUpdateQuota(qint64,qint64))); slotUpdateQuota(quotaInfo->lastQuotaTotalBytes(), quotaInfo->lastQuotaUsedBytes()); } - } void AccountSettings::slotFolderActivated( const QModelIndex& indx ) { - bool isValid = indx.isValid(); - - bool haveFolders = ui->_folderList->model()->rowCount() > 0; - - ui->_buttonRemove->setEnabled(isValid); - if( Theme::instance()->singleSyncFolder() ) { - // only one folder synced folder allowed. - ui->_buttonAdd->setVisible(!haveFolders); - } else { - ui->_buttonAdd->setVisible(true); - } - bool isConnected = _accountState && _accountState->isConnected(); - ui->_buttonAdd->setEnabled(isConnected); - ui->_buttonEnable->setEnabled( isValid ); - ui->_buttonSelectiveSync->setEnabled(isConnected && isValid); - - if ( isValid ) { - bool folderPaused = _model->data( indx, FolderStatusDelegate::FolderSyncPaused).toBool(); - if ( !folderPaused) { - ui->_buttonEnable->setText( tr( "Pause" ) ); - } else { - ui->_buttonEnable->setText( tr( "Resume" ) ); + if (indx.data(FolderStatusDelegate::AddButton).toBool()) { + slotAddFolder(); + return; } - ui->_buttonEnable->setEnabled(isConnected); - } } - - void AccountSettings::slotAddFolder() { FolderMan *folderMan = FolderMan::instance(); @@ -204,13 +208,11 @@ void AccountSettings::slotFolderWizardAccepted() return; Folder *f = folderMan->setupFolderFromConfigFile( alias ); - slotAddFolder( f ); folderMan->setSyncEnabled(true); if( f ) { folderMan->slotScheduleAllFolders(); emit folderChanged(); } - slotButtonsSetEnabled(); } void AccountSettings::slotFolderWizardRejected() @@ -221,32 +223,6 @@ void AccountSettings::slotFolderWizardRejected() folderMan->slotScheduleAllFolders(); } -void AccountSettings::slotOpenAccountWizard() -{ - if (QSystemTrayIcon::isSystemTrayAvailable()) { - topLevelWidget()->close(); - } - OwncloudSetupWizard::runWizard(qApp, SLOT(slotownCloudWizardDone(int)), 0); -} - -void AccountSettings::slotAddFolder( Folder *folder ) -{ - if( ! folder || folder->alias().isEmpty() ) return; - - QStandardItem *item = new QStandardItem(); - folderToModelItem( item, folder, _accountState && _accountState->isConnectedOrMaintenance()); - _model->appendRow( item ); - // in order to update the enabled state of the "Sync now" button - connect(folder, SIGNAL(syncStateChange()), this, SLOT(slotFolderSyncStateChange()), Qt::UniqueConnection); -} - -void AccountSettings::slotButtonsSetEnabled() -{ - QModelIndex selected = ui->_folderList->currentIndex(); - - slotFolderActivated(selected); -} - void AccountSettings::setGeneralErrors( const QStringList& errors ) { _generalErrors = errors; @@ -339,24 +315,12 @@ void AccountSettings::slotRemoveCurrentFolder() if( ret == QMessageBox::No ) { return; } - /* Remove the selected item from the timer hash. */ - QStandardItem *item = NULL; - if( selected.isValid() ) - item = _model->itemFromIndex(selected); - - if( selected.isValid() && item && _hideProgressTimers.contains(item) ) { - QTimer *t = _hideProgressTimers[item]; - t->stop(); - _hideProgressTimers.remove(item); - delete(t); - } FolderMan *folderMan = FolderMan::instance(); folderMan->slotRemoveFolder( alias ); _model->removeRow(row); // single folder fix to show add-button and hide remove-button - slotButtonsSetEnabled(); emit folderChanged(); } @@ -368,6 +332,8 @@ void AccountSettings::slotResetCurrentFolder() QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); if( selected.isValid() ) { QString alias = _model->data( selected, FolderStatusDelegate::FolderAliasRole ).toString(); + if (alias.isEmpty()) + return; int ret = QMessageBox::question( 0, tr("Confirm Folder Reset"), tr("

Do you really want to reset folder %1 and rebuild your client database?

" "

Note: This function is designed for maintenance purposes only. " @@ -385,23 +351,11 @@ void AccountSettings::slotResetCurrentFolder() } } -void AccountSettings::slotSelectiveSync() -{ - QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); - if( selected.isValid() ) { - QString alias = _model->data( selected, FolderStatusDelegate::FolderAliasRole ).toString(); - FolderMan *folderMan = FolderMan::instance(); - Folder *f = folderMan->folder(alias); - if (f) { - (new SelectiveSyncDialog(AccountManager::instance()->account(), f, this))->open(); - } - } -} - void AccountSettings::slotDoubleClicked( const QModelIndex& indx ) { if( ! indx.isValid() ) return; QString alias = _model->data( indx, FolderStatusDelegate::FolderAliasRole ).toString(); + if (alias.isEmpty()) return; emit openFolderAlias( alias ); } @@ -421,28 +375,7 @@ void AccountSettings::showConnectionLabel( const QString& message, const QString ui->connectLabel->setToolTip(QString()); ui->connectLabel->setStyleSheet(errStyle); } -} - -void AccountSettings::setFolderList( const Folder::Map &folders ) -{ - _model->clear(); - - foreach(QTimer *t, _hideProgressTimers) { - t->stop(); - delete t; - } - _hideProgressTimers.clear(); - - foreach( Folder *f, folders ) { - slotAddFolder( f ); - } - - QModelIndex idx = _model->index(0, 0); - if (idx.isValid()) { - ui->_folderList->setCurrentIndex(idx); - } - slotButtonsSetEnabled(); - + ui->accountStatus->setVisible(!message.isEmpty()); } void AccountSettings::slotEnableCurrentFolder() @@ -503,8 +436,6 @@ void AccountSettings::slotEnableCurrentFolder() if( currentlyPaused ) _wasDisabledBefore = true; slotUpdateFolderState (f); - // set the button text accordingly. - slotFolderActivated( selected ); } } @@ -525,7 +456,7 @@ void AccountSettings::slotUpdateFolderState( Folder *folder ) int row = 0; if( ! folder ) return; - +#if 0 item = _model->item( row ); while( item ) { @@ -541,6 +472,7 @@ void AccountSettings::slotUpdateFolderState( Folder *folder ) } else { // the dialog is not visible. } +#endif } void AccountSettings::slotOpenOC() @@ -551,24 +483,7 @@ void AccountSettings::slotOpenOC() QStandardItem* AccountSettings::itemForFolder(const QString& folder) { - QStandardItem *item = NULL; - - if( folder.isEmpty() ) { - return item; - } - - int row = 0; - - item = _model->item( row ); - - while( item ) { - if( item->data( FolderStatusDelegate::FolderAliasRole ) == folder ) { - // its the item to update! - break; - } - item = _model->item( ++row ); - } - return item; + return nullptr; } QString AccountSettings::shortenFilename( const QString& folder, const QString& file ) const @@ -593,6 +508,7 @@ QString AccountSettings::shortenFilename( const QString& folder, const QString& void AccountSettings::slotSetProgress(const QString& folder, const Progress::Info &progress ) { +#if 0 if (!isVisible()) { return; // for https://github.com/owncloud/client/issues/2648#issuecomment-71377909 } @@ -686,10 +602,12 @@ void AccountSettings::slotSetProgress(const QString& folder, const Progress::Inf } overallPercent = qBound(0, overallPercent, 100); item->setData( overallPercent, FolderStatusDelegate::SyncProgressOverallPercent); +#endif } void AccountSettings::slotHideProgress() { +#if 0 QTimer *send_timer = qobject_cast(this->sender()); QHash::const_iterator i = _hideProgressTimers.constBegin(); while (i != _hideProgressTimers.constEnd()) { @@ -717,11 +635,12 @@ void AccountSettings::slotHideProgress() } send_timer->deleteLater(); +#endif } void AccountSettings::slotFolderSyncStateChange() { - slotButtonsSetEnabled(); +#if 0 Folder* folder = qobject_cast(sender()); if (!folder) return; @@ -745,12 +664,14 @@ void AccountSettings::slotFolderSyncStateChange() } timer->start(5000); } +#endif } void AccountSettings::slotUpdateQuota(qint64 total, qint64 used) { if( total > 0 ) { + ui->storageGroupBox->setVisible(true); ui->quotaProgressBar->setVisible(true); ui->quotaInfoLabel->setVisible(true); ui->quotaProgressBar->setEnabled(true); @@ -763,22 +684,12 @@ void AccountSettings::slotUpdateQuota(qint64 total, qint64 used) QString totalStr = Utility::octetsToString(total); double percent = used/(double)total*100; QString percentStr = Utility::compactFormatDouble(percent, 1); - ui->quotaLabel->setText(tr("%1 (%3%) of %2 server space in use.").arg(usedStr, totalStr, percentStr)); + _quotaLabel->setText(tr("%1 (%3%) of %2 server space in use.").arg(usedStr, totalStr, percentStr)); } else { - ui->quotaProgressBar->setVisible(false); + ui->storageGroupBox->setVisible(false); ui->quotaInfoLabel->setVisible(false); - ui->quotaLabel->setText(tr("Currently there is no storage usage information available.")); - } -} - -void AccountSettings::slotIgnoreFilesEditor() -{ - if (_ignoreEditor.isNull()) { - _ignoreEditor = new IgnoreListEditor(this); - _ignoreEditor->setAttribute( Qt::WA_DeleteOnClose, true ); - _ignoreEditor->open(); - } else { - ownCloudGui::raiseDialog(_ignoreEditor); + ui->quotaProgressBar->setMaximum(0); + _quotaLabel->setText(tr("Currently there is no storage usage information available.")); } } @@ -789,7 +700,6 @@ void AccountSettings::slotAccountStateChanged(int state) AccountPtr account = _accountState->account(); QUrl safeUrl(account->url()); safeUrl.setPassword(QString()); // Remove the password from the URL to avoid showing it in the UI - slotButtonsSetEnabled(); FolderMan *folderMan = FolderMan::instance(); foreach (Folder *folder, folderMan->map().values()) { slotUpdateFolderState(folder); @@ -815,7 +725,6 @@ void AccountSettings::slotAccountStateChanged(int state) } else { // ownCloud is not yet configured. showConnectionLabel( tr("No %1 connection configured.").arg(Theme::instance()->appNameGUI()) ); - ui->_buttonAdd->setEnabled( false); } } @@ -824,4 +733,29 @@ AccountSettings::~AccountSettings() delete ui; } +void AccountSettings::refreshSelectiveSyncStatus() +{ + ui->selectiveSyncApply->setEnabled(_model->isDirty()); + ui->selectiveSyncCancel->setEnabled(_model->isDirty()); + bool shouldBeVisible = _model->isDirty(); + for (int i = 0; !shouldBeVisible && i < _model->rowCount(); ++i) { + if (ui->_folderList->isExpanded(_model->index(i))) + shouldBeVisible = true; + } + bool wasVisible = ui->selectiveSyncApply->isVisible(); + if (wasVisible != shouldBeVisible) { + QSize hint = ui->selectiveSyncStatus->sizeHint(); + if (shouldBeVisible) { + ui->selectiveSyncStatus->setMaximumHeight(0); + ui->selectiveSyncStatus->setVisible(true); + } + auto anim = new QPropertyAnimation(ui->selectiveSyncStatus, "maximumHeight", ui->selectiveSyncStatus); + anim->setEndValue(shouldBeVisible ? hint.height() : 0); + anim->start(QAbstractAnimation::DeleteWhenStopped); + if (!shouldBeVisible) { + connect(anim, SIGNAL(finished()), ui->selectiveSyncStatus, SLOT(hide())); + } + } +} + } // namespace OCC diff --git a/src/gui/accountsettings.h b/src/gui/accountsettings.h index 94110a9bc..da0132bb7 100644 --- a/src/gui/accountsettings.h +++ b/src/gui/accountsettings.h @@ -29,6 +29,7 @@ class QModelIndex; class QStandardItem; class QNetworkReply; class QListWidgetItem; +class QLabel; namespace OCC { @@ -37,9 +38,10 @@ class AccountSettings; } class FolderMan; -class IgnoreListEditor; + class Account; class AccountState; +class FolderStatusModel; class AccountSettings : public QWidget { @@ -62,27 +64,22 @@ public slots: void slotUpdateFolderState( Folder* ); void slotDoubleClicked( const QModelIndex& ); void slotSetProgress(const QString& folder, const Progress::Info& progress); - void slotButtonsSetEnabled(); void slotUpdateQuota( qint64,qint64 ); - void slotIgnoreFilesEditor(); void slotAccountStateChanged(int state); void setGeneralErrors( const QStringList& errors ); - void setFolderList( const Folder::Map& ); protected slots: void slotAddFolder(); - void slotAddFolder( Folder* ); void slotEnableCurrentFolder(); void slotSyncCurrentFolderNow(); void slotRemoveCurrentFolder(); void slotResetCurrentFolder(); void slotFolderWizardAccepted(); void slotFolderWizardRejected(); - void slotOpenAccountWizard(); void slotHideProgress(); - void slotSelectiveSync(); + void refreshSelectiveSyncStatus(); private: QString shortenFilename( const QString& folder, const QString& file ) const; @@ -91,16 +88,18 @@ private: void showConnectionLabel( const QString& message, const QString& tooltip = QString() ); Ui::AccountSettings *ui; - QPointer _ignoreEditor; - QStandardItemModel *_model; + + FolderStatusModel *_model; QUrl _OCUrl; QHash _hideProgressTimers; QStringList _generalErrors; bool _wasDisabledBefore; AccountState *_accountState; + QLabel *_quotaLabel; private slots: void slotFolderSyncStateChange(); void slotAccountStateChanged(AccountState*); + void slotCustomContextMenuRequested(const QPoint&); }; } // namespace OCC diff --git a/src/gui/accountsettings.ui b/src/gui/accountsettings.ui index 82ea9e235..e2adf856b 100644 --- a/src/gui/accountsettings.ui +++ b/src/gui/accountsettings.ui @@ -6,101 +6,71 @@ 0 0 - 615 - 422 + 469 + 652 Form - - - - - Account to Synchronize - - - - - - - - - 0 - 0 - - - - - - - - - - - Connected with <server> as <user> - - - - + + + + + + + + + 0 + 0 + + + + + + - - + + + + + 0 + 0 + + + + Connected with <server> as <user> + + - - - - - - Add Folder... - - - - - - - Pause - - - - - - - Remove - - - - - - - Choose What to Sync - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - + + + + + 0 + 0 + + + + Delete + + - + + + + 0 + 0 + + Storage Usage - + @@ -117,17 +87,16 @@ - - - - Retrieving usage information... - - - + + + 0 + 0 + + - <b>Note:</b> Some folders, including network mounted or shared folders, might have different limits. + Some folders, including network mounted or shared folders, might have different limits. true @@ -137,26 +106,76 @@ - - - - Account Maintenance + + + + + 0 + 5 + - - - - - true + + Qt::CustomContextMenu + + + QAbstractItemView::NoEditTriggers + + + true + + + + + + + + 0 + 0 + + + + + QLayout::SetNoConstraint + + + + + + 0 + 0 + - Edit Ignored Files + Cancel - - + + + + + 0 + 0 + + - Modify Account + Apply + + + + + + + + 0 + 0 + + + + Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore + + + true diff --git a/src/gui/folderstatusmodel.cpp b/src/gui/folderstatusmodel.cpp index d86edd7b9..04a0e6a93 100644 --- a/src/gui/folderstatusmodel.cpp +++ b/src/gui/folderstatusmodel.cpp @@ -13,7 +13,10 @@ */ #include "folderstatusmodel.h" +#include "folderman.h" #include "utility.h" +#include +#include #include #include @@ -21,17 +24,42 @@ #include #endif +Q_DECLARE_METATYPE(QPersistentModelIndex) + namespace OCC { -FolderStatusModel::FolderStatusModel() - :QStandardItemModel() -{ +static const char propertyParentIndexC[] = "oc_parentIndex"; +FolderStatusModel::FolderStatusModel(QObject *parent) + :QAbstractItemModel(parent) +{ } -Qt::ItemFlags FolderStatusModel::flags ( const QModelIndex& ) const +FolderStatusModel::~FolderStatusModel() +{ } + + +void FolderStatusModel::setAccount(const AccountPtr& account) { - return Qt::ItemIsSelectable | Qt::ItemIsEnabled; + beginResetModel(); + _dirty = false; + _folders.clear(); + _account = account; + endResetModel(); +} + + +Qt::ItemFlags FolderStatusModel::flags ( const QModelIndex &index ) const +{ + switch (classify(index)) { + case AddButton: + return Qt::ItemIsEnabled; + case RootFolder: + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; + case SubFolder: + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; + } + return 0; } QVariant FolderStatusModel::data(const QModelIndex &index, int role) const @@ -41,10 +69,433 @@ QVariant FolderStatusModel::data(const QModelIndex &index, int role) const if (role == Qt::EditRole) return QVariant(); - else - return QStandardItemModel::data(index,role); + + switch(classify(index)) { + case AddButton: + if (role == FolderStatusDelegate::AddButton) + return QVariant(true); + return QVariant(); + case SubFolder: + { + const auto &x = static_cast(index.internalPointer())->_subs[index.row()]; + switch (role) { + case Qt::ToolTipRole: + case Qt::DisplayRole: + return x._name; + case Qt::CheckStateRole: + return x._checked; + case Qt::DecorationRole: + return QFileIconProvider().icon(QFileIconProvider::Folder); + } + } + return QVariant(); + case RootFolder: + break; + } + + auto folderList = FolderMan::instance()->map().values(); + auto f = folderList.at(index.row()); + if (!f) + return QVariant(); + + bool accountConnected = true; // FIXME + + switch (role) { + case FolderStatusDelegate::FolderPathRole : return f->nativePath(); + case FolderStatusDelegate::FolderSecondPathRole : return f->remotePath(); + case FolderStatusDelegate::FolderAliasRole : return f->alias(); + case FolderStatusDelegate::FolderSyncPaused : return f->syncPaused(); + case FolderStatusDelegate::FolderAccountConnected : return accountConnected; + case Qt::ToolTipRole: + return Theme::instance()->statusHeaderText(f->syncResult().status()); + case FolderStatusDelegate::FolderStatusIconRole: + if ( accountConnected ) { + auto theme = Theme::instance(); + auto status = f->syncResult().status(); + if( f->syncPaused() ) { + return theme->folderDisabledIcon( ); + } else { + if( status == SyncResult::SyncPrepare ) { + return theme->syncStateIcon(SyncResult::SyncRunning); + } else if( status == SyncResult::Undefined ) { + return theme->syncStateIcon( SyncResult::SyncRunning); + } else { + // kepp the previous icon for the prepare phase. + if( status == SyncResult::Problem) { + return theme->syncStateIcon( SyncResult::Success); + } else { + return theme->syncStateIcon( status ); + } + } + } + } else { + return Theme::instance()->folderOfflineIcon(); + } + } + return QVariant(); } +bool FolderStatusModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if(role == Qt::CheckStateRole) { + auto info = infoForIndex(index); + Qt::CheckState checked = static_cast(value.toInt()); + + if (info && info->_checked != checked) { + info->_checked = checked; + if (checked == Qt::Checked) { + // If we are checked, check that we may need to check the parent as well if + // all the sibilings are also checked + QModelIndex parent = index.parent(); + auto parentInfo = infoForIndex(parent); + if (parentInfo && parentInfo->_checked != Qt::Checked) { + bool hasUnchecked = false; + foreach(const auto &sub, parentInfo->_subs) { + if (sub._checked != Qt::Checked) { + hasUnchecked = true; + break; + } + } + if (!hasUnchecked) { + setData(parent, Qt::Checked, Qt::CheckStateRole); + } else if (parentInfo->_checked == Qt::Unchecked) { + setData(parent, Qt::PartiallyChecked, Qt::CheckStateRole); + } + } + // also check all the children + for (int i = 0; i < info->_subs.count(); ++i) { + if (info->_subs[i]._checked != Qt::Checked) { + setData(index.child(i, 0), Qt::Checked, Qt::CheckStateRole); + } + } + } + + if (checked == Qt::Unchecked) { + QModelIndex parent = index.parent(); + auto parentInfo = infoForIndex(parent); + if (parentInfo && parentInfo->_checked == Qt::Checked) { + setData(parent, Qt::PartiallyChecked, Qt::CheckStateRole); + } + + // Uncheck all the children + for (int i = 0; i < info->_subs.count(); ++i) { + if (info->_subs[i]._checked != Qt::Unchecked) { + setData(index.child(i, 0), Qt::Unchecked, Qt::CheckStateRole); + } + } + } + + if (checked == Qt::PartiallyChecked) { + QModelIndex parent = index.parent(); + auto parentInfo = infoForIndex(parent); + if (parentInfo && parentInfo->_checked != Qt::PartiallyChecked) { + setData(parent, Qt::PartiallyChecked, Qt::CheckStateRole); + } + } + + } + _dirty = true; + emit dirtyChanged(); + dataChanged(index, index, QVector() << role); + return true; + } + return QAbstractItemModel::setData(index, value, role); +} + + +int FolderStatusModel::columnCount(const QModelIndex&) const +{ + return 1; +} + +int FolderStatusModel::rowCount(const QModelIndex& parent) const +{ + if (!parent.isValid()) { + return FolderMan::instance()->map().count() + 1; + } + + auto info = infoForIndex(parent); + if (!info) + return 0; + return info->_subs.count(); +} + +FolderStatusModel::ItemType FolderStatusModel::classify(const QModelIndex& index) const +{ + if (index.internalPointer()) { + return SubFolder; + } + //FIXME: + auto folderList = FolderMan::instance()->map(); //.values(); + if (index.row() < folderList.count()) { + return RootFolder; + } + return AddButton; +} + +FolderStatusModel::SubFolderInfo* FolderStatusModel::infoForIndex(const QModelIndex& index) const +{ + if (!index.isValid()) + return 0; + if (auto parentInfo = index.internalPointer()) { + return &static_cast(parentInfo)->_subs[index.row()]; + } else { + auto folders = FolderMan::instance()->map(); // FIXME + if (index.row() >= folders.count()) { + // AddButton + return 0; + } + if (_folders.size() <= index.row()) { + _folders.resize(index.row() + 1); + } + auto info = &_folders[index.row()]; + if (info->_pathIdx.isEmpty()) { + info->_pathIdx << index.row(); + info->_name = folders.values().at(index.row())->alias(); + info->_path = "/"; + info->_folder = folders.values().at(index.row()); + } + return info; + } +} + + +QModelIndex FolderStatusModel::index(int row, int column, const QModelIndex& parent) const +{ + if (!parent.isValid()) { + return createIndex(row, column, nullptr); + } + switch(classify(parent)) { + case AddButton: return QModelIndex(); + case RootFolder: + if (_folders.count() <= parent.row()) + return QModelIndex(); // should not happen + return createIndex(row, column, const_cast(&_folders[parent.row()])); + case SubFolder: + //return QModelIndex(); + if (static_cast(parent.internalPointer())->_subs.count() <= parent.row()) + return QModelIndex(); // should not happen + if (static_cast(parent.internalPointer())->_subs.at(parent.row())._subs.count() <= row) + return QModelIndex(); // should not happen + return createIndex(row, column, &static_cast(parent.internalPointer())->_subs[parent.row()]); + } + return QModelIndex(); +} + +QModelIndex FolderStatusModel::parent(const QModelIndex& child) const +{ + if (!child.isValid()) { + return QModelIndex(); + } + switch(classify(child)) { + case RootFolder: + case AddButton: + return QModelIndex(); + case SubFolder: + break; + } + auto pathIdx = static_cast(child.internalPointer())->_subs[child.row()]._pathIdx; + int i = 1; + Q_ASSERT(pathIdx.at(0) < _folders.count()); + if (pathIdx.count() == 2) { + return createIndex(pathIdx.at(0), 0, nullptr); + } + + const SubFolderInfo *info = &_folders[pathIdx.at(0)]; + while (i < pathIdx.count() - 2) { + Q_ASSERT(pathIdx.at(i) < info->_subs.count()); + info = &info->_subs[pathIdx.at(i)]; + ++i; + } + return createIndex(pathIdx.at(i), 0, const_cast(info)); +} + +bool FolderStatusModel::hasChildren(const QModelIndex& parent) const +{ + if (!parent.isValid()) + return true; + + auto info = infoForIndex(parent); + if (!info) + return false; + + if (!info->_fetched) + return true; + + if (info->_subs.isEmpty()) + return false; + + return true; +} + + +bool FolderStatusModel::canFetchMore(const QModelIndex& parent) const +{ + auto info = infoForIndex(parent); + if (!info || info->_fetched || info->_fetching) + return false; + return true; +} + + +void FolderStatusModel::fetchMore(const QModelIndex& parent) +{ + auto info = infoForIndex(parent); + if (!info || info->_fetched || info->_fetching) + return; + + info->_fetching = true; + LsColJob *job = new LsColJob(_account, info->_folder->remotePath() + "/" + info->_path, this); + job->setProperties(QList() << "resourcetype" << "quota-used-bytes"); + connect(job, SIGNAL(directoryListingSubfolders(QStringList)), + SLOT(slotUpdateDirectories(QStringList))); + job->start(); + job->setProperty(propertyParentIndexC , QVariant::fromValue(parent)); +} + +void FolderStatusModel::slotUpdateDirectories(const QStringList &list_) +{ + auto job = qobject_cast(sender()); + Q_ASSERT(job); + QModelIndex idx = qvariant_cast(job->property(propertyParentIndexC)); + if (!idx.isValid()) { + return; + } + auto parentInfo = infoForIndex(idx); + + auto list = list_; + list.removeFirst(); // remove the parent item + + beginInsertRows(idx, 0, list.count()); + + QUrl url = parentInfo->_folder->remoteUrl(); + QString pathToRemove = url.path(); + if (!pathToRemove.endsWith('/')) + pathToRemove += '/'; + + parentInfo->_fetched = true; + parentInfo->_fetching = false; + + int i = 0; + foreach (QString path, list) { + SubFolderInfo newInfo; + newInfo._folder = parentInfo->_folder; + newInfo._pathIdx = parentInfo->_pathIdx; + newInfo._pathIdx << i++; + auto size = job ? job->_sizes.value(path) : 0; + newInfo._size = size; + path.remove(pathToRemove); + newInfo._path = path; + newInfo._name = path.split('/', QString::SkipEmptyParts).last(); + + if (path.isEmpty()) + continue; + + if (parentInfo->_checked == Qt::Unchecked) { + newInfo._checked = Qt::Unchecked; + } else { + auto *f = FolderMan::instance()->map().values().at(parentInfo->_pathIdx.first()); + foreach(const QString &str , f->selectiveSyncBlackList()) { + if (str == path || str == QLatin1String("/")) { + newInfo._checked = Qt::Unchecked; + break; + } else if (str.startsWith(path)) { + newInfo._checked = Qt::PartiallyChecked; + } + } + } + parentInfo->_subs.append(newInfo); + } + + endInsertRows(); +} + +/*void SelectiveSyncTreeView::slotLscolFinishedWithError(QNetworkReply *r) +{ + if (r->error() == QNetworkReply::ContentNotFoundError) { + _loading->setText(tr("No subfolders currently on the server.")); + } else { + _loading->setText(tr("An error occured while loading the list of sub folders.")); + } + _loading->resize(_loading->sizeHint()); // because it's not in a layout +}*/ + +QStringList FolderStatusModel::createBlackList(FolderStatusModel::SubFolderInfo *root, + const QStringList &oldBlackList) const +{ + if (!root) return QStringList(); + + switch(root->_checked) { + case Qt::Unchecked: + return QStringList(root->_path); + case Qt::Checked: + return QStringList(); + case Qt::PartiallyChecked: + break; + } + + QStringList result; + if (root->_fetched) { + for (int i = 0; i < root->_subs.count(); ++i) { + result += createBlackList(&root->_subs[i], oldBlackList); + } + } else { + // We did not load from the server so we re-use the one from the old black list + QString path = root->_path; + foreach (const QString & it, oldBlackList) { + if (it.startsWith(path)) + result += it; + } + } + return result; +} + +void FolderStatusModel::slotApplySelectiveSync() +{ + if (!_dirty) + return; + auto folderList = FolderMan::instance()->map().values(); //FIXME + + for (int i = 0; i < folderList.count(); ++i) { + if (i >= _folders.count()) break; + if (!_folders[i]._fetched) continue; + auto folder = folderList.at(i); + + auto oldBlackList = folder->selectiveSyncBlackList(); + QStringList blackList = createBlackList(&_folders[i], oldBlackList); + folder->setSelectiveSyncBlackList(blackList); + + // FIXME: Use ConfigFile + QSettings settings(folder->configFile(), QSettings::IniFormat); + settings.beginGroup(FolderMan::escapeAlias(folder->alias())); + settings.setValue("blackList", blackList); + FolderMan *folderMan = FolderMan::instance(); + auto blackListSet = blackList.toSet(); + auto oldBlackListSet = oldBlackList.toSet(); + auto changes = (oldBlackListSet - blackListSet) + (blackListSet - oldBlackListSet); + if (!changes.isEmpty()) { + if (folder->isBusy()) { + folder->slotTerminateSync(); + } + //The part that changed should not be read from the DB on next sync because there might be new folders + // (the ones that are no longer in the blacklist) + foreach(const auto &it, changes) { + folder->journalDb()->avoidReadFromDbOnNextSync(it); + } + folderMan->slotScheduleSync(folder->alias()); + } + } + + resetFolders(); +} + + +void FolderStatusModel::resetFolders() +{ + setAccount(_account); +} + + // ==================================================================================== FolderStatusDelegate::FolderStatusDelegate() @@ -62,6 +513,12 @@ FolderStatusDelegate::~FolderStatusDelegate() QSize FolderStatusDelegate::sizeHint(const QStyleOptionViewItem & option , const QModelIndex & index) const { + + if (static_cast(index.model())->classify(index) != FolderStatusModel::RootFolder) { + return QStyledItemDelegate::sizeHint(option, index); + } + + Q_UNUSED(option) QFont aliasFont = option.font; QFont font = option.font; @@ -101,8 +558,17 @@ QSize FolderStatusDelegate::sizeHint(const QStyleOptionViewItem & option , void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + if (qvariant_cast(index.data(AddButton))) { + painter->drawText(option.rect, "[+ Add Folder]"); + return; + } + + QStyledItemDelegate::paint(painter,option,index); + if (static_cast(index.model())->classify(index) != FolderStatusModel::RootFolder) { + return; + } painter->save(); QFont aliasFont = option.font; @@ -141,7 +607,7 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & QRect iconRect = option.rect; QRect aliasRect = option.rect; - iconRect.setLeft( aliasMargin ); + iconRect.setLeft( option.rect.left() + aliasMargin ); iconRect.setTop( iconRect.top() + aliasMargin ); // (iconRect.height()-iconsize.height())/2); // alias box @@ -313,11 +779,14 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem & painter->restore(); } + painter->restore(); } -bool FolderStatusDelegate::editorEvent ( QEvent * /*event*/, QAbstractItemModel * /*model*/, const QStyleOptionViewItem & /*option*/, const QModelIndex & /*index*/ ) +bool FolderStatusDelegate::editorEvent ( QEvent * event, QAbstractItemModel * model, + const QStyleOptionViewItem & option, const QModelIndex & index ) { + return QStyledItemDelegate::editorEvent(event, model, option, index); return false; } diff --git a/src/gui/folderstatusmodel.h b/src/gui/folderstatusmodel.h index b1cbf2b90..2e1709b78 100644 --- a/src/gui/folderstatusmodel.h +++ b/src/gui/folderstatusmodel.h @@ -17,16 +17,72 @@ #include #include +#include + +#ifndef Q_DECL_OVERRIDE +#define Q_DECL_OVERRIDE +#endif namespace OCC { -class FolderStatusModel : public QStandardItemModel -{ -public: - FolderStatusModel(); - virtual Qt::ItemFlags flags( const QModelIndex& ) const Q_DECL_OVERRIDE; - QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; +class Folder; +class FolderStatusModel : public QAbstractItemModel +{ + Q_OBJECT +public: + FolderStatusModel(QObject * parent = 0); + ~FolderStatusModel(); + void setAccount(const OCC::AccountPtr& account); + + Qt::ItemFlags flags( const QModelIndex& ) const Q_DECL_OVERRIDE; + QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) Q_DECL_OVERRIDE; + int columnCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; + int rowCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; + QModelIndex index(int row, int column = 0, const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; + QModelIndex parent(const QModelIndex& child) const Q_DECL_OVERRIDE; + bool canFetchMore(const QModelIndex& parent) const Q_DECL_OVERRIDE; + void fetchMore(const QModelIndex& parent) Q_DECL_OVERRIDE; + bool hasChildren(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; + + + struct SubFolderInfo { +// QWeakPointer parent; + QString _name; + QString _path; + QVector _pathIdx; + int _size = 0; + bool _fetched = false; // If we did the LSCOL for this folder already + bool _fetching = false; + QVector _subs; + Qt::CheckState _checked = Qt::Checked; + Folder *_folder; + }; + + mutable QVector _folders; + + enum ItemType { RootFolder, SubFolder, AddButton, SelectiveSyncText }; + ItemType classify(const QModelIndex &index) const; + SubFolderInfo *infoForIndex(const QModelIndex &index) const; + + bool isDirty() { return _dirty; } + +public slots: + void slotApplySelectiveSync(); + void resetFolders(); + +private slots: + void slotUpdateDirectories(const QStringList &); + +private: + QStringList createBlackList(OCC::FolderStatusModel::SubFolderInfo* root, + const QStringList& oldBlackList) const; + AccountPtr _account; + bool _dirty = false; + +signals: + void dirtyChanged(); }; class FolderStatusDelegate : public QStyledItemDelegate @@ -51,7 +107,9 @@ class FolderStatusDelegate : public QStyledItemDelegate SyncProgressItemString, AddProgressSpace, WarningCount, - SyncRunning + SyncRunning, + + AddButton }; void paint( QPainter*, const QStyleOptionViewItem&, const QModelIndex& ) const Q_DECL_OVERRIDE; QSize sizeHint( const QStyleOptionViewItem&, const QModelIndex& ) const Q_DECL_OVERRIDE; diff --git a/src/gui/generalsettings.cpp b/src/gui/generalsettings.cpp index 2c63770d8..5ee97fe3e 100644 --- a/src/gui/generalsettings.cpp +++ b/src/gui/generalsettings.cpp @@ -19,9 +19,12 @@ #include "application.h" #include "utility.h" #include "configfile.h" +#include "owncloudsetupwizard.h" + #include "updater/updater.h" #include "updater/ocupdater.h" +#include "ignorelisteditor.h" #include "config.h" @@ -68,6 +71,9 @@ GeneralSettings::GeneralSettings(QWidget *parent) : QString themeDir = QString::fromLatin1(":/client/theme/%1/") .arg(Theme::instance()->systrayIconFlavor(true)); _ui->monoIconsCheckBox->setVisible(QDir(themeDir).exists()); + + connect(_ui->ignoredFilesButton, SIGNAL(clicked()), SLOT(slotIgnoreFilesEditor())); + connect(_ui->addAccountButton, SIGNAL(clicked()), SLOT(slotOpenAccountWizard())); } GeneralSettings::~GeneralSettings() @@ -119,4 +125,24 @@ void GeneralSettings::slotToggleOptionalDesktopNotifications(bool enable) cfgFile.setOptionalDesktopNotifications(enable); } +void GeneralSettings::slotIgnoreFilesEditor() +{ + if (_ignoreEditor.isNull()) { + _ignoreEditor = new IgnoreListEditor(this); + _ignoreEditor->setAttribute( Qt::WA_DeleteOnClose, true ); + _ignoreEditor->open(); + } else { + ownCloudGui::raiseDialog(_ignoreEditor); + } +} + +void GeneralSettings::slotOpenAccountWizard() +{ + if (QSystemTrayIcon::isSystemTrayAvailable()) { + topLevelWidget()->close(); + } + OwncloudSetupWizard::runWizard(qApp, SLOT(slotownCloudWizardDone(int)), 0); +} + + } // namespace OCC diff --git a/src/gui/generalsettings.h b/src/gui/generalsettings.h index bf3961cd3..6c0c4aa7a 100644 --- a/src/gui/generalsettings.h +++ b/src/gui/generalsettings.h @@ -15,9 +15,10 @@ #define MIRALL_GENERALSETTINGS_H #include - +#include namespace OCC { +class IgnoreListEditor; namespace Ui { class GeneralSettings; @@ -36,11 +37,15 @@ private slots: void slotToggleLaunchOnStartup(bool); void slotToggleOptionalDesktopNotifications(bool); void slotUpdateInfo(); + void slotIgnoreFilesEditor(); + void slotOpenAccountWizard(); + private: void loadMiscSettings(); Ui::GeneralSettings *_ui; + QPointer _ignoreEditor; }; diff --git a/src/gui/generalsettings.ui b/src/gui/generalsettings.ui index ed2a47f7a..443e8de95 100644 --- a/src/gui/generalsettings.ui +++ b/src/gui/generalsettings.ui @@ -6,8 +6,8 @@ 0 0 - 468 - 249 + 599 + 429 @@ -41,13 +41,76 @@ + + + + + + + Advanced + + + + + + + Edit Ignored Files + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + 0 + + Show crash reporter + + + + + + Add an Account + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + diff --git a/src/gui/selectivesyncdialog.cpp b/src/gui/selectivesyncdialog.cpp index 70348472b..7dc4b2fe1 100644 --- a/src/gui/selectivesyncdialog.cpp +++ b/src/gui/selectivesyncdialog.cpp @@ -410,7 +410,5 @@ qint64 SelectiveSyncDialog::estimatedSize() } - - }