kmail Library API Documentation

kmsystemtray.cpp

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 "kmfoldertree.h"
00023 #include "kmfoldermgr.h"
00024 #include "kmfolderimap.h"
00025 #include "kmmainwidget.h"
00026 #include "kmacctmgr.h"
00027 #include "globalsettings.h"
00028 
00029 #include <kapplication.h>
00030 #include <kmainwindow.h>
00031 #include <kglobalsettings.h>
00032 #include <kiconloader.h>
00033 #include <kiconeffect.h>
00034 #include <kwin.h>
00035 #include <kdebug.h>
00036 
00037 #include <qpainter.h>
00038 #include <qbitmap.h>
00039 #include <qtooltip.h>
00040 #include <qwidgetlist.h>
00041 #include <qobjectlist.h>
00042 
00043 #include <math.h>
00044 #include <assert.h>
00045 
00057 KMSystemTray::KMSystemTray(QWidget *parent, const char *name)
00058   : KSystemTray( parent, name ),
00059     mParentVisible( true ),
00060     mPosOfMainWin( 0, 0 ),
00061     mDesktopOfMainWin( 0 ),
00062     mMode( GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ),
00063     mCount( 0 ),
00064     mNewMessagePopupId(-1),
00065     mPopupMenu(0)
00066 {
00067   setAlignment( AlignCenter );
00068   kdDebug(5006) << "Initting systray" << endl;
00069 
00070   mLastUpdate = time( 0 );
00071   mUpdateTimer = new QTimer( this, "systraytimer" );
00072   connect( mUpdateTimer, SIGNAL( timeout() ), SLOT( updateNewMessages() ) );
00073 
00074   mDefaultIcon = loadIcon( "kmail" );
00075   mLightIconImage = loadIcon( "kmaillight" ).convertToImage();
00076 
00077   setPixmap(mDefaultIcon);
00078 
00079   KMMainWidget * mainWidget = getKMMainWidget();
00080   if ( mainWidget ) {
00081     QWidget * mainWin = mainWidget->topLevelWidget();
00082     if ( mainWin ) {
00083       mDesktopOfMainWin = KWin::windowInfo( mainWin->winId(),
00084                                             NET::WMDesktop ).desktop();
00085       mPosOfMainWin = mainWin->pos();
00086     }
00087   }
00088 
00089   // register the applet with the kernel
00090   kmkernel->registerSystemTrayApplet( this );
00091 
00093   foldersChanged();
00094 
00095   connect( kmkernel->folderMgr(), SIGNAL(changed()), SLOT(foldersChanged()));
00096   connect( kmkernel->imapFolderMgr(), SIGNAL(changed()), SLOT(foldersChanged()));
00097   connect( kmkernel->dimapFolderMgr(), SIGNAL(changed()), SLOT(foldersChanged()));
00098   connect( kmkernel->searchFolderMgr(), SIGNAL(changed()), SLOT(foldersChanged()));
00099 
00100   connect( kmkernel->acctMgr(), SIGNAL( checkedMail( bool, bool, const QMap<QString, int> & ) ),
00101            SLOT( updateNewMessages() ) );
00102 }
00103 
00104 void KMSystemTray::buildPopupMenu()
00105 {
00106   // Delete any previously created popup menu
00107   delete mPopupMenu;
00108   mPopupMenu = 0;
00109 
00110   mPopupMenu = new KPopupMenu();
00111   KMMainWidget * mainWidget = getKMMainWidget();
00112   if ( !mainWidget )
00113     return;
00114 
00115   mPopupMenu->insertTitle(*(this->pixmap()), "KMail");
00116   KAction * action;
00117   if ( ( action = mainWidget->action("check_mail") ) )
00118     action->plug( mPopupMenu );
00119   if ( ( action = mainWidget->action("check_mail_in") ) )
00120     action->plug( mPopupMenu );
00121   mPopupMenu->insertSeparator();
00122   if ( ( action = mainWidget->action("new_message") ) )
00123     action->plug( mPopupMenu );
00124   if ( ( action = mainWidget->action("kmail_configure_kmail") ) )
00125     action->plug( mPopupMenu );
00126   mPopupMenu->insertSeparator();
00127 
00128   KMainWindow *mainWin = ::qt_cast<KMainWindow*>(getKMMainWidget()->topLevelWidget());
00129   if(mainWin)
00130     if ( ( action=mainWin->actionCollection()->action("file_quit") ) )
00131       action->plug( mPopupMenu );
00132 }
00133 
00134 KMSystemTray::~KMSystemTray()
00135 {
00136   // unregister the applet
00137   kmkernel->unregisterSystemTrayApplet( this );
00138 
00139   delete mPopupMenu;
00140   mPopupMenu = 0;
00141 }
00142 
00143 void KMSystemTray::setMode(int newMode)
00144 {
00145   if(newMode == mMode) return;
00146 
00147   kdDebug(5006) << "Setting systray mMode to " << newMode << endl;
00148   mMode = newMode;
00149 
00150   switch ( mMode ) {
00151   case GlobalSettings::EnumSystemTrayPolicy::ShowAlways:
00152     if ( isHidden() )
00153       show();
00154     break;
00155   case GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread:
00156     if ( mCount == 0 && !isHidden() )
00157       hide();
00158     else if ( mCount > 0 && isHidden() )
00159       show();
00160   default:
00161     kdDebug(5006) << k_funcinfo << " Unknown systray mode " << mMode << endl;
00162   }
00163 }
00164 
00165 int KMSystemTray::mode() const
00166 {
00167   return mMode;
00168 }
00169 
00175 void KMSystemTray::updateCount()
00176 {
00177   if(mCount != 0)
00178   {
00179     int oldPixmapWidth = pixmap()->size().width();
00180     int oldPixmapHeight = pixmap()->size().height();
00181 
00182     QString countString = QString::number( mCount );
00183     QFont countFont = KGlobalSettings::generalFont();
00184     countFont.setBold(true);
00185 
00186     // decrease the size of the font for the number of unread messages if the
00187     // number doesn't fit into the available space
00188     float countFontSize = countFont.pointSizeFloat();
00189     QFontMetrics qfm( countFont );
00190     int width = qfm.width( countString );
00191     if( width > oldPixmapWidth )
00192     {
00193       countFontSize *= float( oldPixmapWidth ) / float( width );
00194       countFont.setPointSizeFloat( countFontSize );
00195     }
00196 
00197     // Create an image which represents the number of unread messages
00198     // and which has a transparent background.
00199     // Unfortunately this required the following twisted code because for some
00200     // reason text that is drawn on a transparent pixmap is invisible
00201     // (apparently the alpha channel isn't changed when the text is drawn).
00202     // Therefore I have to draw the text on a solid background and then remove
00203     // the background by making it transparent with QPixmap::setMask. This
00204     // involves the slow createHeuristicMask() function (from the API docs:
00205     // "This function is slow because it involves transformation to a QImage,
00206     // non-trivial computations and a transformation back to a QBitmap."). Then
00207     // I have to convert the resulting QPixmap to a QImage in order to overlay
00208     // the light KMail icon with the number (because KIconEffect::overlay only
00209     // works with QImage). Finally the resulting QImage has to be converted
00210     // back to a QPixmap.
00211     // That's a lot of work for overlaying the KMail icon with the number of
00212     // unread messages, but every other approach I tried failed miserably.
00213     //                                                           IK, 2003-09-22
00214     QPixmap numberPixmap( oldPixmapWidth, oldPixmapHeight );
00215     numberPixmap.fill( Qt::white );
00216     QPainter p( &numberPixmap );
00217     p.setFont( countFont );
00218     p.setPen( Qt::blue );
00219     p.drawText( numberPixmap.rect(), Qt::AlignCenter, countString );
00220     numberPixmap.setMask( numberPixmap.createHeuristicMask() );
00221     QImage numberImage = numberPixmap.convertToImage();
00222 
00223     // Overlay the light KMail icon with the number image
00224     QImage iconWithNumberImage = mLightIconImage.copy();
00225     KIconEffect::overlay( iconWithNumberImage, numberImage );
00226 
00227     QPixmap iconWithNumber;
00228     iconWithNumber.convertFromImage( iconWithNumberImage );
00229     setPixmap( iconWithNumber );
00230   } else
00231   {
00232     setPixmap( mDefaultIcon );
00233   }
00234 }
00235 
00240 void KMSystemTray::foldersChanged()
00241 {
00246   mFoldersWithUnread.clear();
00247   mCount = 0;
00248 
00249   if ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ) {
00250     hide();
00251   }
00252 
00254   disconnect(this, SLOT(updateNewMessageNotification(KMFolder *)));
00255 
00256   QStringList folderNames;
00257   QValueList<QGuardedPtr<KMFolder> > folderList;
00258   kmkernel->folderMgr()->createFolderList(&folderNames, &folderList);
00259   kmkernel->imapFolderMgr()->createFolderList(&folderNames, &folderList);
00260   kmkernel->dimapFolderMgr()->createFolderList(&folderNames, &folderList);
00261   kmkernel->searchFolderMgr()->createFolderList(&folderNames, &folderList);
00262 
00263   QStringList::iterator strIt = folderNames.begin();
00264 
00265   for(QValueList<QGuardedPtr<KMFolder> >::iterator it = folderList.begin();
00266      it != folderList.end() && strIt != folderNames.end(); ++it, ++strIt)
00267   {
00268     KMFolder * currentFolder = *it;
00269     QString currentName = *strIt;
00270 
00271     if ( ((!currentFolder->isSystemFolder() || (currentFolder->name().lower() == "inbox")) ||
00272          (currentFolder->folderType() == KMFolderTypeImap)) &&
00273          !currentFolder->ignoreNewMail() )
00274     {
00276       connect(currentFolder, SIGNAL(numUnreadMsgsChanged(KMFolder *)),
00277               this, SLOT(updateNewMessageNotification(KMFolder *)));
00278 
00280       updateNewMessageNotification(currentFolder);
00281     }
00282   }
00283 }
00284 
00289 void KMSystemTray::mousePressEvent(QMouseEvent *e)
00290 {
00291   // switch to kmail on left mouse button
00292   if( e->button() == LeftButton )
00293   {
00294     if( mParentVisible && mainWindowIsOnCurrentDesktop() )
00295       hideKMail();
00296     else
00297       showKMail();
00298   }
00299 
00300   // open popup menu on right mouse button
00301   if( e->button() == RightButton )
00302   {
00303     mPopupFolders.clear();
00304     mPopupFolders.resize(mFoldersWithUnread.count());
00305 
00306     // Rebuild popup menu at click time to minimize race condition if
00307     // the base KMainWidget is closed.
00308     buildPopupMenu();
00309 
00310     if(mNewMessagePopupId != -1)
00311     {
00312       mPopupMenu->removeItem(mNewMessagePopupId);
00313     }
00314 
00315     if(mFoldersWithUnread.count() > 0)
00316     {
00317       KPopupMenu *newMessagesPopup = new KPopupMenu();
00318 
00319       QMap<QGuardedPtr<KMFolder>, int>::Iterator it = mFoldersWithUnread.begin();
00320       for(uint i=0; it != mFoldersWithUnread.end(); ++i)
00321       {
00322         kdDebug(5006) << "Adding folder" << endl;
00323         if(i > mPopupFolders.size()) mPopupFolders.resize(i * 2);
00324         mPopupFolders.insert(i, it.key());
00325         QString item = prettyName(it.key()) + "(" + QString::number(it.data()) + ")";
00326         newMessagesPopup->insertItem(item, this, SLOT(selectedAccount(int)), 0, i);
00327         ++it;
00328       }
00329 
00330       mNewMessagePopupId = mPopupMenu->insertItem(i18n("New Messages In"),
00331                                                   newMessagesPopup, mNewMessagePopupId, 3);
00332 
00333       kdDebug(5006) << "Folders added" << endl;
00334     }
00335 
00336     mPopupMenu->popup(e->globalPos());
00337   }
00338 
00339 }
00340 
00345 QString KMSystemTray::prettyName(KMFolder * fldr)
00346 {
00347   QString rvalue = fldr->label();
00348   if(fldr->folderType() == KMFolderTypeImap)
00349   {
00350     KMFolderImap * imap = dynamic_cast<KMFolderImap*> (fldr->storage());
00351     assert(imap);
00352 
00353     if((imap->account() != 0) &&
00354        (imap->account()->name() != 0) )
00355     {
00356       kdDebug(5006) << "IMAP folder, prepend label with type" << endl;
00357       rvalue = imap->account()->name() + "->" + rvalue;
00358     }
00359   }
00360 
00361   kdDebug(5006) << "Got label " << rvalue << endl;
00362 
00363   return rvalue;
00364 }
00365 
00366 
00367 bool KMSystemTray::mainWindowIsOnCurrentDesktop()
00368 {
00369   KMMainWidget * mainWidget = getKMMainWidget();
00370   if ( !mainWidget )
00371     return false;
00372 
00373   QWidget *mainWin = getKMMainWidget()->topLevelWidget();
00374   if ( !mainWin )
00375     return false;
00376 
00377   return KWin::windowInfo( mainWin->winId(),
00378                            NET::WMDesktop ).isOnCurrentDesktop();
00379 }
00380 
00385 void KMSystemTray::showKMail()
00386 {
00387   if (!getKMMainWidget())
00388     return;
00389   QWidget *mainWin = getKMMainWidget()->topLevelWidget();
00390   assert(mainWin);
00391   if(mainWin)
00392   {
00393     // switch to appropriate desktop
00394     if ( mDesktopOfMainWin != NET::OnAllDesktops )
00395       KWin::setCurrentDesktop( mDesktopOfMainWin );
00396     if ( !mParentVisible ) {
00397       if ( mDesktopOfMainWin == NET::OnAllDesktops )
00398         KWin::setOnAllDesktops( mainWin->winId(), true );
00399       mainWin->move( mPosOfMainWin );
00400       mainWin->show();
00401     }
00402     KWin::activateWindow( mainWin->winId() );
00403     mParentVisible = true;
00404   }
00405 
00406   //Fake that the folders have changed so that the icon status is correct
00407   foldersChanged();
00408 }
00409 
00410 void KMSystemTray::hideKMail()
00411 {
00412   if (!getKMMainWidget())
00413     return;
00414   QWidget *mainWin = getKMMainWidget()->topLevelWidget();
00415   assert(mainWin);
00416   if(mainWin)
00417   {
00418     mDesktopOfMainWin = KWin::windowInfo( mainWin->winId(),
00419                                           NET::WMDesktop ).desktop();
00420     mPosOfMainWin = mainWin->pos();
00421     // iconifying is unnecessary, but it looks cooler
00422     KWin::iconifyWindow( mainWin->winId() );
00423     mainWin->hide();
00424     mParentVisible = false;
00425   }
00426 }
00427 
00431 KMMainWidget * KMSystemTray::getKMMainWidget()
00432 {
00433   QWidgetList *l = kapp->topLevelWidgets();
00434   QWidgetListIt it( *l );
00435   QWidget *wid;
00436 
00437   while ( (wid = it.current()) != 0 ) {
00438     ++it;
00439     QObjectList *l2 = wid->topLevelWidget()->queryList("KMMainWidget");
00440     if (l2 && l2->first())
00441     {
00442       KMMainWidget* kmmw = dynamic_cast<KMMainWidget *>(l2->first());
00443       assert (kmmw);
00444       delete l2;
00445       delete l;
00446       return kmmw;
00447     }
00448     delete l2;
00449   }
00450   delete l;
00451   return 0;
00452 }
00453 
00460 void KMSystemTray::updateNewMessageNotification(KMFolder * fldr)
00461 {
00462   //We don't want to count messages from search folders as they
00463   //  already counted as part of their original folders
00464   if( !fldr ||
00465       fldr->folderType() == KMFolderTypeSearch )
00466   {
00467     // kdDebug(5006) << "Null or a search folder, can't mess with that" << endl;
00468     return;
00469   }
00470 
00471   mPendingUpdates[ fldr ] = true;
00472   if ( time( 0 ) - mLastUpdate > 2 ) {
00473     mUpdateTimer->stop();
00474     updateNewMessages();
00475   }
00476   else {
00477     mUpdateTimer->start(150, true);
00478   }
00479 }
00480 
00481 void KMSystemTray::updateNewMessages()
00482 {
00483   for ( QMap<QGuardedPtr<KMFolder>, bool>::Iterator it = mPendingUpdates.begin();
00484         it != mPendingUpdates.end(); ++it)
00485   {
00486   KMFolder *fldr = it.key();
00487   if ( !fldr ) // deleted folder
00488     continue;
00489 
00491   int unread = fldr->countUnread();
00492 
00493   QMap<QGuardedPtr<KMFolder>, int>::Iterator it =
00494       mFoldersWithUnread.find(fldr);
00495   bool unmapped = (it == mFoldersWithUnread.end());
00496 
00499   if(unmapped) mCount += unread;
00500   /* Otherwise, get the difference between the numUnread in the folder and
00501    * our last known version, and adjust mCount with that difference */
00502   else
00503   {
00504     int diff = unread - it.data();
00505     mCount += diff;
00506   }
00507 
00508   if(unread > 0)
00509   {
00511     mFoldersWithUnread.insert(fldr, unread);
00512     //kdDebug(5006) << "There are now " << mFoldersWithUnread.count() << " folders with unread" << endl;
00513   }
00514 
00520   if(unmapped)
00521   {
00523     if(unread == 0) continue;
00524 
00526     if ( ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread )
00527          && isHidden() ) {
00528       show();
00529     }
00530 
00531   } else
00532   {
00533 
00534     if(unread == 0)
00535     {
00536       kdDebug(5006) << "Removing folder from internal store " << fldr->name() << endl;
00537 
00539       mFoldersWithUnread.remove(fldr);
00540 
00542       if(mFoldersWithUnread.count() == 0)
00543       {
00544         mPopupFolders.clear();
00545         disconnect(this, SLOT(selectedAccount(int)));
00546 
00547         mCount = 0;
00548 
00549         if ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ) {
00550           hide();
00551         }
00552       }
00553     }
00554   }
00555 
00556   }
00557   mPendingUpdates.clear();
00558   updateCount();
00559 
00561   QToolTip::remove(this);
00562   QToolTip::add(this, i18n("There is 1 unread message.",
00563                            "There are %n unread messages.",
00564                            mCount));
00565 
00566   mLastUpdate = time( 0 );
00567 }
00568 
00574 void KMSystemTray::selectedAccount(int id)
00575 {
00576   showKMail();
00577 
00578   KMMainWidget * mainWidget = getKMMainWidget();
00579   if (!mainWidget)
00580   {
00581     kmkernel->openReader();
00582     mainWidget = getKMMainWidget();
00583   }
00584 
00585   assert(mainWidget);
00586 
00588   KMFolder * fldr = mPopupFolders.at(id);
00589   if(!fldr) return;
00590   KMFolderTree * ft = mainWidget->folderTree();
00591   if(!ft) return;
00592   QListViewItem * fldrIdx = ft->indexOfFolder(fldr);
00593   if(!fldrIdx) return;
00594 
00595   ft->setCurrentItem(fldrIdx);
00596   ft->selectCurrentFolder();
00597 }
00598 
00599 #include "kmsystemtray.moc"
KDE Logo
This file is part of the documentation for kmail Library Version 3.3.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Wed Mar 23 22:43:53 2005 by doxygen 1.3.9.1 written by Dimitri van Heesch, © 1997-2003