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