// -*- mode: C++; c-file-style: "gnu" -*- /*************************************************************************** kmsystemtray.cpp - description ------------------ begin : Fri Aug 31 22:38:44 EDT 2001 copyright : (C) 2001 by Ryan Breen email : ryan@porivo.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; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kmsystemtray.h" #include "kmfolder.h" #include "kmmainwidget.h" #include "globalsettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * Construct a KSystemTray icon to be displayed when new mail * has arrived in a non-system folder. The KMSystemTray listens * for updateNewMessageNotification events from each non-system * KMFolder and maintains a store of all folders with unread * messages. * * The KMSystemTray also provides a popup menu listing each folder * with its count of unread messages, allowing the user to jump * to the first unread message in each folder. */ KMSystemTray::KMSystemTray(QWidget *parent) : KSystemTrayIcon( parent), mParentVisible( true ), mPosOfMainWin( 0, 0 ), mDesktopOfMainWin( 0 ), mMode( GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ), mCount( 0 ), mNewMessagesPopup( 0 ), mSendQueued( 0 ) { kDebug() << "Initting systray"; mLastUpdate = time( 0 ); mUpdateTimer = new QTimer( this ); mUpdateTimer->setSingleShot( true ); connect( mUpdateTimer, SIGNAL( timeout() ), SLOT( updateNewMessages() ) ); mDefaultIcon = KIcon( "kmail" ).pixmap( 22 ); setIcon( mDefaultIcon ); #ifdef Q_WS_X11 KMMainWidget * mainWidget = kmkernel->getKMMainWidget(); if ( mainWidget ) { QWidget * mainWin = mainWidget->topLevelWidget(); if ( mainWin ) { mDesktopOfMainWin = KWindowSystem::windowInfo( mainWin->winId(), NET::WMDesktop ).desktop(); mPosOfMainWin = mainWin->pos(); } } #endif // register the applet with the kernel kmkernel->registerSystemTrayApplet( this ); /** Initiate connections between folders and this object */ foldersChanged(); connect( this, SIGNAL( activated( QSystemTrayIcon::ActivationReason ) ), this, SLOT( slotActivated( QSystemTrayIcon::ActivationReason ) ) ); connect( contextMenu(), SIGNAL( aboutToShow() ), this, SLOT( slotContextMenuAboutToShow() ) ); #if 0 connect( kmkernel->folderMgr(), SIGNAL(changed()), SLOT(foldersChanged())); connect( kmkernel->searchFolderMgr(), SIGNAL(changed()), SLOT(foldersChanged())); #else kDebug() << "AKONADI PORT: Disabled code in " << Q_FUNC_INFO; #endif #if 0 connect( kmkernel->acctMgr(), SIGNAL( checkedMail( bool, bool, const QMap & ) ), SLOT( updateNewMessages() ) ); #else kDebug() << "AKONADI PORT: Disabled code in " << Q_FUNC_INFO; #endif } void KMSystemTray::buildPopupMenu() { if ( !contextMenu() ) { setContextMenu( new KMenu() ); } contextMenu()->clear(); KMMainWidget * mainWidget = kmkernel->getKMMainWidget(); if ( !mainWidget ) return; static_cast( contextMenu() )->addTitle(this->icon(), "KMail"); QAction * action; if ( ( action = mainWidget->action("check_mail") ) ) contextMenu()->addAction( action ); if ( ( action = mainWidget->action("check_mail_in") ) ) contextMenu()->addAction( action ); if ( ( mSendQueued = mainWidget->action("send_queued") ) ) contextMenu()->addAction( mSendQueued ); if ( ( action = mainWidget->action("send_queued_via") ) ) contextMenu()->addAction( action ); contextMenu()->addSeparator(); if ( ( action = mainWidget->action("new_message") ) ) contextMenu()->addAction( action ); if ( ( action = mainWidget->action("kmail_configure_kmail") ) ) contextMenu()->addAction( action ); contextMenu()->addSeparator(); KXmlGuiWindow *mainWin = ::qobject_cast(kmkernel->getKMMainWidget()->topLevelWidget()); if(mainWin) if ( ( action=mainWin->actionCollection()->action("file_quit") ) ) contextMenu()->addAction( action ); } KMSystemTray::~KMSystemTray() { // unregister the applet kmkernel->unregisterSystemTrayApplet( this ); } void KMSystemTray::setMode(int newMode) { if(newMode == mMode) return; kDebug() << "Setting systray mMode to" << newMode; mMode = newMode; switch ( mMode ) { case GlobalSettings::EnumSystemTrayPolicy::ShowAlways: if ( !isVisible() ) show(); break; case GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread: if ( mCount == 0 && isVisible() ) hide(); else if ( mCount > 0 && !isVisible() ) show(); break; default: kDebug() << "Unknown systray mode" << mMode; } } int KMSystemTray::mode() const { return mMode; } /** * Update the count of unread messages. If there are unread messages, * overlay the count on top of a transparent version of the KMail icon. * If there is no unread mail, restore the normal KMail icon. */ void KMSystemTray::updateCount() { if(mCount != 0) { int oldPixmapWidth = mDefaultIcon.size().width(); QString countString = QString::number( mCount ); QFont countFont = KGlobalSettings::generalFont(); countFont.setBold(true); // decrease the size of the font for the number of unread messages if the // number doesn't fit into the available space float countFontSize = countFont.pointSizeF(); QFontMetrics qfm( countFont ); int width = qfm.width( countString ); if( width > (oldPixmapWidth - 2) ) { countFontSize *= float( oldPixmapWidth - 2 ) / float( width ); countFont.setPointSizeF( countFontSize ); } // Overlay the light KMail icon with the number image QPixmap iconWithNumberImage = mDefaultIcon; QPainter p( &iconWithNumberImage ); p.setFont( countFont ); KColorScheme scheme( QPalette::Active, KColorScheme::View ); qfm = QFontMetrics( countFont ); QRect boundingRect = qfm.tightBoundingRect( countString ); boundingRect.adjust( 0, 0, 0, 2 ); boundingRect.setHeight( qMin( boundingRect.height(), oldPixmapWidth ) ); boundingRect.moveTo( (oldPixmapWidth - boundingRect.width()) / 2, ((oldPixmapWidth - boundingRect.height()) / 2) - 1 ); p.setOpacity( 0.7 ); p.setBrush( scheme.background( KColorScheme::LinkBackground ) ); p.setPen( scheme.background( KColorScheme::LinkBackground ).color() ); p.drawRoundedRect( boundingRect, 2.0, 2.0 ); p.setBrush( Qt::NoBrush ); p.setPen( scheme.foreground( KColorScheme::LinkText ).color() ); p.setOpacity( 1.0 ); p.drawText( iconWithNumberImage.rect(), Qt::AlignCenter, countString ); setIcon( iconWithNumberImage ); } else { setIcon( mDefaultIcon ); } } /** * Refreshes the list of folders we are monitoring. This is called on * startup and is also connected to the 'changed' signal on the KMFolderMgr. */ void KMSystemTray::foldersChanged() { /** * Hide and remove all unread mappings to cover the case where the only * unread message was in a folder that was just removed. */ mFoldersWithUnread.clear(); mPendingUpdates.clear(); mCount = 0; if ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ) { hide(); } /** Disconnect all previous connections */ disconnect(this, SLOT(updateNewMessageNotification(KMFolder *))); QStringList folderNames; QList > folderList; #if 0 kmkernel->folderMgr()->createFolderList(&folderNames, &folderList); #else kDebug() << "AKONADI PORT: Disabled code in " << Q_FUNC_INFO; #endif QStringList::iterator strIt = folderNames.begin(); for(QList >::iterator it = folderList.begin(); it != folderList.end() && strIt != folderNames.end(); ++it, ++strIt) { KMFolder * currentFolder = *it; QString currentName = *strIt; if ( ((!currentFolder->isSystemFolder() || (currentFolder->name().toLower() == "inbox")) || (currentFolder->folderType() == KMFolderTypeImap)) && !currentFolder->ignoreNewMail() ) { /** If this is a new folder, start listening for messages */ connect(currentFolder, SIGNAL(numUnreadMsgsChanged(KMFolder *)), this, SLOT(updateNewMessageNotification(KMFolder *))); /** Check all new folders to see if we started with any new messages */ updateNewMessageNotification(currentFolder); } else { disconnect( currentFolder, SIGNAL( numUnreadMsgsChanged(KMFolder*) ), this, SLOT( updateNewMessageNotification(KMFolder *) ) ); } } } /** * On left mouse click, switch focus to the first KMMainWidget. On right * click, bring up a list of all folders with a count of unread messages. */ void KMSystemTray::slotActivated( QSystemTrayIcon::ActivationReason reason ) { kDebug() << "trigger:" << reason; // switch to kmail on left mouse button if( reason == QSystemTrayIcon::Trigger ) { if( mParentVisible && mainWindowIsOnCurrentDesktop() ) hideKMail(); else showKMail(); } } void KMSystemTray::slotContextMenuAboutToShow() { // Rebuild popup menu before show to minimize race condition if // the base KMainWidget is closed. buildPopupMenu(); if ( mNewMessagesPopup != 0 ) { contextMenu()->removeAction( mNewMessagesPopup->menuAction() ); delete mNewMessagesPopup; mNewMessagesPopup = 0; } if ( mFoldersWithUnread.count() > 0 ) { mNewMessagesPopup = new KMenu(); QMap, int>::Iterator it = mFoldersWithUnread.begin(); QSignalMapper *folderMapper = new QSignalMapper( this ); connect( folderMapper, SIGNAL( mapped( int ) ), this, SLOT( selectedAccount( int ) ) ); for(uint i=0; it != mFoldersWithUnread.end(); ++i) { mPopupFolders.append( it.key() ); QString folderText = prettyName(it.key()) + " (" + QString::number(it.value()) + ')'; QAction *action = new QAction( folderText, this ); connect( action, SIGNAL( triggered( bool ) ), folderMapper, SLOT( map() ) ); folderMapper->setMapping( action, i ); mNewMessagesPopup->addAction( action ); ++it; } mNewMessagesPopup->setTitle( i18n("New Messages In") ); contextMenu()->insertAction( mSendQueued, mNewMessagesPopup->menuAction() ); kDebug() << "Folders added"; } } /** * Return the name of the folder in which the mail is deposited, prepended * with the account name if the folder is IMAP. */ QString KMSystemTray::prettyName(KMFolder * fldr) { QString rvalue = fldr->label(); if(fldr->folderType() == KMFolderTypeImap) { #if 0 //TODO port to akonadi KMFolderImap * imap = dynamic_cast (fldr->storage()); assert(imap); if((imap->account() != 0) && (imap->account()->name() != 0) ) { kDebug() << "IMAP folder, prepend label with type"; rvalue = imap->account()->name() + "->" + rvalue; } #else kDebug() << "AKONADI PORT: Disabled code in " << Q_FUNC_INFO; #endif } kDebug() << "Got label" << rvalue; return rvalue; } bool KMSystemTray::mainWindowIsOnCurrentDesktop() { #ifdef Q_WS_X11 KMMainWidget * mainWidget = kmkernel->getKMMainWidget(); if ( !mainWidget ) return false; QWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget(); if ( !mainWin ) return false; return KWindowSystem::windowInfo( mainWin->winId(), NET::WMDesktop ).isOnCurrentDesktop(); #else return true; #endif } /** * Shows and raises the first KMMainWidget and * switches to the appropriate virtual desktop. */ void KMSystemTray::showKMail() { if (!kmkernel->getKMMainWidget()) return; QWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget(); assert(mainWin); #ifdef Q_WS_X11 if(mainWin) { KWindowInfo cur = KWindowSystem::windowInfo( mainWin->winId(), NET::WMDesktop ); if ( cur.valid() ) mDesktopOfMainWin = cur.desktop(); // switch to appropriate desktop if ( mDesktopOfMainWin != NET::OnAllDesktops ) KWindowSystem::setCurrentDesktop( mDesktopOfMainWin ); if ( !mParentVisible ) { if ( mDesktopOfMainWin == NET::OnAllDesktops ) KWindowSystem::setOnAllDesktops( mainWin->winId(), true ); } KWindowSystem::activateWindow( mainWin->winId() ); } #endif if ( !mParentVisible ) { mainWin->move( mPosOfMainWin ); mainWin->show(); mParentVisible = true; } kmkernel->raise(); //Fake that the folders have changed so that the icon status is correct foldersChanged(); } void KMSystemTray::hideKMail() { if (!kmkernel->getKMMainWidget()) return; QWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget(); assert(mainWin); if(mainWin) { mPosOfMainWin = mainWin->pos(); #ifdef Q_WS_X11 mDesktopOfMainWin = KWindowSystem::windowInfo( mainWin->winId(), NET::WMDesktop ).desktop(); // iconifying is unnecessary, but it looks cooler KWindowSystem::minimizeWindow( mainWin->winId() ); #endif mainWin->hide(); mParentVisible = false; } } /** * Called on startup of the KMSystemTray and when the numUnreadMsgsChanged signal * is emitted for one of the watched folders. Shows the system tray icon if there * are new messages and the icon was hidden, or hides the system tray icon if there * are no more new messages. */ void KMSystemTray::updateNewMessageNotification(KMFolder * fldr) { //We don't want to count messages from search folders as they // already counted as part of their original folders if( !fldr || fldr->folderType() == KMFolderTypeSearch ) { // kDebug() << "Null or a search folder, can't mess with that"; return; } mPendingUpdates[ fldr ] = true; if ( time( 0 ) - mLastUpdate > 2 ) { mUpdateTimer->stop(); updateNewMessages(); } else { mUpdateTimer->start(150); } } void KMSystemTray::updateNewMessages() { for ( QMap, bool>::Iterator it1 = mPendingUpdates.begin(); it1 != mPendingUpdates.end(); ++it1) { KMFolder *fldr = it1.key(); if ( !fldr ) // deleted folder continue; // The number of unread messages in that folder int unread = fldr->countUnread(); QMap, int>::Iterator it = mFoldersWithUnread.find( fldr ); bool unmapped = ( it == mFoldersWithUnread.end() ); // If the folder is not mapped yet, increment count by numUnread // in folder if ( unmapped ) mCount += unread; //Otherwise, get the difference between the numUnread in the folder and // our last known version, and adjust mCount with that difference else { int diff = unread - it.value(); mCount += diff; } if ( unread > 0 ) { // Add folder to our internal store, or update unread count if already mapped mFoldersWithUnread.insert( fldr, unread ); //kDebug() << "There are now" << mFoldersWithUnread.count() << "folders with unread"; } /* * Look for folder in the list of folders already represented. If there are * unread messages and the system tray icon is hidden, show it. If there are * no unread messages, remove the folder from the mapping. */ if ( unmapped ) { // Spurious notification, ignore if ( unread == 0 ) continue; // Make sure the icon will be displayed if ( ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ) && !isVisible() ) { show(); } } else { if ( unread == 0 ) { kDebug() << "Removing folder from internal store" << fldr->name(); // Remove the folder from the internal store mFoldersWithUnread.remove(fldr); // if this was the last folder in the dictionary, hide the systemtray icon if ( mFoldersWithUnread.count() == 0 ) { mPopupFolders.clear(); disconnect ( this, SLOT( selectedAccount( int ) ) ); mCount = 0; if ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ) { hide(); } } } } } mPendingUpdates.clear(); updateCount(); // Update tooltip to reflect count of unread messages setToolTip( mCount == 0 ? i18n("KMail - There are no unread messages") : i18np("KMail - 1 unread message", "KMail - %1 unread messages", mCount)); mLastUpdate = time( 0 ); } /** * Called when user selects a folder name from the popup menu. Shows * the first KMMainWin in the memberlist and jumps to the first unread * message in the selected folder. */ void KMSystemTray::selectedAccount(int id) { showKMail(); KMMainWidget * mainWidget = kmkernel->getKMMainWidget(); if (!mainWidget) { kmkernel->openReader(); mainWidget = kmkernel->getKMMainWidget(); } assert(mainWidget); /** Select folder */ KMFolder * fldr = mPopupFolders.at(id); if(!fldr) return; #ifdef OLD_FOLDERVIEW KMail::MainFolderView * ftw = mainWidget->mainFolderView(); if ( !ftw ) return; ftw->setCurrentFolder( fldr ); #endif } bool KMSystemTray::hasUnreadMail() const { return ( mCount != 0 ); } #include "kmsystemtray.moc"