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 <tdeapplication.h>
00032 #include <tdemainwindow.h>
00033 #include <tdeglobalsettings.h>
00034 #include <kiconloader.h>
00035 #include <kiconeffect.h>
00036 #include <twin.h>
00037 #include <kdebug.h>
00038 #include <tdepopupmenu.h>
00039 
00040 #include <tqpainter.h>
00041 #include <tqbitmap.h>
00042 #include <tqtooltip.h>
00043 #include <tqwidgetlist.h>
00044 #include <tqobjectlist.h>
00045 
00046 #include <math.h>
00047 #include <assert.h>
00048 
00060 KMSystemTray::KMSystemTray(TQWidget *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 TQTimer( this, "systraytimer" );
00075   connect( mUpdateTimer, TQT_SIGNAL( timeout() ), TQT_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     TQWidget * 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(), TQT_SIGNAL(changed()), TQT_SLOT(foldersChanged()));
00099   connect( kmkernel->imapFolderMgr(), TQT_SIGNAL(changed()), TQT_SLOT(foldersChanged()));
00100   connect( kmkernel->dimapFolderMgr(), TQT_SIGNAL(changed()), TQT_SLOT(foldersChanged()));
00101   connect( kmkernel->searchFolderMgr(), TQT_SIGNAL(changed()), TQT_SLOT(foldersChanged()));
00102 
00103   connect( kmkernel->acctMgr(), TQT_SIGNAL( checkedMail( bool, bool, const TQMap<TQString, int> & ) ),
00104            TQT_SLOT( updateNewMessages() ) );
00105 
00106   connect( this, TQT_SIGNAL( quitSelected() ), TQT_SLOT( tray_quit() ) );
00107 }
00108 
00109 void KMSystemTray::buildPopupMenu()
00110 {
00111   // Delete any previously created popup menu
00112   delete mPopupMenu;
00113 
00114   mPopupMenu = new TDEPopupMenu();
00115   KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
00116   if ( !mainWidget )
00117     return;
00118 
00119   mPopupMenu->insertTitle(*(this->pixmap()), "KMail");
00120   TDEAction * action;
00121   if ( ( action = mainWidget->action("check_mail") ) )
00122     action->plug( mPopupMenu );
00123   if ( ( action = mainWidget->action("check_mail_in") ) )
00124     action->plug( mPopupMenu );
00125   if ( ( action = mainWidget->action("send_queued") ) )
00126     action->plug( mPopupMenu );
00127   if ( ( action = mainWidget->action("send_queued_via") ) )
00128     action->plug( mPopupMenu );
00129   mPopupMenu->insertSeparator();
00130   if ( ( action = mainWidget->action("new_message") ) )
00131     action->plug( mPopupMenu );
00132   if ( ( action = mainWidget->action("kmail_configure_kmail") ) )
00133     action->plug( mPopupMenu );
00134   mPopupMenu->insertSeparator();
00135 
00136   mPopupMenu->insertItem( SmallIcon("system-log-out"), i18n("&Quit"), this, TQT_SLOT(maybeQuit()) );
00137 }
00138 
00139 void KMSystemTray::tray_quit()
00140 {
00141   // Quit all of KMail
00142   kapp->quit();
00143 }
00144 
00145 KMSystemTray::~KMSystemTray()
00146 {
00147   // unregister the applet
00148   kmkernel->unregisterSystemTrayApplet( this );
00149 
00150   delete mPopupMenu;
00151   mPopupMenu = 0;
00152 }
00153 
00154 void KMSystemTray::setMode(int newMode)
00155 {
00156   if(newMode == mMode) return;
00157 
00158   kdDebug(5006) << "Setting systray mMode to " << newMode << endl;
00159   mMode = newMode;
00160 
00161   switch ( mMode ) {
00162   case GlobalSettings::EnumSystemTrayPolicy::ShowAlways:
00163     if ( isHidden() )
00164       show();
00165     break;
00166   case GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread:
00167     if ( mCount == 0 && !isHidden() )
00168       hide();
00169     else if ( mCount > 0 && isHidden() )
00170       show();
00171     break;
00172   default:
00173     kdDebug(5006) << k_funcinfo << " Unknown systray mode " << mMode << endl;
00174   }
00175 }
00176 
00177 int KMSystemTray::mode() const
00178 {
00179   return mMode;
00180 }
00181 
00182 void KMSystemTray::resizeEvent(TQResizeEvent *)
00183 {
00184   updateCount();
00185 }
00186 
00192 void KMSystemTray::updateCount()
00193 {
00194   if(mCount != 0)
00195   {
00196     int oldPixmapWidth = pixmap()->size().width();
00197     int oldPixmapHeight = pixmap()->size().height();
00198 
00199     TQString countString = TQString::number( mCount );
00200     TQFont countFont = TDEGlobalSettings::generalFont();
00201     countFont.setBold(true);
00202 
00203     // increase the size of the font for the number of unread messages if the
00204     // icon size is less than 22 pixels
00205     // see bug 1251
00206     int realIconHeight = height();
00207     if (realIconHeight < 22) {
00208       countFont.setPointSizeFloat( countFont.pointSizeFloat() * 2.0 );
00209     }
00210 
00211     // decrease the size of the font for the number of unread messages if the
00212     // number doesn't fit into the available space
00213     float countFontSize = countFont.pointSizeFloat();
00214     TQFontMetrics qfm( countFont );
00215     int width = qfm.width( countString );
00216     if( width > oldPixmapWidth )
00217     {
00218       countFontSize *= float( oldPixmapWidth ) / float( width );
00219       countFont.setPointSizeFloat( countFontSize );
00220     }
00221 
00222     // Create an image which represents the number of unread messages
00223     // and which has a transparent background.
00224     // Unfortunately this required the following twisted code because for some
00225     // reason text that is drawn on a transparent pixmap is invisible
00226     // (apparently the alpha channel isn't changed when the text is drawn).
00227     // Therefore I have to draw the text on a solid background and then remove
00228     // the background by making it transparent with TQPixmap::setMask. This
00229     // involves the slow createHeuristicMask() function (from the API docs:
00230     // "This function is slow because it involves transformation to a TQImage,
00231     // non-trivial computations and a transformation back to a TQBitmap."). Then
00232     // I have to convert the resulting TQPixmap to a TQImage in order to overlay
00233     // the light KMail icon with the number (because TDEIconEffect::overlay only
00234     // works with TQImage). Finally the resulting TQImage has to be converted
00235     // back to a TQPixmap.
00236     // That's a lot of work for overlaying the KMail icon with the number of
00237     // unread messages, but every other approach I tried failed miserably.
00238     //                                                           IK, 2003-09-22
00239     TQPixmap numberPixmap( oldPixmapWidth, oldPixmapHeight );
00240     numberPixmap.fill( TQt::white );
00241     TQPainter p( &numberPixmap );
00242     p.setFont( countFont );
00243     p.setPen( TQt::blue );
00244     p.drawText( numberPixmap.rect(), TQt::AlignCenter, countString );
00245     numberPixmap.setMask( numberPixmap.createHeuristicMask() );
00246     TQImage numberImage = numberPixmap.convertToImage();
00247 
00248     // Overlay the light KMail icon with the number image
00249     TQImage iconWithNumberImage = mLightIconImage.copy();
00250     TDEIconEffect::overlay( iconWithNumberImage, numberImage );
00251 
00252     TQPixmap iconWithNumber;
00253     iconWithNumber.convertFromImage( iconWithNumberImage );
00254     setPixmap( iconWithNumber );
00255   } else
00256   {
00257     setPixmap( mDefaultIcon );
00258   }
00259 }
00260 
00265 void KMSystemTray::foldersChanged()
00266 {
00271   mFoldersWithUnread.clear();
00272   mCount = 0;
00273 
00274   if ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ) {
00275     hide();
00276   }
00277 
00279   disconnect(this, TQT_SLOT(updateNewMessageNotification(KMFolder *)));
00280 
00281   TQStringList folderNames;
00282   TQValueList<TQGuardedPtr<KMFolder> > folderList;
00283   kmkernel->folderMgr()->createFolderList(&folderNames, &folderList);
00284   kmkernel->imapFolderMgr()->createFolderList(&folderNames, &folderList);
00285   kmkernel->dimapFolderMgr()->createFolderList(&folderNames, &folderList);
00286   kmkernel->searchFolderMgr()->createFolderList(&folderNames, &folderList);
00287 
00288   TQStringList::iterator strIt = folderNames.begin();
00289 
00290   for(TQValueList<TQGuardedPtr<KMFolder> >::iterator it = folderList.begin();
00291      it != folderList.end() && strIt != folderNames.end(); ++it, ++strIt)
00292   {
00293     KMFolder * currentFolder = *it;
00294     TQString currentName = *strIt;
00295 
00296     if ( ((!currentFolder->isSystemFolder() || (currentFolder->name().lower() == "inbox")) ||
00297          (currentFolder->folderType() == KMFolderTypeImap)) &&
00298          !currentFolder->ignoreNewMail() )
00299     {
00301       connect(currentFolder, TQT_SIGNAL(numUnreadMsgsChanged(KMFolder *)),
00302               this, TQT_SLOT(updateNewMessageNotification(KMFolder *)));
00303 
00305       updateNewMessageNotification(currentFolder);
00306     }
00307     else {
00308         disconnect(currentFolder, TQT_SIGNAL(numUnreadMsgsChanged(KMFolder *)), this, TQT_SLOT(updateNewMessageNotification(KMFolder *)) );
00309     }
00310   }
00311 }
00312 
00317 void KMSystemTray::mousePressEvent(TQMouseEvent *e)
00318 {
00319   // switch to kmail on left mouse button
00320   if( e->button() == Qt::LeftButton )
00321   {
00322     if( mParentVisible && mainWindowIsOnCurrentDesktop() )
00323       hideKMail();
00324     else
00325       showKMail();
00326   }
00327 
00328   // open popup menu on right mouse button
00329   if( e->button() == Qt::RightButton )
00330   {
00331     mPopupFolders.clear();
00332     mPopupFolders.reserve( mFoldersWithUnread.count() );
00333 
00334     // Rebuild popup menu at click time to minimize race condition if
00335     // the base TDEMainWidget is closed.
00336     buildPopupMenu();
00337 
00338     if(mNewMessagePopupId != -1)
00339     {
00340       mPopupMenu->removeItem(mNewMessagePopupId);
00341     }
00342 
00343     if(mFoldersWithUnread.count() > 0)
00344     {
00345       TDEPopupMenu *newMessagesPopup = new TDEPopupMenu();
00346 
00347       TQMap<TQGuardedPtr<KMFolder>, int>::Iterator it = mFoldersWithUnread.begin();
00348       for(uint i=0; it != mFoldersWithUnread.end(); ++i)
00349       {
00350         kdDebug(5006) << "Adding folder" << endl;
00351         mPopupFolders.append( it.key() );
00352         TQString item = prettyName(it.key()) + " (" + TQString::number(it.data()) + ")";
00353         newMessagesPopup->insertItem(item, this, TQT_SLOT(selectedAccount(int)), 0, i);
00354         ++it;
00355       }
00356 
00357       mNewMessagePopupId = mPopupMenu->insertItem(i18n("New Messages In"),
00358                                                   newMessagesPopup, mNewMessagePopupId, 3);
00359 
00360       kdDebug(5006) << "Folders added" << endl;
00361     }
00362 
00363     mPopupMenu->popup(e->globalPos());
00364   }
00365 
00366 }
00367 
00372 TQString KMSystemTray::prettyName(KMFolder * fldr)
00373 {
00374   TQString rvalue = fldr->label();
00375   if(fldr->folderType() == KMFolderTypeImap)
00376   {
00377     KMFolderImap * imap = dynamic_cast<KMFolderImap*> (fldr->storage());
00378     assert(imap);
00379 
00380     if((imap->account() != 0) &&
00381        (imap->account()->name() != 0) )
00382     {
00383       kdDebug(5006) << "IMAP folder, prepend label with type" << endl;
00384       rvalue = imap->account()->name() + "->" + rvalue;
00385     }
00386   }
00387 
00388   kdDebug(5006) << "Got label " << rvalue << endl;
00389 
00390   return rvalue;
00391 }
00392 
00393 
00394 bool KMSystemTray::mainWindowIsOnCurrentDesktop()
00395 {
00396   KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
00397   if ( !mainWidget )
00398     return false;
00399 
00400   TQWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget();
00401   if ( !mainWin )
00402     return false;
00403 
00404   return KWin::windowInfo( mainWin->winId(),
00405                            NET::WMDesktop ).isOnCurrentDesktop();
00406 }
00407 
00412 void KMSystemTray::showKMail()
00413 {
00414   if (!kmkernel->getKMMainWidget())
00415     return;
00416   TQWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget();
00417   assert(mainWin);
00418   if(mainWin)
00419   {
00420     KWin::WindowInfo cur =  KWin::windowInfo( mainWin->winId(), NET::WMDesktop );
00421     if ( cur.valid() ) mDesktopOfMainWin = cur.desktop();
00422     // switch to appropriate desktop
00423     if ( mDesktopOfMainWin != NET::OnAllDesktops )
00424       KWin::setCurrentDesktop( mDesktopOfMainWin );
00425     if ( !mParentVisible ) {
00426       if ( mDesktopOfMainWin == NET::OnAllDesktops )
00427         KWin::setOnAllDesktops( mainWin->winId(), true );
00428       mainWin->move( mPosOfMainWin );
00429       mainWin->show();
00430     }
00431     KWin::activateWindow( mainWin->winId() );
00432     mParentVisible = true;
00433   }
00434   kmkernel->raise();
00435 
00436   //Fake that the folders have changed so that the icon status is correct
00437   foldersChanged();
00438 }
00439 
00440 void KMSystemTray::hideKMail()
00441 {
00442   if (!kmkernel->getKMMainWidget())
00443     return;
00444   TQWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget();
00445   assert(mainWin);
00446   if(mainWin)
00447   {
00448     mDesktopOfMainWin = KWin::windowInfo( mainWin->winId(),
00449                                           NET::WMDesktop ).desktop();
00450     mPosOfMainWin = mainWin->pos();
00451     // iconifying is unnecessary, but it looks cooler
00452     KWin::iconifyWindow( mainWin->winId() );
00453     mainWin->hide();
00454     mParentVisible = false;
00455   }
00456 }
00457 
00464 void KMSystemTray::updateNewMessageNotification(KMFolder * fldr)
00465 {
00466   //We don't want to count messages from search folders as they
00467   //  already counted as part of their original folders
00468   if( !fldr ||
00469       fldr->folderType() == KMFolderTypeSearch )
00470   {
00471     // kdDebug(5006) << "Null or a search folder, can't mess with that" << endl;
00472     return;
00473   }
00474 
00475   mPendingUpdates[ fldr ] = true;
00476   if ( time( 0 ) - mLastUpdate > 2 ) {
00477     mUpdateTimer->stop();
00478     updateNewMessages();
00479   }
00480   else {
00481     mUpdateTimer->start(150, true);
00482   }
00483 }
00484 
00485 void KMSystemTray::updateNewMessages()
00486 {
00487   for ( TQMap<TQGuardedPtr<KMFolder>, bool>::Iterator it = mPendingUpdates.begin();
00488         it != mPendingUpdates.end(); ++it)
00489   {
00490   KMFolder *fldr = it.key();
00491   if ( !fldr ) // deleted folder
00492     continue;
00493 
00495   int unread = fldr->countUnread();
00496 
00497   TQMap<TQGuardedPtr<KMFolder>, int>::Iterator unread_it =
00498       mFoldersWithUnread.find(fldr);
00499   bool unmapped = (unread_it == mFoldersWithUnread.end());
00500 
00503   if(unmapped) mCount += unread;
00504   /* Otherwise, get the difference between the numUnread in the folder and
00505    * our last known version, and adjust mCount with that difference */
00506   else
00507   {
00508     int diff = unread - unread_it.data();
00509     mCount += diff;
00510   }
00511 
00512   if(unread > 0)
00513   {
00515     mFoldersWithUnread.insert(fldr, unread);
00516     //kdDebug(5006) << "There are now " << mFoldersWithUnread.count() << " folders with unread" << endl;
00517   }
00518 
00524   if(unmapped)
00525   {
00527     if(unread == 0) continue;
00528 
00530     if ( ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread )
00531          && isHidden() ) {
00532       show();
00533     }
00534 
00535   } else
00536   {
00537 
00538     if(unread == 0)
00539     {
00540       kdDebug(5006) << "Removing folder from internal store " << fldr->name() << endl;
00541 
00543       mFoldersWithUnread.remove(fldr);
00544 
00546       if(mFoldersWithUnread.count() == 0)
00547       {
00548         mPopupFolders.clear();
00549         disconnect(this, TQT_SLOT(selectedAccount(int)));
00550 
00551         mCount = 0;
00552 
00553         if ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ) {
00554           hide();
00555         }
00556       }
00557     }
00558   }
00559 
00560   }
00561   mPendingUpdates.clear();
00562   updateCount();
00563 
00565   TQToolTip::remove(this);
00566   TQToolTip::add(this, mCount == 0 ?
00567               i18n("There are no unread messages")
00568               : i18n("There is 1 unread message.",
00569                              "There are %n unread messages.",
00570                            mCount));
00571 
00572   mLastUpdate = time( 0 );
00573 }
00574 
00580 void KMSystemTray::selectedAccount(int id)
00581 {
00582   showKMail();
00583 
00584   KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
00585   if (!mainWidget)
00586   {
00587     kmkernel->openReader();
00588     mainWidget = kmkernel->getKMMainWidget();
00589   }
00590 
00591   assert(mainWidget);
00592 
00594   KMFolder * fldr = mPopupFolders.at(id);
00595   if(!fldr) return;
00596   KMFolderTree * ft = mainWidget->folderTree();
00597   if(!ft) return;
00598   TQListViewItem * fldrIdx = ft->indexOfFolder(fldr);
00599   if(!fldrIdx) return;
00600 
00601   ft->setCurrentItem(fldrIdx);
00602   ft->selectCurrentFolder();
00603 }
00604 
00605 bool KMSystemTray::hasUnreadMail() const
00606 {
00607   return ( mCount != 0 );
00608 }
00609 
00610 #include "kmsystemtray.moc"