2 /***************************************************************************
3 kmsystemtray.cpp - description
5 begin : Fri Aug 31 22:38:44 EDT 2001
6 copyright : (C) 2001 by Ryan Breen
7 email : ryan@porivo.com
9 Copyright (c) 2010-2015 Montel Laurent <montel@kde.org>
10 ***************************************************************************/
12 /***************************************************************************
14 * This program is free software; you can redistribute it and/or modify *
15 * it under the terms of the GNU General Public License as published by *
16 * the Free Software Foundation; either version 2 of the License, or *
17 * (at your option) any later version. *
19 ***************************************************************************/
21 #include "kmsystemtray.h"
22 #include "kmmainwidget.h"
23 #include "settings/globalsettings.h"
24 #include "util/mailutil.h"
25 #include "mailcommon/kernel/mailkernel.h"
26 #include "mailcommon/folder/foldertreeview.h"
27 #include <AkonadiCore/NewMailNotifierAttribute>
29 #include <kiconloader.h>
30 #include <kcolorscheme.h>
31 #include <kwindowsystem.h>
32 #include "kmail_debug.h"
34 #include <KLocalizedString>
36 #include <KActionMenu>
37 #include "widgets/kactionmenutransport.h"
38 #include <KActionCollection>
41 #include <AkonadiCore/ChangeRecorder>
42 #include <AkonadiCore/EntityTreeModel>
43 #include <QFontDatabase>
44 #include <AkonadiCore/EntityMimeTypeFilterModel>
46 using namespace MailCommon
;
49 * Construct a KSystemTray icon to be displayed when new mail
50 * has arrived in a non-system folder. The KMSystemTray listens
51 * for updateNewMessageNotification events from each non-system
52 * KMFolder and maintains a store of all folders with unread
55 * The KMSystemTray also provides a popup menu listing each folder
56 * with its count of unread messages, allowing the user to jump
57 * to the first unread message in each folder.
61 KMSystemTray::KMSystemTray(QObject
*parent
)
62 : KStatusNotifierItem(parent
),
63 mIcon(QIcon::fromTheme(QStringLiteral("mail-unread-new"))),
65 mMode(GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread
),
67 mShowUnreadMailCount(true),
68 mIconNotificationsEnabled(true),
69 mNewMessagesPopup(Q_NULLPTR
),
70 mSendQueued(Q_NULLPTR
)
72 qCDebug(KMAIL_LOG
) << "Initting systray";
73 setToolTipTitle(i18n("KMail"));
74 setToolTipIconByName(QStringLiteral("kmail"));
75 setIconByName(QStringLiteral("kmail"));
77 KMMainWidget
*mainWidget
= kmkernel
->getKMMainWidget();
79 QWidget
*mainWin
= mainWidget
->window();
81 mDesktopOfMainWin
= KWindowSystem::windowInfo(mainWin
->winId(),
82 NET::WMDesktop
).desktop();
86 connect(this, &KMSystemTray::activateRequested
, this, &KMSystemTray::slotActivated
);
87 connect(contextMenu(), &QMenu::aboutToShow
,
88 this, &KMSystemTray::slotContextMenuAboutToShow
);
90 connect(kmkernel
->folderCollectionMonitor(), SIGNAL(collectionStatisticsChanged(Akonadi::Collection::Id
,Akonadi::CollectionStatistics
)), SLOT(slotCollectionStatisticsChanged(Akonadi::Collection::Id
,Akonadi::CollectionStatistics
)));
92 connect(kmkernel
->folderCollectionMonitor(), SIGNAL(collectionAdded(Akonadi::Collection
,Akonadi::Collection
)), this, SLOT(initListOfCollection()));
93 connect(kmkernel
->folderCollectionMonitor(), SIGNAL(collectionRemoved(Akonadi::Collection
)), this, SLOT(initListOfCollection()));
94 connect(kmkernel
->folderCollectionMonitor(), SIGNAL(collectionSubscribed(Akonadi::Collection
,Akonadi::Collection
)), SLOT(initListOfCollection()));
95 connect(kmkernel
->folderCollectionMonitor(), SIGNAL(collectionUnsubscribed(Akonadi::Collection
)), SLOT(initListOfCollection()));
97 initListOfCollection();
101 bool KMSystemTray::buildPopupMenu()
103 KMMainWidget
*mainWidget
= kmkernel
->getKMMainWidget();
104 if (!mainWidget
|| kmkernel
->shuttingDown()) {
108 if (!contextMenu()) {
109 setContextMenu(new QMenu());
112 contextMenu()->clear();
113 contextMenu()->setIcon(qApp
->windowIcon());
114 contextMenu()->setTitle(i18n("KMail"));
116 if ((action
= mainWidget
->action(QStringLiteral("check_mail")))) {
117 contextMenu()->addAction(action
);
119 if ((action
= mainWidget
->action(QStringLiteral("check_mail_in")))) {
120 contextMenu()->addAction(action
);
123 mSendQueued
= mainWidget
->sendQueuedAction();
124 contextMenu()->addAction(mSendQueued
);
125 contextMenu()->addAction(mainWidget
->sendQueueViaMenu());
127 contextMenu()->addSeparator();
128 if ((action
= mainWidget
->action(QStringLiteral("new_message")))) {
129 contextMenu()->addAction(action
);
131 if ((action
= mainWidget
->action(QStringLiteral("kmail_configure_kmail")))) {
132 contextMenu()->addAction(action
);
134 contextMenu()->addSeparator();
135 if ((action
= mainWidget
->action(QStringLiteral("akonadi_work_offline")))) {
136 contextMenu()->addAction(action
);
138 contextMenu()->addSeparator();
140 if ((action
= mainWidget
->action(QStringLiteral("file_quit")))) {
141 contextMenu()->addAction(action
);
146 KMSystemTray::~KMSystemTray()
150 void KMSystemTray::setShowUnreadCount(bool showUnreadCount
)
152 if (mShowUnreadMailCount
== showUnreadCount
) {
155 mShowUnreadMailCount
= showUnreadCount
;
159 void KMSystemTray::setMode(int newMode
)
161 if (newMode
== mMode
) {
165 qCDebug(KMAIL_LOG
) << "Setting systray mMode to" << newMode
;
169 case GlobalSettings::EnumSystemTrayPolicy::ShowAlways
:
170 setStatus(KStatusNotifierItem::Active
);
172 case GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread
:
173 setStatus(mCount
> 0 ? KStatusNotifierItem::Active
: KStatusNotifierItem::Passive
);
176 qCDebug(KMAIL_LOG
) << "Unknown systray mode" << mMode
;
180 int KMSystemTray::mode() const
185 void KMSystemTray::slotGeneralFontChanged()
190 void KMSystemTray::slotGeneralPaletteChanged()
192 const KColorScheme
scheme(QPalette::Active
, KColorScheme::View
);
193 mTextColor
= scheme
.foreground(KColorScheme::LinkText
).color();
198 * Update the count of unread messages. If there are unread messages,
199 * overlay the count on top of a transparent version of the KMail icon.
200 * If there is no unread mail, restore the normal KMail icon.
202 void KMSystemTray::updateCount()
204 if (mCount
== 0 || !mIconNotificationsEnabled
) {
205 setIconByName(QStringLiteral("kmail"));
208 if (mShowUnreadMailCount
) {
209 const int overlaySize
= IconSize(KIconLoader::Panel
);
211 const QString countString
= QString::number(mCount
);
212 QFont countFont
= QFontDatabase::systemFont(QFontDatabase::GeneralFont
);
213 countFont
.setBold(true);
215 // decrease the size of the font for the number of unread messages if the
216 // number doesn't fit into the available space
217 float countFontSize
= countFont
.pointSizeF();
218 QFontMetrics
qfm(countFont
);
219 const int width
= qfm
.width(countString
);
220 if (width
> (overlaySize
- 2)) {
221 countFontSize
*= float(overlaySize
- 2) / float(width
);
222 countFont
.setPointSizeF(countFontSize
);
225 // Paint the number in a pixmap
226 QPixmap
overlayPixmap(overlaySize
, overlaySize
);
227 overlayPixmap
.fill(Qt::transparent
);
229 QPainter
p(&overlayPixmap
);
230 p
.setFont(countFont
);
231 if (!mTextColor
.isValid()) {
232 slotGeneralPaletteChanged();
235 p
.setBrush(Qt::NoBrush
);
236 p
.setPen(mTextColor
);
238 p
.drawText(overlayPixmap
.rect(), Qt::AlignCenter
, countString
);
241 QPixmap iconPixmap
= mIcon
.pixmap(overlaySize
, overlaySize
);
243 QPainter
pp(&iconPixmap
);
244 pp
.drawPixmap(0, 0, overlayPixmap
);
247 setIconByPixmap(iconPixmap
);
249 setIconByPixmap(mIcon
);
253 void KMSystemTray::setSystrayIconNotificationsEnabled(bool enabled
)
255 if (enabled
!= mIconNotificationsEnabled
) {
256 mIconNotificationsEnabled
= enabled
;
262 * On left mouse click, switch focus to the first KMMainWidget. On right
263 * click, bring up a list of all folders with a count of unread messages.
265 void KMSystemTray::slotActivated()
267 KMMainWidget
*mainWidget
= kmkernel
->getKMMainWidget();
272 QWidget
*mainWin
= mainWidget
->window();
277 KWindowInfo cur
= KWindowSystem::windowInfo(mainWin
->winId(), NET::WMDesktop
);
279 const int currentDesktop
= KWindowSystem::currentDesktop();
280 const bool wasMinimized
= cur
.isMinimized();
283 mDesktopOfMainWin
= cur
.desktop();
286 if (wasMinimized
&& (currentDesktop
!= mDesktopOfMainWin
) && (mDesktopOfMainWin
== NET::OnAllDesktops
)) {
287 KWindowSystem::setOnDesktop(mainWin
->winId(), currentDesktop
);
290 if (mDesktopOfMainWin
== NET::OnAllDesktops
) {
291 KWindowSystem::setOnAllDesktops(mainWin
->winId(), true);
294 KWindowSystem::activateWindow(mainWin
->winId());
301 void KMSystemTray::slotContextMenuAboutToShow()
303 // Rebuild popup menu before show to minimize race condition if
304 // the base KMainWidget is closed.
305 if (!buildPopupMenu()) {
309 if (mNewMessagesPopup
!= Q_NULLPTR
) {
310 contextMenu()->removeAction(mNewMessagesPopup
->menuAction());
311 delete mNewMessagesPopup
;
312 mNewMessagesPopup
= Q_NULLPTR
;
314 mNewMessagesPopup
= new QMenu();
315 fillFoldersMenu(mNewMessagesPopup
, kmkernel
->treeviewModelSelection());
317 connect(mNewMessagesPopup
, &QMenu::triggered
, this, &KMSystemTray::slotSelectCollection
);
320 mNewMessagesPopup
->setTitle(i18n("New Messages In"));
321 contextMenu()->insertAction(mSendQueued
, mNewMessagesPopup
->menuAction());
325 void KMSystemTray::fillFoldersMenu(QMenu
*menu
, const QAbstractItemModel
*model
, const QString
&parentName
, const QModelIndex
&parentIndex
)
327 const int rowCount
= model
->rowCount(parentIndex
);
328 for (int row
= 0; row
< rowCount
; ++row
) {
329 const QModelIndex index
= model
->index(row
, 0, parentIndex
);
330 const Akonadi::Collection collection
= model
->data(index
, Akonadi::EntityTreeModel::CollectionRole
).value
<Akonadi::Collection
>();
332 if (!excludeFolder(collection
)) {
333 Akonadi::CollectionStatistics statistics
= collection
.statistics();
334 count
= qMax(0LL, statistics
.unreadCount());
336 if (ignoreNewMailInFolder(collection
)) {
343 QString label
= parentName
.isEmpty() ? QString() : QString(parentName
+ QLatin1String("->"));
344 label
+= model
->data(index
).toString();
345 label
.replace(QLatin1Char('&'), QStringLiteral("&&"));
348 QAction
*action
= menu
->addAction(label
);
349 action
->setData(collection
.id());
351 if (model
->rowCount(index
) > 0) {
352 fillFoldersMenu(menu
, model
, label
, index
);
357 void KMSystemTray::hideKMail()
359 KMMainWidget
*mainWidget
= kmkernel
->getKMMainWidget();
363 QWidget
*mainWin
= mainWidget
->window();
366 mDesktopOfMainWin
= KWindowSystem::windowInfo(mainWin
->winId(),
367 NET::WMDesktop
).desktop();
368 // iconifying is unnecessary, but it looks cooler
369 KWindowSystem::minimizeWindow(mainWin
->winId());
374 void KMSystemTray::initListOfCollection()
377 const QAbstractItemModel
*model
= kmkernel
->collectionModel();
378 if (model
->rowCount() == 0) {
379 QTimer::singleShot(1000, this, SLOT(initListOfCollection()));
384 if (mMode
== GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread
) {
385 if (status() == KStatusNotifierItem::Passive
&& (mCount
> 0)) {
386 setStatus(KStatusNotifierItem::Active
);
387 } else if (status() == KStatusNotifierItem::Active
&& (mCount
== 0)) {
388 setStatus(KStatusNotifierItem::Passive
);
392 //qCDebug(KMAIL_LOG)<<" mCount :"<<mCount;
396 void KMSystemTray::unreadMail(const QAbstractItemModel
*model
, const QModelIndex
&parentIndex
)
398 const int rowCount
= model
->rowCount(parentIndex
);
399 for (int row
= 0; row
< rowCount
; ++row
) {
400 const QModelIndex index
= model
->index(row
, 0, parentIndex
);
401 const Akonadi::Collection collection
= model
->data(index
, Akonadi::EntityTreeModel::CollectionRole
).value
<Akonadi::Collection
>();
403 if (!excludeFolder(collection
)) {
405 const Akonadi::CollectionStatistics statistics
= collection
.statistics();
406 const qint64 count
= qMax(0LL, statistics
.unreadCount());
409 if (!ignoreNewMailInFolder(collection
)) {
414 if (model
->rowCount(index
) > 0) {
415 unreadMail(model
, index
);
418 // Update tooltip to reflect count of unread messages
419 setToolTipSubTitle(mCount
== 0 ? i18n("There are no unread messages")
420 : i18np("1 unread message",
421 "%1 unread messages",
425 bool KMSystemTray::hasUnreadMail() const
427 return (mCount
!= 0);
430 void KMSystemTray::slotSelectCollection(QAction
*act
)
432 const Akonadi::Collection::Id id
= act
->data().value
<Akonadi::Collection::Id
>();
433 kmkernel
->selectCollectionFromId(id
);
434 KMMainWidget
*mainWidget
= kmkernel
->getKMMainWidget();
438 QWidget
*mainWin
= mainWidget
->window();
439 if (mainWin
&& !mainWin
->isVisible()) {
444 void KMSystemTray::updateSystemTray()
446 initListOfCollection();
449 void KMSystemTray::slotCollectionStatisticsChanged(Akonadi::Collection::Id id
, const Akonadi::CollectionStatistics
&)
451 //Exclude sent mail folder
453 if (CommonKernel
->outboxCollectionFolder().id() == id
||
454 CommonKernel
->sentCollectionFolder().id() == id
||
455 CommonKernel
->templatesCollectionFolder().id() == id
||
456 CommonKernel
->trashCollectionFolder().id() == id
||
457 CommonKernel
->draftsCollectionFolder().id() == id
) {
460 initListOfCollection();
463 bool KMSystemTray::excludeFolder(const Akonadi::Collection
&collection
) const
465 if (!collection
.isValid()) {
468 if (!collection
.contentMimeTypes().contains(KMime::Message::mimeType())) {
471 if (CommonKernel
->outboxCollectionFolder() == collection
||
472 CommonKernel
->sentCollectionFolder() == collection
||
473 CommonKernel
->templatesCollectionFolder() == collection
||
474 CommonKernel
->trashCollectionFolder() == collection
||
475 CommonKernel
->draftsCollectionFolder() == collection
) {
479 if (MailCommon::Util::isVirtualCollection(collection
)) {
485 bool KMSystemTray::ignoreNewMailInFolder(const Akonadi::Collection
&collection
)
487 if (collection
.hasAttribute
<Akonadi::NewMailNotifierAttribute
>()) {
488 if (collection
.attribute
<Akonadi::NewMailNotifierAttribute
>()->ignoreNewMail()) {