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"