kmail

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 "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 
00112   mPopupMenu = new KPopupMenu();
00113   KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
00114   if ( !mainWidget )
00115     return;
00116 
00117   mPopupMenu->insertTitle(*(this->pixmap()), "KMail");
00118   KAction * action;
00119   if ( ( action = mainWidget->action("check_mail") ) )
00120     action->plug( mPopupMenu );
00121   if ( ( action = mainWidget->action("check_mail_in") ) )
00122     action->plug( mPopupMenu );
00123   if ( ( action = mainWidget->action("send_queued") ) )
00124     action->plug( mPopupMenu );
00125   if ( ( action = mainWidget->action("send_queued_via") ) )
00126     action->plug( mPopupMenu );
00127   mPopupMenu->insertSeparator();
00128   if ( ( action = mainWidget->action("new_message") ) )
00129     action->plug( mPopupMenu );
00130   if ( ( action = mainWidget->action("kmail_configure_kmail") ) )
00131     action->plug( mPopupMenu );
00132   mPopupMenu->insertSeparator();
00133 
00134   KMainWindow *mainWin = ::qt_cast<KMainWindow*>(kmkernel->getKMMainWidget()->topLevelWidget());
00135   if(mainWin)
00136     if ( ( action=mainWin->actionCollection()->action("file_quit") ) )
00137       action->plug( mPopupMenu );
00138 }
00139 
00140 KMSystemTray::~KMSystemTray()
00141 {
00142   // unregister the applet
00143   kmkernel->unregisterSystemTrayApplet( this );
00144 
00145   delete mPopupMenu;
00146   mPopupMenu = 0;
00147 }
00148 
00149 void KMSystemTray::setMode(int newMode)
00150 {
00151   if(newMode == mMode) return;
00152 
00153   kdDebug(5006) << "Setting systray mMode to " << newMode << endl;
00154   mMode = newMode;
00155 
00156   switch ( mMode ) {
00157   case GlobalSettings::EnumSystemTrayPolicy::ShowAlways:
00158     if ( isHidden() )
00159       show();
00160     break;
00161   case GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread:
00162     if ( mCount == 0 && !isHidden() )
00163       hide();
00164     else if ( mCount > 0 && isHidden() )
00165       show();
00166     break;
00167   default:
00168     kdDebug(5006) << k_funcinfo << " Unknown systray mode " << mMode << endl;
00169   }
00170 }
00171 
00172 int KMSystemTray::mode() const
00173 {
00174   return mMode;
00175 }
00176 
00182 void KMSystemTray::updateCount()
00183 {
00184   if(mCount != 0)
00185   {
00186     int oldPixmapWidth = pixmap()->size().width();
00187     int oldPixmapHeight = pixmap()->size().height();
00188 
00189     QString countString = QString::number( mCount );
00190     QFont countFont = KGlobalSettings::generalFont();
00191     countFont.setBold(true);
00192 
00193     // decrease the size of the font for the number of unread messages if the
00194     // number doesn't fit into the available space
00195     float countFontSize = countFont.pointSizeFloat();
00196     QFontMetrics qfm( countFont );
00197     int width = qfm.width( countString );
00198     if( width > oldPixmapWidth )
00199     {
00200       countFontSize *= float( oldPixmapWidth ) / float( width );
00201       countFont.setPointSizeFloat( countFontSize );
00202     }
00203 
00204     // Create an image which represents the number of unread messages
00205     // and which has a transparent background.
00206     // Unfortunately this required the following twisted code because for some
00207     // reason text that is drawn on a transparent pixmap is invisible
00208     // (apparently the alpha channel isn't changed when the text is drawn).
00209     // Therefore I have to draw the text on a solid background and then remove
00210     // the background by making it transparent with QPixmap::setMask. This
00211     // involves the slow createHeuristicMask() function (from the API docs:
00212     // "This function is slow because it involves transformation to a QImage,
00213     // non-trivial computations and a transformation back to a QBitmap."). Then
00214     // I have to convert the resulting QPixmap to a QImage in order to overlay
00215     // the light KMail icon with the number (because KIconEffect::overlay only
00216     // works with QImage). Finally the resulting QImage has to be converted
00217     // back to a QPixmap.
00218     // That's a lot of work for overlaying the KMail icon with the number of
00219     // unread messages, but every other approach I tried failed miserably.
00220     //                                                           IK, 2003-09-22
00221     QPixmap numberPixmap( oldPixmapWidth, oldPixmapHeight );
00222     numberPixmap.fill( Qt::white );
00223     QPainter p( &numberPixmap );
00224     p.setFont( countFont );
00225     p.setPen( Qt::blue );
00226     p.drawText( numberPixmap.rect(), Qt::AlignCenter, countString );
00227     numberPixmap.setMask( numberPixmap.createHeuristicMask() );
00228     QImage numberImage = numberPixmap.convertToImage();
00229 
00230     // Overlay the light KMail icon with the number image
00231     QImage iconWithNumberImage = mLightIconImage.copy();
00232     KIconEffect::overlay( iconWithNumberImage, numberImage );
00233 
00234     QPixmap iconWithNumber;
00235     iconWithNumber.convertFromImage( iconWithNumberImage );
00236     setPixmap( iconWithNumber );
00237   } else
00238   {
00239     setPixmap( mDefaultIcon );
00240   }
00241 }
00242 
00247 void KMSystemTray::foldersChanged()
00248 {
00253   mFoldersWithUnread.clear();
00254   mCount = 0;
00255 
00256   if ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ) {
00257     hide();
00258   }
00259 
00261   disconnect(this, SLOT(updateNewMessageNotification(KMFolder *)));
00262 
00263   QStringList folderNames;
00264   QValueList<QGuardedPtr<KMFolder> > folderList;
00265   kmkernel->folderMgr()->createFolderList(&folderNames, &folderList);
00266   kmkernel->imapFolderMgr()->createFolderList(&folderNames, &folderList);
00267   kmkernel->dimapFolderMgr()->createFolderList(&folderNames, &folderList);
00268   kmkernel->searchFolderMgr()->createFolderList(&folderNames, &folderList);
00269 
00270   QStringList::iterator strIt = folderNames.begin();
00271 
00272   for(QValueList<QGuardedPtr<KMFolder> >::iterator it = folderList.begin();
00273      it != folderList.end() && strIt != folderNames.end(); ++it, ++strIt)
00274   {
00275     KMFolder * currentFolder = *it;
00276     QString currentName = *strIt;
00277 
00278     if ( ((!currentFolder->isSystemFolder() || (currentFolder->name().lower() == "inbox")) ||
00279          (currentFolder->folderType() == KMFolderTypeImap)) &&
00280          !currentFolder->ignoreNewMail() )
00281     {
00283       connect(currentFolder, SIGNAL(numUnreadMsgsChanged(KMFolder *)),
00284               this, SLOT(updateNewMessageNotification(KMFolder *)));
00285 
00287       updateNewMessageNotification(currentFolder);
00288     }
00289     else {
00290         disconnect(currentFolder, SIGNAL(numUnreadMsgsChanged(KMFolder *)), this, SLOT(updateNewMessageNotification(KMFolder *)) );
00291     }
00292   }
00293 }
00294 
00299 void KMSystemTray::mousePressEvent(QMouseEvent *e)
00300 {
00301   // switch to kmail on left mouse button
00302   if( e->button() == LeftButton )
00303   {
00304     if( mParentVisible && mainWindowIsOnCurrentDesktop() )
00305       hideKMail();
00306     else
00307       showKMail();
00308   }
00309 
00310   // open popup menu on right mouse button
00311   if( e->button() == RightButton )
00312   {
00313     mPopupFolders.clear();
00314     mPopupFolders.reserve( mFoldersWithUnread.count() );
00315 
00316     // Rebuild popup menu at click time to minimize race condition if
00317     // the base KMainWidget is closed.
00318     buildPopupMenu();
00319 
00320     if(mNewMessagePopupId != -1)
00321     {
00322       mPopupMenu->removeItem(mNewMessagePopupId);
00323     }
00324 
00325     if(mFoldersWithUnread.count() > 0)
00326     {
00327       KPopupMenu *newMessagesPopup = new KPopupMenu();
00328 
00329       QMap<QGuardedPtr<KMFolder>, int>::Iterator it = mFoldersWithUnread.begin();
00330       for(uint i=0; it != mFoldersWithUnread.end(); ++i)
00331       {
00332         kdDebug(5006) << "Adding folder" << endl;
00333         mPopupFolders.append( it.key() );
00334         QString item = prettyName(it.key()) + " (" + QString::number(it.data()) + ")";
00335         newMessagesPopup->insertItem(item, this, SLOT(selectedAccount(int)), 0, i);
00336         ++it;
00337       }
00338 
00339       mNewMessagePopupId = mPopupMenu->insertItem(i18n("New Messages In"),
00340                                                   newMessagesPopup, mNewMessagePopupId, 3);
00341 
00342       kdDebug(5006) << "Folders added" << endl;
00343     }
00344 
00345     mPopupMenu->popup(e->globalPos());
00346   }
00347 
00348 }
00349 
00354 QString KMSystemTray::prettyName(KMFolder * fldr)
00355 {
00356   QString rvalue = fldr->label();
00357   if(fldr->folderType() == KMFolderTypeImap)
00358   {
00359     KMFolderImap * imap = dynamic_cast<KMFolderImap*> (fldr->storage());
00360     assert(imap);
00361 
00362     if((imap->account() != 0) &&
00363        (imap->account()->name() != 0) )
00364     {
00365       kdDebug(5006) << "IMAP folder, prepend label with type" << endl;
00366       rvalue = imap->account()->name() + "->" + rvalue;
00367     }
00368   }
00369 
00370   kdDebug(5006) << "Got label " << rvalue << endl;
00371 
00372   return rvalue;
00373 }
00374 
00375 
00376 bool KMSystemTray::mainWindowIsOnCurrentDesktop()
00377 {
00378   KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
00379   if ( !mainWidget )
00380     return false;
00381 
00382   QWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget();
00383   if ( !mainWin )
00384     return false;
00385 
00386   return KWin::windowInfo( mainWin->winId(),
00387                            NET::WMDesktop ).isOnCurrentDesktop();
00388 }
00389 
00394 void KMSystemTray::showKMail()
00395 {
00396   if (!kmkernel->getKMMainWidget())
00397     return;
00398   QWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget();
00399   assert(mainWin);
00400   if(mainWin)
00401   {
00402     KWin::WindowInfo cur =  KWin::windowInfo( mainWin->winId(), NET::WMDesktop );
00403     if ( cur.valid() ) mDesktopOfMainWin = cur.desktop();
00404     // switch to appropriate desktop
00405     if ( mDesktopOfMainWin != NET::OnAllDesktops )
00406       KWin::setCurrentDesktop( mDesktopOfMainWin );
00407     if ( !mParentVisible ) {
00408       if ( mDesktopOfMainWin == NET::OnAllDesktops )
00409         KWin::setOnAllDesktops( mainWin->winId(), true );
00410       mainWin->move( mPosOfMainWin );
00411       mainWin->show();
00412     }
00413     KWin::activateWindow( mainWin->winId() );
00414     mParentVisible = true;
00415   }
00416   kmkernel->raise();
00417 
00418   //Fake that the folders have changed so that the icon status is correct
00419   foldersChanged();
00420 }
00421 
00422 void KMSystemTray::hideKMail()
00423 {
00424   if (!kmkernel->getKMMainWidget())
00425     return;
00426   QWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget();
00427   assert(mainWin);
00428   if(mainWin)
00429   {
00430     mDesktopOfMainWin = KWin::windowInfo( mainWin->winId(),
00431                                           NET::WMDesktop ).desktop();
00432     mPosOfMainWin = mainWin->pos();
00433     // iconifying is unnecessary, but it looks cooler
00434     KWin::iconifyWindow( mainWin->winId() );
00435     mainWin->hide();
00436     mParentVisible = false;
00437   }
00438 }
00439 
00446 void KMSystemTray::updateNewMessageNotification(KMFolder * fldr)
00447 {
00448   //We don't want to count messages from search folders as they
00449   //  already counted as part of their original folders
00450   if( !fldr ||
00451       fldr->folderType() == KMFolderTypeSearch )
00452   {
00453     // kdDebug(5006) << "Null or a search folder, can't mess with that" << endl;
00454     return;
00455   }
00456 
00457   mPendingUpdates[ fldr ] = true;
00458   if ( time( 0 ) - mLastUpdate > 2 ) {
00459     mUpdateTimer->stop();
00460     updateNewMessages();
00461   }
00462   else {
00463     mUpdateTimer->start(150, true);
00464   }
00465 }
00466 
00467 void KMSystemTray::updateNewMessages()
00468 {
00469   for ( QMap<QGuardedPtr<KMFolder>, bool>::Iterator it_pendingUpdates = mPendingUpdates.begin();
00470         it_pendingUpdates != mPendingUpdates.end(); ++it_pendingUpdates)
00471   {
00472   KMFolder *fldr = it_pendingUpdates.key();
00473   if ( !fldr ) // deleted folder
00474     continue;
00475 
00477   int unread = fldr->countUnread();
00478 
00479   QMap<QGuardedPtr<KMFolder>, int>::Iterator it =
00480       mFoldersWithUnread.find(fldr);
00481   bool unmapped = (it == mFoldersWithUnread.end());
00482 
00485   if(unmapped) mCount += unread;
00486   /* Otherwise, get the difference between the numUnread in the folder and
00487    * our last known version, and adjust mCount with that difference */
00488   else
00489   {
00490     int diff = unread - it.data();
00491     mCount += diff;
00492   }
00493 
00494   if(unread > 0)
00495   {
00497     mFoldersWithUnread.insert(fldr, unread);
00498     //kdDebug(5006) << "There are now " << mFoldersWithUnread.count() << " folders with unread" << endl;
00499   }
00500 
00506   if(unmapped)
00507   {
00509     if(unread == 0) continue;
00510 
00512     if ( ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread )
00513          && isHidden() ) {
00514       show();
00515     }
00516 
00517   } else
00518   {
00519 
00520     if(unread == 0)
00521     {
00522       kdDebug(5006) << "Removing folder from internal store " << fldr->name() << endl;
00523 
00525       mFoldersWithUnread.remove(fldr);
00526 
00528       if(mFoldersWithUnread.count() == 0)
00529       {
00530         mPopupFolders.clear();
00531         disconnect(this, SLOT(selectedAccount(int)));
00532 
00533         mCount = 0;
00534 
00535         if ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ) {
00536           hide();
00537         }
00538       }
00539     }
00540   }
00541 
00542   }
00543   mPendingUpdates.clear();
00544   updateCount();
00545 
00547   QToolTip::remove(this);
00548   QToolTip::add(this, mCount == 0 ?
00549               i18n("There are no unread messages")
00550               : i18n("There is 1 unread message.",
00551                              "There are %n unread messages.",
00552                            mCount));
00553 
00554   mLastUpdate = time( 0 );
00555 }
00556 
00562 void KMSystemTray::selectedAccount(int id)
00563 {
00564   showKMail();
00565 
00566   KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
00567   if (!mainWidget)
00568   {
00569     kmkernel->openReader();
00570     mainWidget = kmkernel->getKMMainWidget();
00571   }
00572 
00573   assert(mainWidget);
00574 
00576   KMFolder * fldr = mPopupFolders.at(id);
00577   if(!fldr) return;
00578   KMFolderTree * ft = mainWidget->folderTree();
00579   if(!ft) return;
00580   QListViewItem * fldrIdx = ft->indexOfFolder(fldr);
00581   if(!fldrIdx) return;
00582 
00583   ft->setCurrentItem(fldrIdx);
00584   ft->selectCurrentFolder();
00585 }
00586 
00587 bool KMSystemTray::hasUnreadMail() const
00588 {
00589   return ( mCount != 0 );
00590 }
00591 
00592 #include "kmsystemtray.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys