Move away from KMime::Headers::Generic overloaded ctors.
[kdepim.git] / kmail / kmsystemtray.cpp
blobffb567fb424afdddbe26ac6caa82882cb3736c91
1 //
2 /***************************************************************************
3 kmsystemtray.cpp - description
4 ------------------
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 /***************************************************************************
13 * *
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. *
18 * *
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"
33 #include <QMenu>
34 #include <KLocalizedString>
35 #include <QAction>
36 #include <KActionMenu>
37 #include "widgets/kactionmenutransport.h"
38 #include <KActionCollection>
39 #include <QPainter>
41 #include <AkonadiCore/ChangeRecorder>
42 #include <AkonadiCore/EntityTreeModel>
43 #include <QFontDatabase>
44 #include <AkonadiCore/EntityMimeTypeFilterModel>
46 using namespace MailCommon;
48 /**
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
53 * messages.
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.
59 namespace KMail
61 KMSystemTray::KMSystemTray(QObject *parent)
62 : KStatusNotifierItem(parent),
63 mIcon(QIcon::fromTheme(QStringLiteral("mail-unread-new"))),
64 mDesktopOfMainWin(0),
65 mMode(GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread),
66 mCount(0),
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();
78 if (mainWidget) {
79 QWidget *mainWin = mainWidget->window();
80 if (mainWin) {
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()) {
105 return false;
108 if (!contextMenu()) {
109 setContextMenu(new QMenu());
112 contextMenu()->clear();
113 contextMenu()->setIcon(qApp->windowIcon());
114 contextMenu()->setTitle(i18n("KMail"));
115 QAction *action;
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);
143 return true;
146 KMSystemTray::~KMSystemTray()
150 void KMSystemTray::setShowUnreadCount(bool showUnreadCount)
152 if (mShowUnreadMailCount == showUnreadCount) {
153 return;
155 mShowUnreadMailCount = showUnreadCount;
156 updateSystemTray();
159 void KMSystemTray::setMode(int newMode)
161 if (newMode == mMode) {
162 return;
165 qCDebug(KMAIL_LOG) << "Setting systray mMode to" << newMode;
166 mMode = newMode;
168 switch (mMode) {
169 case GlobalSettings::EnumSystemTrayPolicy::ShowAlways:
170 setStatus(KStatusNotifierItem::Active);
171 break;
172 case GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread:
173 setStatus(mCount > 0 ? KStatusNotifierItem::Active : KStatusNotifierItem::Passive);
174 break;
175 default:
176 qCDebug(KMAIL_LOG) << "Unknown systray mode" << mMode;
180 int KMSystemTray::mode() const
182 return mMode;
185 void KMSystemTray::slotGeneralFontChanged()
187 updateSystemTray();
190 void KMSystemTray::slotGeneralPaletteChanged()
192 const KColorScheme scheme(QPalette::Active, KColorScheme::View);
193 mTextColor = scheme.foreground(KColorScheme::LinkText).color();
194 updateSystemTray();
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"));
206 return;
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);
237 p.setOpacity(1.0);
238 p.drawText(overlayPixmap.rect(), Qt::AlignCenter, countString);
239 p.end();
241 QPixmap iconPixmap = mIcon.pixmap(overlaySize, overlaySize);
243 QPainter pp(&iconPixmap);
244 pp.drawPixmap(0, 0, overlayPixmap);
245 pp.end();
247 setIconByPixmap(iconPixmap);
248 } else {
249 setIconByPixmap(mIcon);
253 void KMSystemTray::setSystrayIconNotificationsEnabled(bool enabled)
255 if (enabled != mIconNotificationsEnabled) {
256 mIconNotificationsEnabled = enabled;
257 updateSystemTray();
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();
268 if (!mainWidget) {
269 return ;
272 QWidget *mainWin = mainWidget->window();
273 if (!mainWin) {
274 return ;
277 KWindowInfo cur = KWindowSystem::windowInfo(mainWin->winId(), NET::WMDesktop);
279 const int currentDesktop = KWindowSystem::currentDesktop();
280 const bool wasMinimized = cur.isMinimized();
282 if (cur.valid()) {
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());
296 if (wasMinimized) {
297 kmkernel->raise();
301 void KMSystemTray::slotContextMenuAboutToShow()
303 // Rebuild popup menu before show to minimize race condition if
304 // the base KMainWidget is closed.
305 if (!buildPopupMenu()) {
306 return;
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);
319 if (mCount > 0) {
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>();
331 qint64 count = 0;
332 if (!excludeFolder(collection)) {
333 Akonadi::CollectionStatistics statistics = collection.statistics();
334 count = qMax(0LL, statistics.unreadCount());
335 if (count > 0) {
336 if (ignoreNewMailInFolder(collection)) {
337 count = 0;
338 } else {
339 mCount += count;
343 QString label = parentName.isEmpty() ? QString() : QString(parentName + QLatin1String("->"));
344 label += model->data(index).toString();
345 label.replace(QLatin1Char('&'), QStringLiteral("&&"));
346 if (count > 0) {
347 // insert an item
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();
360 if (!mainWidget) {
361 return;
363 QWidget *mainWin = mainWidget->window();
364 Q_ASSERT(mainWin);
365 if (mainWin) {
366 mDesktopOfMainWin = KWindowSystem::windowInfo(mainWin->winId(),
367 NET::WMDesktop).desktop();
368 // iconifying is unnecessary, but it looks cooler
369 KWindowSystem::minimizeWindow(mainWin->winId());
370 mainWin->hide();
374 void KMSystemTray::initListOfCollection()
376 mCount = 0;
377 const QAbstractItemModel *model = kmkernel->collectionModel();
378 if (model->rowCount() == 0) {
379 QTimer::singleShot(1000, this, SLOT(initListOfCollection()));
380 return;
382 unreadMail(model);
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;
393 updateCount();
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());
408 if (count > 0) {
409 if (!ignoreNewMailInFolder(collection)) {
410 mCount += count;
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",
422 mCount));
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();
435 if (!mainWidget) {
436 return ;
438 QWidget *mainWin = mainWidget->window();
439 if (mainWin && !mainWin->isVisible()) {
440 activate();
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) {
458 return;
460 initListOfCollection();
463 bool KMSystemTray::excludeFolder(const Akonadi::Collection &collection) const
465 if (!collection.isValid()) {
466 return true;
468 if (!collection.contentMimeTypes().contains(KMime::Message::mimeType())) {
469 return true;
471 if (CommonKernel->outboxCollectionFolder() == collection ||
472 CommonKernel->sentCollectionFolder() == collection ||
473 CommonKernel->templatesCollectionFolder() == collection ||
474 CommonKernel->trashCollectionFolder() == collection ||
475 CommonKernel->draftsCollectionFolder() == collection) {
476 return true;
479 if (MailCommon::Util::isVirtualCollection(collection)) {
480 return true;
482 return false;
485 bool KMSystemTray::ignoreNewMailInFolder(const Akonadi::Collection &collection)
487 if (collection.hasAttribute<Akonadi::NewMailNotifierAttribute>()) {
488 if (collection.attribute<Akonadi::NewMailNotifierAttribute>()->ignoreNewMail()) {
489 return true;
492 return false;