1 #include <QMenu>
2 #include <QAction>
3 #include <QToolButton>
4 #include <QScopedPointer>
5 #include <QPainter>
6 #include <QStringList>
7 #include <QDesktopServices>
8 #include <QMouseEvent>
9 #include <QUrl>
10 #include <QUrlQuery>
11 #include <QThreadPool>
12 
13 #include "account.h"
14 #include "seafile-applet.h"
15 #include "account-mgr.h"
16 #include "login-dialog.h"
17 #include "settings-mgr.h"
18 #ifdef HAVE_SHIBBOLETH_SUPPORT
19 #include "shib/shib-login-dialog.h"
20 #endif // HAVE_SHIBBOLETH_SUPPORT
21 #include "account-settings-dialog.h"
22 #include "rpc/rpc-client.h"
23 #include "main-window.h"
24 #include "init-vdrive-dialog.h"
25 #include "auto-login-service.h"
26 #include "avatar-service.h"
27 #include "utils/paint-utils.h"
28 #include "filebrowser/file-browser-manager.h"
29 #include "api/api-error.h"
30 #include "api/requests.h"
31 #include "filebrowser/auto-update-mgr.h"
32 #include "repo-service.h"
33 
34 #include "account-view.h"
35 namespace {
36 
37 } // namespace
38 
AccountView(QWidget * parent)39 AccountView::AccountView(QWidget *parent)
40     : QWidget(parent)
41 {
42     setupUi(this);
43 
44     // Init account drop down menu
45     account_menu_ = new QMenu;
46     mAccountBtn->setMenu(account_menu_);
47     mAccountBtn->setPopupMode(QToolButton::InstantPopup);
48     mAccountBtn->setFixedSize(QSize(AvatarService::kAvatarSize, AvatarService::kAvatarSize));
49 
50     onAccountChanged();
51 
52     connect(AvatarService::instance(), SIGNAL(avatarUpdated(const QString&, const QImage&)),
53             this, SLOT(updateAvatar()));
54 
55     mAccountBtn->setCursor(Qt::PointingHandCursor);
56     mAccountBtn->installEventFilter(this);
57     account_menu_->installEventFilter(this);
58 
59     connect(seafApplet->accountManager(), SIGNAL(requireAddAccount()),
60             this, SLOT(showAddAccountDialog()));
61     connect(mServerAddr, SIGNAL(linkActivated(const QString&)),
62             this, SLOT(visitServerInBrowser(const QString&)));
63 
64     // Must get the pixmap from QIcon because QIcon would load the 2x version
65     // automatically.
66     mRefreshLabel->setPixmap(QIcon(":/images/toolbar/refresh-new.png").pixmap(20));
67     mRefreshLabel->installEventFilter(this);
68 }
69 
showAddAccountDialog()70 void AccountView::showAddAccountDialog()
71 {
72     LoginDialog dialog(this);
73     // Show InitVirtualDriveDialog for the first account added
74     AccountManager *account_mgr = seafApplet->accountManager();
75     if (dialog.exec() == QDialog::Accepted
76         && account_mgr->accounts().size() == 1) {
77 
78         InitVirtualDriveDialog dialog(account_mgr->currentAccount(), seafApplet->mainWindow());
79 #if defined(Q_OS_WIN32)
80         dialog.exec();
81 #endif
82     }
83 }
84 
deleteAccount()85 void AccountView::deleteAccount()
86 {
87     QAction *action = qobject_cast<QAction*>(sender());
88     if (!action)
89         return;
90     Account account = qvariant_cast<Account>(action->data());
91 
92     // QString question = tr("Are you sure to remove account from \"%1\"?<br>"
93     //                       "<b>Warning: All libraries of this account would be unsynced!</b>").arg(account.serverUrl.toString());
94 
95     QString question = tr("Are you sure you want to remove account %1?<br><br>"
96                           "The account will be removed locally. All syncing "
97                           "configuration will be removed too. The account at "
98                           "the server will not be affected.")
99                            .arg(account.username);
100 
101     if (seafApplet->yesOrNoBox(question, this, false)) {
102         FileBrowserManager::getInstance()->closeAllDialogByAccount(account);
103         QString error;
104         QUrl server_url = account.serverUrl;
105         server_url.setPath("/");
106         if (seafApplet->rpcClient()->unsyncReposByAccount(server_url,
107                                                           account.username,
108                                                           &error) < 0) {
109 
110             seafApplet->warningBox(
111                 tr("Failed to unsync libraries of this account: %1").arg(error),
112                 this);
113         }
114 
115         seafApplet->accountManager()->removeAccount(account);
116     }
117 }
118 
editAccountSettings()119 void AccountView::editAccountSettings()
120 {
121     QAction *action = qobject_cast<QAction*>(sender());
122     if (!action)
123         return;
124     Account account = qvariant_cast<Account>(action->data());
125 
126     AccountSettingsDialog dialog(account, this);
127 
128     dialog.exec();
129 }
130 
updateAccountInfoDisplay()131 void AccountView::updateAccountInfoDisplay()
132 {
133     if (seafApplet->accountManager()->hasAccount()) {
134         const Account account = seafApplet->accountManager()->currentAccount();
135         if (!account.accountInfo.name.isEmpty()) {
136             mEmail->setText(account.accountInfo.name);
137         } else {
138             mEmail->setText(account.username);
139         }
140         // mServerAddr->setOpenExternalLinks(true);
141         mServerAddr->setToolTip(tr("click to open the website"));
142 
143         QString host = account.serverUrl.host();
144         QString href = account.serverUrl.toString();
145         QString text = QString("<a style="
146                                "\"color:#A4A4A4; text-decoration: none;\" "
147                                "href=\"%1\">%2</a>").arg(href).arg(host);
148 
149         mServerAddr->setText(account.isPro() ? QString("%1 <small>%2<small>").arg(text).arg(tr("pro version")) : text);
150     } else {
151         mEmail->setText(tr("No account"));
152         mServerAddr->setText(QString());
153     }
154 
155     updateAvatar();
156 }
157 
158 /**
159  * Update the account menu when accounts changed
160  */
onAccountChanged()161 void AccountView::onAccountChanged()
162 {
163     const std::vector<Account>& accounts = seafApplet->accountManager()->accounts();
164 
165     // Remove all menu items
166     account_menu_->clear();
167 
168     if (!accounts.empty()) {
169         for (size_t i = 0, n = accounts.size(); i < n; i++) {
170             const Account &account = accounts[i];
171             QString text_name = account.accountInfo.name.isEmpty() ?
172                         account.username : account.accountInfo.name;
173             QString text = text_name + " (" + account.serverUrl.host() + ")";
174             if (!account.isValid()) {
175                 text += ", " + tr("not logged in");
176             }
177             QMenu *submenu = new QMenu(text, account_menu_);
178             if (i == 0) {
179                 submenu->setIcon(QIcon(":/images/account-checked.png"));
180             } else {
181                 submenu->setIcon(QIcon(":/images/account-else.png"));
182             }
183 
184             QAction *submenu_action = submenu->menuAction();
185             submenu_action->setData(QVariant::fromValue(account));
186             connect(submenu_action, SIGNAL(triggered()), this, SLOT(onAccountItemClicked()));
187 
188             QAction *action = new QAction(tr("Choose"), submenu);
189             action->setIcon(QIcon(":/images/account-checked.png"));
190             action->setIconVisibleInMenu(true);
191             action->setData(QVariant::fromValue(account));
192             connect(action, SIGNAL(triggered()), this, SLOT(onAccountItemClicked()));
193 
194             submenu->addAction(action);
195             submenu->setDefaultAction(action);
196 
197             QAction *account_settings_action = new QAction(tr("Account settings"), this);
198             account_settings_action->setIcon(QIcon(":/images/account-settings.png"));
199             account_settings_action->setIconVisibleInMenu(true);
200             account_settings_action->setData(QVariant::fromValue(account));
201             connect(account_settings_action, SIGNAL(triggered()), this, SLOT(editAccountSettings()));
202             submenu->addAction(account_settings_action);
203 
204             QAction *toggle_action = new QAction(this);
205             toggle_action->setIcon(QIcon(":/images/logout.png"));
206             toggle_action->setIconVisibleInMenu(true);
207             toggle_action->setData(QVariant::fromValue(account));
208             connect(toggle_action, SIGNAL(triggered()), this, SLOT(toggleAccount()));
209             if (account.isValid())
210                 toggle_action->setText(tr("Logout"));
211             else
212                 toggle_action->setText(tr("Login"));
213             submenu->addAction(toggle_action);
214 
215             QAction *delete_account_action = new QAction(tr("Delete"), this);
216             delete_account_action->setIcon(QIcon(":/images/delete-account.png"));
217             delete_account_action->setIconVisibleInMenu(true);
218             delete_account_action->setData(QVariant::fromValue(account));
219             connect(delete_account_action, SIGNAL(triggered()), this, SLOT(deleteAccount()));
220             submenu->addAction(delete_account_action);
221 
222             account_menu_->addMenu(submenu);
223         }
224 
225         account_menu_->addSeparator();
226     }
227 
228     add_account_action_ = new QAction(tr("Add an account"), this);
229     add_account_action_->setIcon(QIcon(":/images/add-account.png"));
230     add_account_action_->setIconVisibleInMenu(true);
231     connect(add_account_action_, SIGNAL(triggered()), this, SLOT(showAddAccountDialog()));
232     account_menu_->addAction(add_account_action_);
233 
234     updateAccountInfoDisplay();
235 }
236 
makeAccountAction(const Account & account)237 QAction* AccountView::makeAccountAction(const Account& account)
238 {
239     QString text = account.username + "(" + account.serverUrl.host() + ")";
240     if (!account.isValid()) {
241         text += ", " + tr("not logged in");
242     }
243     QAction *action = new QAction(text, account_menu_);
244     action->setData(QVariant::fromValue(account));
245     // action->setCheckable(true);
246     // QMenu won't display tooltip for menu item
247     // action->setToolTip(account.serverUrl.host());
248 
249     connect(action, SIGNAL(triggered()), this, SLOT(onAccountItemClicked()));
250 
251     return action;
252 }
253 
254 // Switch to the clicked account in the account menu
onAccountItemClicked()255 void AccountView::onAccountItemClicked()
256 {
257     QAction *action = (QAction *)(sender());
258     Account account = qvariant_cast<Account>(action->data());
259 
260     if (!account.isValid()) {
261         seafApplet->accountManager()->reloginAccount(account);
262     } else {
263         seafApplet->accountManager()->setCurrentAccount(account);
264     }
265 }
266 
updateAvatar()267 void AccountView::updateAvatar()
268 {
269     mAccountBtn->setIconSize(QSize(AvatarService::kAvatarSize, AvatarService::kAvatarSize));
270     const Account account = seafApplet->accountManager()->currentAccount();
271     if (!account.isValid())  {
272         mAccountBtn->setIcon(QIcon(":/images/account.png"));
273         return;
274     }
275 
276     AvatarService *service = AvatarService::instance();
277     QIcon avatar = QPixmap::fromImage(service->getAvatar(account.username));
278     mAccountBtn->setIcon(QIcon(avatar));
279 }
280 
eventFilter(QObject * obj,QEvent * event)281 bool AccountView::eventFilter(QObject *obj, QEvent *event)
282 {
283     if (obj == account_menu_ && event->type() == QEvent::MouseButtonRelease) {
284         QMouseEvent *ev = (QMouseEvent*)event;
285         QAction *action = account_menu_->actionAt(ev->pos());
286         if (action) {
287             action->trigger();
288         }
289     }
290     if (obj == mAccountBtn && event->type() == QEvent::Paint) {
291         QRect rect(0, 0, AvatarService::kAvatarSize, AvatarService::kAvatarSize);
292         QPainter painter(mAccountBtn);
293         painter.setRenderHint(QPainter::Antialiasing);
294         painter.setRenderHint(QPainter::HighQualityAntialiasing);
295 
296         // get the device pixel radio from current painter device
297         double scale_factor = globalDevicePixelRatio();
298 
299         QPixmap image(mAccountBtn->icon().pixmap(rect.size()).scaled(scale_factor * rect.size()));
300         QRect actualRect(QPoint(0, 0),
301                          QSize(AvatarService::kAvatarSize * scale_factor,
302                                AvatarService::kAvatarSize * scale_factor));
303 
304         QImage masked_image(actualRect.size(),
305                             QImage::Format_ARGB32_Premultiplied);
306         masked_image.fill(Qt::transparent);
307         QPainter mask_painter;
308         mask_painter.begin(&masked_image);
309         mask_painter.setRenderHint(QPainter::Antialiasing);
310         mask_painter.setRenderHint(QPainter::HighQualityAntialiasing);
311         mask_painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
312         mask_painter.setPen(Qt::NoPen);
313         mask_painter.setBrush(Qt::white);
314         mask_painter.drawEllipse(actualRect);
315         mask_painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
316         mask_painter.drawPixmap(actualRect, image);
317         mask_painter.setCompositionMode(QPainter::CompositionMode_DestinationOver);
318         mask_painter.fillRect(actualRect, Qt::transparent);
319         mask_painter.end();
320         masked_image.setDevicePixelRatio(scale_factor);
321 
322         painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
323         painter.drawImage(QPoint(0,0), masked_image);
324         return true;
325     }
326     if (obj == mRefreshLabel) {
327         if (event->type() == QEvent::MouseButtonPress) {
328             emit refresh();
329             return true;
330         } else if (event->type() == QEvent::Enter) {
331             mRefreshLabel->setPixmap(QIcon(":/images/toolbar/refresh-orange.png").pixmap(20));
332             return true;
333         } else if (event->type() == QEvent::Leave) {
334             mRefreshLabel->setPixmap(QIcon(":/images/toolbar/refresh-new.png").pixmap(20));
335             return true;
336         }
337     }
338     return QObject::eventFilter(obj, event);
339 }
340 
341 /**
342  * Only remove the api token of the account. The accout would still be shown
343  * in the account list.
344  */
toggleAccount()345 void AccountView::toggleAccount()
346 {
347     QAction *action = qobject_cast<QAction*>(sender());
348     if (!action)
349         return;
350     Account account = qvariant_cast<Account>(action->data());
351     if (!account.isValid()) {
352         seafApplet->accountManager()->reloginAccount(account);
353         return;
354     }
355 
356     qWarning("Logging out current account %s", account.username.toUtf8().data());
357     AutoUpdateManager::instance()->cleanCachedFile();
358 
359     // logout Account
360     FileBrowserManager::getInstance()->closeAllDialogByAccount(account);
361     seafApplet->accountManager()->logoutDevice(account);
362 }
363 
visitServerInBrowser(const QString & link)364 void AccountView::visitServerInBrowser(const QString& link)
365 {
366     AutoLoginService::instance()->startAutoLogin("/");
367 }
368