kmail

kmsystemtray.cpp

Go to the documentation of this file.
00001 // -*- mode: C++; c-file-style: "gnu" -*-
00002 /***************************************************************************
00003                           kmsystemtray.cpp  -  description
00004                              -------------------
00005     begin                : Fri Aug 31 22:38:44 EDT 2001
00006     copyright            : (C) 2001 by Ryan Breen
00007     email                : ryan@porivo.com
00008  ***************************************************************************/
00009 
00010 /***************************************************************************
00011  *                                                                         *
00012  *   This program is free software; you can redistribute it and/or modify  *
00013  *   it under the terms of the GNU General Public License as published by  *
00014  *   the Free Software Foundation; either version 2 of the License, or     *
00015  *   (at your option) any later version.                                   *
00016  *                                                                         *
00017  ***************************************************************************/
00018 
00019 #include <config.h>
00020 
00021 #include "kmsystemtray.h"
00022 #include "kmfolder.h"
00023 #include "kmfoldertree.h"
00024 #include "kmfoldermgr.h"
00025 #include "kmfolderimap.h"
00026 #include "kmmainwidget.h"
00027 #include "accountmanager.h"
00028 using KMail::AccountManager;
00029 #include "globalsettings.h"
00030 
00031 #include <kapplication.h>
00032 #include <kmainwindow.h>
00033 #include <kglobalsettings.h>
00034 #include <kiconloader.h>
00035 #include <kiconeffect.h>
00036 #include <kwin.h>
00037 #include <kdebug.h>
00038 #include <kpopupmenu.h>
00039 
00040 #include <qpainter.h>
00041 #include <qbitmap.h>
00042 #include <qtooltip.h>
00043 #include <qwidgetlist.h>
00044 #include <qobjectlist.h>
00045 
00046 #include <math.h>
00047 #include <assert.h>
00048 
00060 KMSystemTray::KMSystemTray(QWidget *parent, const char *name)
00061   : KSystemTray( parent, name ),
00062     mParentVisible( true ),
00063     mPosOfMainWin( 0, 0 ),
00064     mDesktopOfMainWin( 0 ),
00065     mMode( GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ),
00066     mCount( 0 ),
00067     mNewMessagePopupId(-1),
00068     mPopupMenu(0)
00069 {
00070   setAlignment( AlignCenter );
00071   kdDebug(5006) << "Initting systray" << endl;
00072 
00073   mLastUpdate = time( 0 );
00074   mUpdateTimer = new QTimer( this, "systraytimer" );
00075   connect( mUpdateTimer, SIGNAL( timeout() ), SLOT( updateNewMessages() ) );
00076 
00077   mDefaultIcon = loadIcon( "kmail" );
00078   mLightIconImage = loadIcon( "kmaillight" ).convertToImage();
00079 
00080   setPixmap(mDefaultIcon);
00081 
00082   KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
00083   if ( mainWidget ) {
00084     QWidget * mainWin = mainWidget->topLevelWidget();
00085     if ( mainWin ) {
00086       mDesktopOfMainWin = KWin::windowInfo( mainWin->winId(),
00087                                             NET::WMDesktop ).desktop();
00088       mPosOfMainWin = mainWin->pos();
00089     }
00090   }
00091 
00092   // register the applet with the kernel
00093   kmkernel->registerSystemTrayApplet( this );
00094 
00096   foldersChanged();
00097 
00098   connect( kmkernel->folderMgr(), SIGNAL(changed()), SLOT(foldersChanged()));
00099   connect( kmkernel->imapFolderMgr(), SIGNAL(changed()), SLOT(foldersChanged()));
00100   connect( kmkernel->dimapFolderMgr(), SIGNAL(changed()), SLOT(foldersChanged()));
00101   connect( kmkernel->searchFolderMgr(), SIGNAL(changed()), SLOT(foldersChanged()));
00102 
00103   connect( kmkernel->acctMgr(), SIGNAL( checkedMail( bool, bool, const QMap<QString, int> & ) ),
00104            SLOT( updateNewMessages() ) );
00105 }
00106 
00107 void KMSystemTray::buildPopupMenu()
00108 {
00109   // Delete any previously created popup menu
00110   delete mPopupMenu;
00111   mPopupMenu = 0;
00112 
00113   mPopupMenu = new KPopupMenu();
00114   KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
00115   if ( !mainWidget )
00116     return;
00117 
00118   mPopupMenu->insertTitle(*(this->pixmap()), "KMail");
00119   KAction * action;
00120   if ( ( action = mainWidget->action("check_mail") ) )
00121     action->plug( mPopupMenu );
00122   if ( ( action = mainWidget->action("check_mail_in") ) )
00123     action->plug( mPopupMenu );
00124   if ( ( action = mainWidget->action("send_queued") ) )
00125     action->plug( mPopupMenu );
00126   if ( ( action = mainWidget->action("send_queued_via") ) )
00127     action->plug( mPopupMenu );
00128   mPopupMenu->insertSeparator();
00129   if ( ( action = mainWidget->action("new_message") ) )
00130     action->plug( mPopupMenu );
00131   if ( ( action = mainWidget->action("kmail_configure_kmail") ) )
00132     action->plug( mPopupMenu );
00133   mPopupMenu->insertSeparator();
00134 
00135   KMainWindow *mainWin = ::qt_cast<KMainWindow*>(kmkernel->getKMMainWidget()->topLevelWidget());
00136   if(mainWin)
00137     if ( ( action=mainWin->actionCollection()->action("file_quit") ) )
00138       action->plug( mPopupMenu );
00139 }
00140 
00141 KMSystemTray::~KMSystemTray()
00142 {
00143   // unregister the applet
00144   kmkernel->unregisterSystemTrayApplet( this );
00145 
00146   delete mPopupMenu;
00147   mPopupMenu = 0;
00148 }
00149 
00150 void KMSystemTray::setMode(int newMode)
00151 {
00152   if(newMode == mMode) return;
00153 
00154   kdDebug(5006) << "Setting systray mMode to " << newMode << endl;
00155   mMode = newMode;
00156 
00157   switch ( mMode ) {
00158   case GlobalSettings::EnumSystemTrayPolicy::ShowAlways:
00159     if ( isHidden() )
00160       show();
00161     break;
00162   case GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread:
00163     if ( mCount == 0 && !isHidden() )
00164       hide();
00165     else if ( mCount > 0 && isHidden() )
00166       show();
00167     break;
00168   default:
00169     kdDebug(5006) << k_funcinfo << " Unknown systray mode " << mMode << endl;
00170   }
00171 }
00172 
00173 int KMSystemTray::mode() const
00174 {
00175   return mMode;
00176 }
00177 
00183 void KMSystemTray::updateCount()
00184 {
00185   if(mCount != 0)
00186   {
00187     int oldPixmapWidth = pixmap()->size().width();
00188     int oldPixmapHeight = pixmap()->size().height();
00189 
00190     QString countString = QString::number( mCount );
00191     QFont countFont = KGlobalSettings::generalFont();
00192     countFont.setBold(true);
00193 
00194     // decrease the size of the font for the number of unread messages if the
00195     // number doesn't fit into the available space
00196     float countFontSize = countFont.pointSizeFloat();
00197     QFontMetrics qfm( countFont );
00198     int width = qfm.width( countString );
00199     if( width > oldPixmapWidth )
00200     {
00201       countFontSize *= float( oldPixmapWidth ) / float( width );
00202       countFont.setPointSizeFloat( countFontSize );
00203     }
00204 
00205     // Create an image which represents the number of unread messages
00206     // and which has a transparent background.
00207     // Unfortunately this required the following twisted code because for some
00208     // reason text that is drawn on a transparent pixmap is invisible
00209     // (apparently the alpha channel isn't changed when the text is drawn).
00210     // Therefore I have to draw the text on a solid background and then remove
00211     // the background by making it transparent with QPixmap::setMask. This
00212     // involves the slow createHeuristicMask() function (from the API docs:
00213     // "This function is slow because it involves transformation to a QImage,
00214     // non-trivial computations and a transformation back to a QBitmap."). Then
00215     // I have to convert the resulting QPixmap to a QImage in order to overlay
00216     // the light KMail icon with the number (because KIconEffect::overlay only
00217     // works with QImage). Finally the resulting QImage has to be converted
00218     // back to a QPixmap.
00219     // That's a lot of work for overlaying the KMail icon with the number of
00220     // unread messages, but every other approach I tried failed miserably.
00221     //                                                           IK, 2003-09-22
00222     QPixmap numberPixmap( oldPixmapWidth, oldPixmapHeight );
00223     numberPixmap.fill( Qt::white );
00224     QPainter p( &numberPixmap );
00225     p.setFont( countFont );
00226     p.setPen( Qt::blue );
00227     p.drawText( numberPixmap.rect(), Qt::AlignCenter, countString );
00228     numberPixmap.setMask( numberPixmap.createHeuristicMask() );
00229     QImage numberImage = numberPixmap.convertToImage();
00230 
00231     // Overlay the light KMail icon with the number image
00232     QImage iconWithNumberImage = mLightIconImage.copy();
00233     KIconEffect::overlay( iconWithNumberImage, numberImage );
00234 
00235     QPixmap iconWithNumber;
00236     iconWithNumber.convertFromImage( iconWithNumberImage );
00237     setPixmap( iconWithNumber );
00238   } else
00239   {
00240     setPixmap( mDefaultIcon );
00241   }
00242 }
00243 
00248 void KMSystemTray::foldersChanged()
00249 {
00254   mFoldersWithUnread.clear();
00255   mCount = 0;
00256 
00257   if ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ) {
00258     hide();
00259   }
00260 
00262   disconnect(this, SLOT(updateNewMessageNotification(KMFolder *)));
00263 
00264   QStringList folderNames;
00265   QValueList<QGuardedPtr<KMFolder> > folderList;
00266   kmkernel->folderMgr()->createFolderList(&folderNames, &folderList);
00267   kmkernel->imapFolderMgr()->createFolderList(&folderNames, &folderList);
00268   kmkernel->dimapFolderMgr()->createFolderList(&folderNames, &folderList);
00269   kmkernel->searchFolderMgr()->createFolderList(&folderNames, &folderList);
00270 
00271   QStringList::iterator strIt = folderNames.begin();
00272 
00273   for(QValueList<QGuardedPtr<KMFolder> >::iterator it = folderList.begin();
00274      it != folderList.end() && strIt != folderNames.end(); ++it, ++strIt)
00275   {
00276     KMFolder * currentFolder = *it;
00277     QString currentName = *strIt;
00278 
00279     if ( ((!currentFolder->isSystemFolder() || (currentFolder->name().lower() == "inbox")) ||
00280          (currentFolder->folderType() == KMFolderTypeImap)) &&
00281          !currentFolder->ignoreNewMail() )
00282     {
00284       connect(currentFolder, SIGNAL(numUnreadMsgsChanged(KMFolder *)),
00285               this, SLOT(updateNewMessageNotification(KMFolder *)));
00286 
00288       updateNewMessageNotification(currentFolder);
00289     }
00290   }
00291 }
00292 
00297 void KMSystemTray::mousePressEvent(QMouseEvent *e)
00298 {
00299   // switch to kmail on left mouse button
00300   if( e->button() == LeftButton )
00301   {
00302     if( mParentVisible && mainWindowIsOnCurrentDesktop() )
00303       hideKMail();
00304     else
00305       showKMail();
00306   }
00307 
00308   // open popup menu on right mouse button
00309   if( e->button() == RightButton )
00310   {
00311     mPopupFolders.clear();
00312     mPopupFolders.reserve( mFoldersWithUnread.count() );
00313 
00314     // Rebuild popup menu at click time to minimize race condition if
00315     // the base KMainWidget is closed.
00316     buildPopupMenu();
00317 
00318     if(mNewMessagePopupId != -1)
00319     {
00320       mPopupMenu->removeItem(mNewMessagePopupId);
00321     }
00322 
00323     if(mFoldersWithUnread.count() > 0)
00324     {
00325       KPopupMenu *newMessagesPopup = new KPopupMenu();
00326 
00327       QMap<QGuardedPtr<KMFolder>, int>::Iterator it = mFoldersWithUnread.begin();
00328       for(uint i=0; it != mFoldersWithUnread.end(); ++i)
00329       {
00330         kdDebug(5006) << "Adding folder" << endl;
00331         mPopupFolders.append( it.key() );
00332         QString item = prettyName(it.key()) + " (" + QString::number(it.data()) + ")";
00333         newMessagesPopup->insertItem(item, this, SLOT(selectedAccount(int)), 0, i);
00334         ++it;
00335       }
00336 
00337       mNewMessagePopupId = mPopupMenu->insertItem(i18n("New Messages In"),
00338                                                   newMessagesPopup, mNewMessagePopupId, 3);
00339 
00340       kdDebug(5006) << "Folders added" << endl;
00341     }
00342 
00343     mPopupMenu->popup(e->globalPos());
00344   }
00345 
00346 }
00347 
00352 QString KMSystemTray::prettyName(KMFolder * fldr)
00353 {
00354   QString rvalue = fldr->label();
00355   if(fldr->folderType() == KMFolderTypeImap)
00356   {
00357     KMFolderImap * imap = dynamic_cast<KMFolderImap*> (fldr->storage());
00358     assert(imap);
00359 
00360     if((imap->account() != 0) &&
00361        (imap->account()->name() != 0) )
00362     {
00363       kdDebug(5006) << "IMAP folder, prepend label with type" << endl;
00364       rvalue = imap->account()->name() + "->" + rvalue;
00365     }
00366   }
00367 
00368   kdDebug(5006) << "Got label " << rvalue << endl;
00369 
00370   return rvalue;
00371 }
00372 
00373 
00374 bool KMSystemTray::mainWindowIsOnCurrentDesktop()
00375 {
00376   KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
00377   if ( !mainWidget )
00378     return false;
00379 
00380   QWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget();
00381   if ( !mainWin )
00382     return false;
00383 
00384   return KWin::windowInfo( mainWin->winId(),
00385                            NET::WMDesktop ).isOnCurrentDesktop();
00386 }
00387 
00392 void KMSystemTray::showKMail()
00393 {
00394   if (!kmkernel->getKMMainWidget())
00395     return;
00396   QWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget();
00397   assert(mainWin);
00398   if(mainWin)
00399   {
00400     KWin::WindowInfo cur =  KWin::windowInfo( mainWin->winId(), NET::WMDesktop );
00401     if ( cur.valid() ) mDesktopOfMainWin = cur.desktop();
00402     // switch to appropriate desktop
00403     if ( mDesktopOfMainWin != NET::OnAllDesktops )
00404       KWin::setCurrentDesktop( mDesktopOfMainWin );
00405     if ( !mParentVisible ) {
00406       if ( mDesktopOfMainWin == NET::OnAllDesktops )
00407         KWin::setOnAllDesktops( mainWin->winId(), true );
00408       mainWin->move( mPosOfMainWin );
00409       mainWin->show();
00410     }
00411     KWin::activateWindow( mainWin->winId() );
00412     mParentVisible = true;
00413   }
00414   kmkernel->raise();
00415 
00416   //Fake that the folders have changed so that the icon status is correct
00417   foldersChanged();
00418 }
00419 
00420 void KMSystemTray::hideKMail()
00421 {
00422   if (!kmkernel->getKMMainWidget())
00423     return;
00424   QWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget();
00425   assert(mainWin);
00426   if(mainWin)
00427   {
00428     mDesktopOfMainWin = KWin::windowInfo( mainWin->winId(),
00429                                           NET::WMDesktop ).desktop();
00430     mPosOfMainWin = mainWin->pos();
00431     // iconifying is unnecessary, but it looks cooler
00432     KWin::iconifyWindow( mainWin->winId() );
00433     mainWin->hide();
00434     mParentVisible = false;
00435   }
00436 }
00437 
00444 void KMSystemTray::updateNewMessageNotification(KMFolder * fldr)
00445 {
00446   //We don't want to count messages from search folders as they
00447   //  already counted as part of their original folders
00448   if( !fldr ||
00449       fldr->folderType() == KMFolderTypeSearch )
00450   {
00451     // kdDebug(5006) << "Null or a search folder, can't mess with that" << endl;
00452     return;
00453   }
00454 
00455   mPendingUpdates[ fldr ] = true;
00456   if ( time( 0 ) - mLastUpdate > 2 ) {
00457     mUpdateTimer->stop();
00458     updateNewMessages();
00459   }
00460   else {
00461     mUpdateTimer->start(150, true);
00462   }
00463 }
00464 
00465 void KMSystemTray::updateNewMessages()
00466 {
00467   for ( QMap<QGuardedPtr<KMFolder>, bool>::Iterator it = mPendingUpdates.begin();
00468         it != mPendingUpdates.end(); ++it)
00469   {
00470   KMFolder *fldr = it.key();
00471   if ( !fldr ) // deleted folder
00472     continue;
00473 
00475   int unread = fldr->countUnread();
00476 
00477   QMap<QGuardedPtr<KMFolder>, int>::Iterator it =
00478       mFoldersWithUnread.find(fldr);
00479   bool unmapped = (it == mFoldersWithUnread.end());
00480 
00483   if(unmapped) mCount += unread;
00484   /* Otherwise, get the difference between the numUnread in the folder and
00485    * our last known version, and adjust mCount with that difference */
00486   else
00487   {
00488     int diff = unread - it.data();
00489     mCount += diff;
00490   }
00491 
00492   if(unread > 0)
00493   {
00495     mFoldersWithUnread.insert(fldr, unread);
00496     //kdDebug(5006) << "There are now " << mFoldersWithUnread.count() << " folders with unread" << endl;
00497   }
00498 
00504   if(unmapped)
00505   {
00507     if(unread == 0) continue;
00508 
00510     if ( ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread )
00511          && isHidden() ) {
00512       show();
00513     }
00514 
00515   } else
00516   {
00517 
00518     if(unread == 0)
00519     {
00520       kdDebug(5006) << "Removing folder from internal store " << fldr->name() << endl;
00521 
00523       mFoldersWithUnread.remove(fldr);
00524 
00526       if(mFoldersWithUnread.count() == 0)
00527       {
00528         mPopupFolders.clear();
00529         disconnect(this, SLOT(selectedAccount(int)));
00530 
00531         mCount = 0;
00532 
00533         if ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ) {
00534           hide();
00535         }
00536       }
00537     }
00538   }
00539 
00540   }
00541   mPendingUpdates.clear();
00542   updateCount();
00543 
00545   QToolTip::remove(this);
00546   QToolTip::add(this, mCount == 0 ?
00547               i18n("There are no unread messages")
00548               : i18n("There is 1 unread message.",
00549                              "There are %n unread messages.",
00550                            mCount));
00551 
00552   mLastUpdate = time( 0 );
00553 }
00554 
00560 void KMSystemTray::selectedAccount(int id)
00561 {
00562   showKMail();
00563 
00564   KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
00565   if (!mainWidget)
00566   {
00567     kmkernel->openReader();
00568     mainWidget = kmkernel->getKMMainWidget();
00569   }
00570 
00571   assert(mainWidget);
00572 
00574   KMFolder * fldr = mPopupFolders.at(id);
00575   if(!fldr) return;
00576   KMFolderTree * ft = mainWidget->folderTree();
00577   if(!ft) return;
00578   QListViewItem * fldrIdx = ft->indexOfFolder(fldr);
00579   if(!fldrIdx) return;
00580 
00581   ft->setCurrentItem(fldrIdx);
00582   ft->selectCurrentFolder();
00583 }
00584 
00585 #include "kmsystemtray.moc"