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