| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627 |
- /*
- * Copyright (C) by Klaas Freitag <freitag@owncloud.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; version 2 of the License.
- *
- * 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 "application.h"
- #include "owncloudgui.h"
- #include "theme.h"
- #include "folderman.h"
- #include "mirallconfigfile.h"
- #include "utility.h"
- #include "progressdispatcher.h"
- #include "owncloudsetupwizard.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"
- #include "openfilemanager.h"
- #include "creds/abstractcredentials.h"
- #include <QDesktopServices>
- #include <QMessageBox>
- #include <QSignalMapper>
- #if defined(Q_OS_X11)
- #include <QX11Info>
- #endif
- namespace Mirall {
- ownCloudGui::ownCloudGui(Application *parent) :
- QObject(parent),
- _tray(0),
- #if defined(Q_OS_MAC)
- _settingsDialog(new SettingsDialogMac(this)),
- #else
- _settingsDialog(new SettingsDialog(this)),
- #endif
- _logBrowser(0),
- _contextMenu(0),
- _recentActionsMenu(0),
- _folderOpenActionMapper(new QSignalMapper(this)),
- _recentItemsMapper(new QSignalMapper(this)),
- _app(parent)
- {
- _tray = new Systray();
- _tray->setParent(this);
- // for the beginning, set the offline icon until the account was verified
- _tray->setIcon( Theme::instance()->folderOfflineIcon(true));
- connect(_tray.data(), SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
- SLOT(slotTrayClicked(QSystemTrayIcon::ActivationReason)));
- setupActions();
- setupContextMenu();
- _tray->show();
- /* use a signal mapper to map the open requests to the alias names */
- connect(_folderOpenActionMapper, SIGNAL(mapped(QString)),
- this, SLOT(slotFolderOpenAction(QString)));
- connect(_recentItemsMapper, SIGNAL(mapped(QString)),
- this, SLOT(slotOpenPath(QString)));
- ProgressDispatcher *pd = ProgressDispatcher::instance();
- connect( pd, SIGNAL(progressInfo(QString,Progress::Info)), this,
- SLOT(slotUpdateProgress(QString,Progress::Info)) );
- FolderMan *folderMan = FolderMan::instance();
- connect( folderMan, SIGNAL(folderSyncStateChange(QString)),
- this,SLOT(slotSyncStateChange(QString)));
- connect( Logger::instance(), SIGNAL(guiLog(QString,QString)),
- SLOT(slotShowTrayMessage(QString,QString)));
- connect( Logger::instance(), SIGNAL(optionalGuiLog(QString,QString)),
- SLOT(slotShowOptionalTrayMessage(QString,QString)));
- connect( Logger::instance(), SIGNAL(guiMessage(QString,QString)),
- SLOT(slotShowGuiMessage(QString,QString)));
- setupOverlayIcons();
- }
- // Use this to do platform specific code to make overlay icons appear
- // in the gui
- // MacOSX: perform a AppleScript code peace to load the Finder Plugin.
- void ownCloudGui::setupOverlayIcons()
- {
- if( Utility::isMac() && QFile::exists("/Library/ScriptingAdditions/OwnCloudFinder.osax") ) {
- QString aScript = QString::fromUtf8("tell application \"Finder\"\n"
- " try\n"
- " «event OWNCload»\n"
- " end try\n"
- "end tell\n");
- QString osascript = "/usr/bin/osascript";
- QStringList processArguments;
- // processArguments << "-l" << "AppleScript";
- QProcess p;
- p.start(osascript, processArguments);
- p.write(aScript.toUtf8());
- p.closeWriteChannel();
- p.waitForReadyRead(-1);
- QByteArray result = p.readAll();
- QString resultAsString(result); // if appropriate
- qDebug() << "Laod Finder Overlay-Plugin: " << resultAsString << ": " << p.exitCode()
- << (p.exitCode() != 0 ? p.errorString() : QString::null);
- }
- }
- // This should rather be in application.... or rather in MirallConfigFile?
- void ownCloudGui::slotOpenSettingsDialog( bool openSettings )
- {
- // if account is set up, start the configuration wizard.
- if( AccountManager::instance()->account() ) {
- if( openSettings ) {
- if (_settingsDialog.isNull() || !_settingsDialog->isVisible()) {
- slotShowSettings();
- } else {
- _settingsDialog->close();
- }
- }
- } else {
- qDebug() << "No configured folders yet, starting setup wizard";
- OwncloudSetupWizard::runWizard(qApp, SLOT(slotownCloudWizardDone(int)));
- }
- }
- void ownCloudGui::slotTrayClicked( QSystemTrayIcon::ActivationReason reason )
- {
- // A click on the tray icon should only open the status window on Win and
- // Linux, not on Mac. They want a menu entry.
- #if !defined Q_OS_MAC
- if( reason == QSystemTrayIcon::Trigger ) {
- slotOpenSettingsDialog(true); // start settings if config is existing.
- }
- #else
- // On Mac, if the settings dialog is already visible but hidden
- // by other applications, this will bring it to the front.
- if( reason == QSystemTrayIcon::Trigger ) {
- if (!_settingsDialog.isNull() && _settingsDialog->isVisible()) {
- slotShowSettings();
- }
- }
- #endif
- }
- void ownCloudGui::slotSyncStateChange( const QString& alias )
- {
- FolderMan *folderMan = FolderMan::instance();
- const SyncResult& result = folderMan->syncResult( alias );
- slotComputeOverallSyncStatus();
- if( alias.isEmpty() ) {
- return; // Valid, just a general GUI redraw was needed.
- }
- qDebug() << "Sync state changed for folder " << alias << ": " << result.statusString();
- if (result.status() == SyncResult::Success || result.status() == SyncResult::Error) {
- Logger::instance()->enterNextLogFile();
- }
- }
- void ownCloudGui::slotFoldersChanged()
- {
- slotComputeOverallSyncStatus();
- setupContextMenu();
- }
- void ownCloudGui::slotOpenPath(const QString &path)
- {
- showInFileManager(path);
- }
- void ownCloudGui::slotAccountStateChanged()
- {
- setupContextMenu();
- slotComputeOverallSyncStatus();
- }
- void ownCloudGui::startupConnected( bool connected, const QStringList& fails )
- {
- FolderMan *folderMan = FolderMan::instance();
- if( connected ) {
- qDebug() << "######## connected to ownCloud Server!";
- folderMan->setSyncEnabled(true);
- // _tray->setIcon( Theme::instance()->syncStateIcon( SyncResult::NotYetStarted, true ) );
- // _tray->show();
- }
- _startupFails = fails; // store that for the settings dialog once it appears.
- if( !_settingsDialog.isNull() ) {
- _settingsDialog->setGeneralErrors( _startupFails );
- }
- slotComputeOverallSyncStatus();
- }
- void ownCloudGui::slotComputeOverallSyncStatus()
- {
- if (Account *a = AccountManager::instance()->account()) {
- if (a->state() == Account::SignedOut) {
- _tray->setIcon(Theme::instance()->folderOfflineIcon(true));
- _tray->setToolTip(tr("Please sign in"));
- return;
- }
- if (a->state() == Account::Disconnected) {
- _tray->setIcon(Theme::instance()->folderOfflineIcon(true));
- _tray->setToolTip(tr("Disconnected from server"));
- return;
- }
- }
- // display the info of the least successful sync (eg. not just display the result of the latest sync
- QString trayMessage;
- FolderMan *folderMan = FolderMan::instance();
- Folder::Map map = folderMan->map();
- SyncResult overallResult = FolderMan::accountStatus(map.values());
- // if there have been startup problems, show an error message.
- if( !_settingsDialog.isNull() )
- _settingsDialog->setGeneralErrors( _startupFails );
- if( !_startupFails.isEmpty() ) {
- trayMessage = _startupFails.join(QLatin1String("\n"));
- QIcon statusIcon;
- if (_app->_startupNetworkError) {
- statusIcon = Theme::instance()->syncStateIcon( SyncResult::NotYetStarted, true );
- } else {
- statusIcon = Theme::instance()->syncStateIcon( SyncResult::Error, true );
- }
- _tray->setIcon( statusIcon );
- _tray->setToolTip(trayMessage);
- } else {
- // create the tray blob message, check if we have an defined state
- if( overallResult.status() != SyncResult::Undefined ) {
- QStringList allStatusStrings;
- foreach(Folder* folder, map.values()) {
- qDebug() << "Folder in overallStatus Message: " << folder << " with name " << folder->alias();
- QString folderMessage = folderMan->statusToString(folder->syncResult().status(), folder->syncPaused());
- allStatusStrings += tr("Folder %1: %2").arg(folder->alias(), folderMessage);
- }
- if( ! allStatusStrings.isEmpty() )
- trayMessage = allStatusStrings.join(QLatin1String("\n"));
- else
- trayMessage = tr("No sync folders configured.");
- QIcon statusIcon = Theme::instance()->syncStateIcon( overallResult.status(), true);
- _tray->setIcon( statusIcon );
- _tray->setToolTip(trayMessage);
- } else {
- // undefined because there are no folders.
- QIcon icon = Theme::instance()->syncStateIcon(SyncResult::Problem);
- _tray->setIcon( icon );
- _tray->setToolTip(tr("There are no sync folders configured."));
- #if !defined Q_OS_MAC
- if( _settingsDialog ) {
- _settingsDialog->slotUpdateAccountIcon(icon);
- }
- #endif
- }
- }
- }
- void ownCloudGui::setupContextMenu()
- {
- FolderMan *folderMan = FolderMan::instance();
- Account *a = AccountManager::instance()->account();
- bool isConfigured = (a != 0);
- _actionOpenoC->setEnabled(isConfigured);
- bool isConnected = false;
- if (isConfigured) {
- isConnected = (a->state() == Account::Connected);
- }
- if ( _contextMenu ) {
- _contextMenu->clear();
- _recentActionsMenu->clear();
- _recentActionsMenu->addAction(tr("None."));
- _recentActionsMenu->addAction(_actionRecent);
- } else {
- _contextMenu = new QMenu(_contextMenu);
- _recentActionsMenu = new QMenu(tr("Recent Changes"));
- // this must be called only once after creating the context menu, or
- // it will trigger a bug in Ubuntu's SNI bridge patch (11.10, 12.04).
- _tray->setContextMenu(_contextMenu);
- }
- _contextMenu->setTitle(Theme::instance()->appNameGUI() );
- _contextMenu->addAction(_actionOpenoC);
- int folderCnt = folderMan->map().size();
- // add open actions for all sync folders to the tray menu
- if( Theme::instance()->singleSyncFolder() ) {
- // there should be exactly one folder. No sync-folder add action will be shown.
- QStringList li = folderMan->map().keys();
- if( li.size() == 1 ) {
- Folder *folder = folderMan->map().value(li.first());
- if( folder ) {
- // if there is singleFolder mode, a generic open action is displayed.
- QAction *action = new QAction( tr("Open %1 folder").arg(Theme::instance()->appNameGUI()), this);
- connect( action, SIGNAL(triggered()),_folderOpenActionMapper,SLOT(map()));
- _folderOpenActionMapper->setMapping( action, folder->alias() );
- _contextMenu->addAction(action);
- }
- }
- } else {
- // show a grouping with more than one folder.
- if ( folderCnt > 1) {
- _contextMenu->addAction(tr("Managed Folders:"))->setDisabled(true);
- }
- foreach (Folder *folder, folderMan->map() ) {
- QAction *action = new QAction( tr("Open folder '%1'").arg(folder->alias()), this );
- connect( action, SIGNAL(triggered()),_folderOpenActionMapper,SLOT(map()));
- _folderOpenActionMapper->setMapping( action, folder->alias() );
- _contextMenu->addAction(action);
- }
- }
- _contextMenu->addSeparator();
- if (isConfigured && isConnected) {
- _contextMenu->addAction(_actionQuota);
- _contextMenu->addSeparator();
- _contextMenu->addAction(_actionStatus);
- _contextMenu->addMenu(_recentActionsMenu);
- _contextMenu->addSeparator();
- }
- _contextMenu->addAction(_actionSettings);
- if (!Theme::instance()->helpUrl().isEmpty()) {
- _contextMenu->addAction(_actionHelp);
- }
- _contextMenu->addSeparator();
- if (isConfigured && isConnected) {
- _contextMenu->addAction(_actionLogout);
- } else {
- _contextMenu->addAction(_actionLogin);
- }
- _contextMenu->addAction(_actionQuit);
- }
- void ownCloudGui::slotShowTrayMessage(const QString &title, const QString &msg)
- {
- if( _tray )
- _tray->showMessage(title, msg);
- else
- qDebug() << "Tray not ready: " << msg;
- }
- void ownCloudGui::slotShowOptionalTrayMessage(const QString &title, const QString &msg)
- {
- MirallConfigFile cfg;
- if (cfg.optionalDesktopNotifications()) {
- slotShowTrayMessage(title, msg);
- }
- }
- /*
- * open the folder with the given Alais
- */
- void ownCloudGui::slotFolderOpenAction( const QString& alias )
- {
- Folder *f = FolderMan::instance()->folder(alias);
- if( f ) {
- qDebug() << "opening local url " << f->path();
- QUrl url = QUrl::fromLocalFile(f->path());
- #ifdef Q_OS_WIN
- // work around a bug in QDesktopServices on Win32, see i-net
- QString filePath = f->path();
- if (filePath.startsWith(QLatin1String("\\\\")) || filePath.startsWith(QLatin1String("//")))
- url.setUrl(QDir::toNativeSeparators(filePath));
- else
- url = QUrl::fromLocalFile(filePath);
- #endif
- QDesktopServices::openUrl(url);
- }
- }
- void ownCloudGui::setupActions()
- {
- _actionOpenoC = new QAction(tr("Open %1 in browser").arg(Theme::instance()->appNameGUI()), this);
- QObject::connect(_actionOpenoC, SIGNAL(triggered(bool)), SLOT(slotOpenOwnCloud()));
- _actionQuota = new QAction(tr("Calculating quota..."), this);
- _actionQuota->setEnabled( false );
- _actionStatus = new QAction(tr("Unknown status"), this);
- _actionStatus->setEnabled( false );
- _actionSettings = new QAction(tr("Settings..."), this);
- _actionRecent = new QAction(tr("Details..."), this);
- _actionRecent->setEnabled( true );
- QObject::connect(_actionRecent, SIGNAL(triggered(bool)), SLOT(slotShowSyncProtocol()));
- QObject::connect(_actionSettings, SIGNAL(triggered(bool)), SLOT(slotShowSettings()));
- _actionHelp = new QAction(tr("Help"), this);
- QObject::connect(_actionHelp, SIGNAL(triggered(bool)), SLOT(slotHelp()));
- _actionQuit = new QAction(tr("Quit %1").arg(Theme::instance()->appNameGUI()), this);
- QObject::connect(_actionQuit, SIGNAL(triggered(bool)), _app, SLOT(quit()));
- _actionLogin = new QAction(tr("Sign in..."), this);
- connect(_actionLogin, SIGNAL(triggered()), _app, SLOT(slotLogin()));
- _actionLogout = new QAction(tr("Sign out"), this);
- connect(_actionLogout, SIGNAL(triggered()), _app, SLOT(slotLogout()));
- }
- void ownCloudGui::slotRefreshQuotaDisplay( qint64 total, qint64 used )
- {
- if (total == 0) {
- _actionQuota->setText(tr("Quota n/a"));
- return;
- }
- double percent = used/(double)total*100;
- QString percentFormatted = Utility::compactFormatDouble(percent, 1);
- QString totalFormatted = Utility::octetsToString(total);
- _actionQuota->setText(tr("%1% of %2 in use").arg(percentFormatted).arg(totalFormatted));
- }
- void ownCloudGui::slotRebuildRecentMenus()
- {
- _recentActionsMenu->clear();
- if (!_recentItemsActions.isEmpty()) {
- foreach(QAction *a, _recentItemsActions) {
- _recentActionsMenu->addAction(a);
- }
- _recentActionsMenu->addSeparator();
- } else {
- _recentActionsMenu->addAction(tr("No items synced recently"))->setEnabled(false);
- }
- // add a more... entry.
- _recentActionsMenu->addAction(_actionRecent);
- }
- void ownCloudGui::slotUpdateProgress(const QString &folder, const Progress::Info& progress)
- {
- Q_UNUSED(folder);
- if (!progress._currentDiscoveredFolder.isEmpty()) {
- _actionStatus->setText( tr("Discovering %1")
- .arg( progress._currentDiscoveredFolder ));
- } else if (progress._totalSize == 0 ) {
- quint64 currentFile = progress._completedFileCount + progress._currentItems.count();
- _actionStatus->setText( tr("Syncing %1 of %2 (%3 left)")
- .arg( currentFile ).arg( progress._totalFileCount )
- .arg( Utility::timeToDescriptiveString(progress.totalEstimate().getEtaEstimate(), 2, " ",true) ) );
- } else {
- QString totalSizeStr = Utility::octetsToString( progress._totalSize );
- _actionStatus->setText( tr("Syncing %1 (%2 left)")
- .arg( totalSizeStr )
- .arg( Utility::timeToDescriptiveString(progress.totalEstimate().getEtaEstimate(), 2, " ",true) ) );
- }
- _actionRecent->setIcon( QIcon() ); // Fixme: Set a "in-progress"-item eventually.
- if (!progress._lastCompletedItem.isEmpty()) {
- if (Progress::isWarningKind(progress._lastCompletedItem._status)) {
- // display a warn icon if warnings happend.
- QIcon warnIcon(":/mirall/resources/warning-16");
- _actionRecent->setIcon(warnIcon);
- }
- QString kindStr = Progress::asResultString(progress._lastCompletedItem);
- QString timeStr = QTime::currentTime().toString("hh:mm");
- QString actionText = tr("%1 (%2, %3)").arg(progress._lastCompletedItem._file, kindStr, timeStr);
- QAction *action = new QAction(actionText, this);
- Folder *f = FolderMan::instance()->folder(folder);
- if (f) {
- QString fullPath = f->path() + '/' + progress._lastCompletedItem._file;
- if (QFile(fullPath).exists()) {
- _recentItemsMapper->setMapping(action, fullPath);
- connect(action, SIGNAL(triggered()), _recentItemsMapper, SLOT(map()));
- } else {
- action->setEnabled(false);
- }
- }
- if (_recentItemsActions.length() > 5) {
- _recentItemsActions.takeFirst()->deleteLater();
- }
- _recentItemsActions.append(action);
- slotRebuildRecentMenus();
- }
- if (progress._completedFileCount == progress._totalFileCount
- && progress._currentDiscoveredFolder.isEmpty()) {
- QTimer::singleShot(2000, this, SLOT(slotDisplayIdle()));
- }
- }
- void ownCloudGui::slotDisplayIdle()
- {
- _actionStatus->setText(tr("Up to date"));
- }
- void ownCloudGui::slotShowGuiMessage(const QString &title, const QString &message)
- {
- QMessageBox *msgBox = new QMessageBox;
- msgBox->setAttribute(Qt::WA_DeleteOnClose);
- msgBox->setText(message);
- msgBox->setWindowTitle(title);
- msgBox->setIcon(QMessageBox::Information);
- msgBox->open();
- }
- void ownCloudGui::slotShowSettings()
- {
- qDebug() << Q_FUNC_INFO;
- if (_settingsDialog.isNull()) {
- _settingsDialog =
- #if defined(Q_OS_MAC)
- new SettingsDialogMac(this);
- #else
- new SettingsDialog(this);
- #endif
- _settingsDialog->setAttribute( Qt::WA_DeleteOnClose, true );
- _settingsDialog->show();
- }
- _settingsDialog->setGeneralErrors( _startupFails );
- raiseDialog(_settingsDialog.data());
- }
- void ownCloudGui::slotShowSyncProtocol()
- {
- slotShowSettings();
- _settingsDialog->showActivityPage();
- }
- void ownCloudGui::slotShutdown()
- {
- // those do delete on close
- if (!_settingsDialog.isNull()) _settingsDialog->close();
- if (!_logBrowser.isNull()) _logBrowser->deleteLater();
- }
- void ownCloudGui::slotToggleLogBrowser()
- {
- if (_logBrowser.isNull()) {
- // init the log browser.
- _logBrowser = new LogBrowser;
- // ## TODO: allow new log name maybe?
- }
- if (_logBrowser->isVisible() ) {
- _logBrowser->hide();
- } else {
- raiseDialog(_logBrowser);
- }
- }
- void ownCloudGui::slotOpenOwnCloud()
- {
- if (Account *account = AccountManager::instance()->account()) {
- QDesktopServices::openUrl(account->url());
- }
- }
- void ownCloudGui::slotHelp()
- {
- QDesktopServices::openUrl(QUrl(Theme::instance()->helpUrl()));
- }
- void ownCloudGui::raiseDialog( QWidget *raiseWidget )
- {
- if( raiseWidget && raiseWidget->parentWidget() == 0) {
- // Qt has a bug which causes parent-less dialogs to pop-under.
- raiseWidget->showNormal();
- 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();
- XEvent e;
- e.xclient.type = ClientMessage;
- e.xclient.message_type = NETWM::NET_ACTIVE_WINDOW;
- e.xclient.display = QX11Info::display();
- e.xclient.window = wid;
- e.xclient.format = 32;
- e.xclient.data.l[0] = 2;
- e.xclient.data.l[1] = QX11Info::appTime();
- e.xclient.data.l[2] = 0;
- e.xclient.data.l[3] = 0l;
- e.xclient.data.l[4] = 0l;
- #endif
- }
- }
- } // end namespace
|