kmail

kmsystemtray.cpp
1 // -*- mode: C++; c-file-style: "gnu" -*-
2 /***************************************************************************
3  kmsystemtray.cpp - description
4  -------------------
5  begin : Fri Aug 31 22:38:44 EDT 2001
6  copyright : (C) 2001 by Ryan Breen
7  email : ryan@porivo.com
8  ***************************************************************************/
9 
10 /***************************************************************************
11  * *
12  * This program is free software; you can redistribute it and/or modify *
13  * it under the terms of the GNU General Public License as published by *
14  * the Free Software Foundation; either version 2 of the License, or *
15  * (at your option) any later version. *
16  * *
17  ***************************************************************************/
18 
19 #include <config.h>
20 
21 #include "kmsystemtray.h"
22 #include "kmfolder.h"
23 #include "kmfoldertree.h"
24 #include "kmfoldermgr.h"
25 #include "kmfolderimap.h"
26 #include "kmmainwidget.h"
27 #include "accountmanager.h"
29 #include "globalsettings.h"
30 
31 #include <kapplication.h>
32 #include <kmainwindow.h>
33 #include <kglobalsettings.h>
34 #include <kiconloader.h>
35 #include <kiconeffect.h>
36 #include <kwin.h>
37 #include <kdebug.h>
38 #include <kpopupmenu.h>
39 
40 #include <tqpainter.h>
41 #include <tqbitmap.h>
42 #include <tqtooltip.h>
43 #include <tqwidgetlist.h>
44 #include <tqobjectlist.h>
45 
46 #include <math.h>
47 #include <assert.h>
48 
60 KMSystemTray::KMSystemTray(TQWidget *parent, const char *name)
61  : KSystemTray( parent, name ),
62  mParentVisible( true ),
63  mPosOfMainWin( 0, 0 ),
64  mDesktopOfMainWin( 0 ),
65  mMode( GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ),
66  mCount( 0 ),
67  mNewMessagePopupId(-1),
68  mPopupMenu(0)
69 {
70  setAlignment( AlignCenter );
71  kdDebug(5006) << "Initting systray" << endl;
72 
73  mLastUpdate = time( 0 );
74  mUpdateTimer = new TQTimer( this, "systraytimer" );
75  connect( mUpdateTimer, TQT_SIGNAL( timeout() ), TQT_SLOT( updateNewMessages() ) );
76 
77  mDefaultIcon = loadIcon( "kmail" );
78  mLightIconImage = loadIcon( "kmaillight" ).convertToImage();
79 
80  setPixmap(mDefaultIcon);
81 
82  KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
83  if ( mainWidget ) {
84  TQWidget * mainWin = mainWidget->topLevelWidget();
85  if ( mainWin ) {
86  mDesktopOfMainWin = KWin::windowInfo( mainWin->winId(),
87  NET::WMDesktop ).desktop();
88  mPosOfMainWin = mainWin->pos();
89  }
90  }
91 
92  // register the applet with the kernel
93  kmkernel->registerSystemTrayApplet( this );
94 
97 
98  connect( kmkernel->folderMgr(), TQT_SIGNAL(changed()), TQT_SLOT(foldersChanged()));
99  connect( kmkernel->imapFolderMgr(), TQT_SIGNAL(changed()), TQT_SLOT(foldersChanged()));
100  connect( kmkernel->dimapFolderMgr(), TQT_SIGNAL(changed()), TQT_SLOT(foldersChanged()));
101  connect( kmkernel->searchFolderMgr(), TQT_SIGNAL(changed()), TQT_SLOT(foldersChanged()));
102 
103  connect( kmkernel->acctMgr(), TQT_SIGNAL( checkedMail( bool, bool, const TQMap<TQString, int> & ) ),
104  TQT_SLOT( updateNewMessages() ) );
105 
106  connect( this, TQT_SIGNAL( quitSelected() ), TQT_SLOT( tray_quit() ) );
107 }
108 
109 void KMSystemTray::buildPopupMenu()
110 {
111  // Delete any previously created popup menu
112  delete mPopupMenu;
113 
114  mPopupMenu = new KPopupMenu();
115  KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
116  if ( !mainWidget )
117  return;
118 
119  mPopupMenu->insertTitle(*(this->pixmap()), "KMail");
120  KAction * action;
121  if ( ( action = mainWidget->action("check_mail") ) )
122  action->plug( mPopupMenu );
123  if ( ( action = mainWidget->action("check_mail_in") ) )
124  action->plug( mPopupMenu );
125  if ( ( action = mainWidget->action("send_queued") ) )
126  action->plug( mPopupMenu );
127  if ( ( action = mainWidget->action("send_queued_via") ) )
128  action->plug( mPopupMenu );
129  mPopupMenu->insertSeparator();
130  if ( ( action = mainWidget->action("new_message") ) )
131  action->plug( mPopupMenu );
132  if ( ( action = mainWidget->action("kmail_configure_kmail") ) )
133  action->plug( mPopupMenu );
134  mPopupMenu->insertSeparator();
135 
136  KMainWindow *mainWin = ::tqqt_cast<KMainWindow*>(kmkernel->getKMMainWidget()->topLevelWidget());
137  mPopupMenu->insertItem( SmallIcon("exit"), i18n("&Quit"), this, TQT_SLOT(maybeQuit()) );
138 }
139 
140 void KMSystemTray::tray_quit()
141 {
142  // Quit all of KMail
143  kapp->quit();
144 }
145 
147 {
148  // unregister the applet
149  kmkernel->unregisterSystemTrayApplet( this );
150 
151  delete mPopupMenu;
152  mPopupMenu = 0;
153 }
154 
155 void KMSystemTray::setMode(int newMode)
156 {
157  if(newMode == mMode) return;
158 
159  kdDebug(5006) << "Setting systray mMode to " << newMode << endl;
160  mMode = newMode;
161 
162  switch ( mMode ) {
163  case GlobalSettings::EnumSystemTrayPolicy::ShowAlways:
164  if ( isHidden() )
165  show();
166  break;
167  case GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread:
168  if ( mCount == 0 && !isHidden() )
169  hide();
170  else if ( mCount > 0 && isHidden() )
171  show();
172  break;
173  default:
174  kdDebug(5006) << k_funcinfo << " Unknown systray mode " << mMode << endl;
175  }
176 }
177 
178 int KMSystemTray::mode() const
179 {
180  return mMode;
181 }
182 
189 {
190  if(mCount != 0)
191  {
192  int oldPixmapWidth = pixmap()->size().width();
193  int oldPixmapHeight = pixmap()->size().height();
194 
195  TQString countString = TQString::number( mCount );
196  TQFont countFont = KGlobalSettings::generalFont();
197  countFont.setBold(true);
198 
199  // decrease the size of the font for the number of unread messages if the
200  // number doesn't fit into the available space
201  float countFontSize = countFont.pointSizeFloat();
202  TQFontMetrics qfm( countFont );
203  int width = qfm.width( countString );
204  if( width > oldPixmapWidth )
205  {
206  countFontSize *= float( oldPixmapWidth ) / float( width );
207  countFont.setPointSizeFloat( countFontSize );
208  }
209 
210  // Create an image which represents the number of unread messages
211  // and which has a transparent background.
212  // Unfortunately this required the following twisted code because for some
213  // reason text that is drawn on a transparent pixmap is invisible
214  // (apparently the alpha channel isn't changed when the text is drawn).
215  // Therefore I have to draw the text on a solid background and then remove
216  // the background by making it transparent with TQPixmap::setMask. This
217  // involves the slow createHeuristicMask() function (from the API docs:
218  // "This function is slow because it involves transformation to a TQImage,
219  // non-trivial computations and a transformation back to a TQBitmap."). Then
220  // I have to convert the resulting TQPixmap to a TQImage in order to overlay
221  // the light KMail icon with the number (because KIconEffect::overlay only
222  // works with TQImage). Finally the resulting TQImage has to be converted
223  // back to a TQPixmap.
224  // That's a lot of work for overlaying the KMail icon with the number of
225  // unread messages, but every other approach I tried failed miserably.
226  // IK, 2003-09-22
227  TQPixmap numberPixmap( oldPixmapWidth, oldPixmapHeight );
228  numberPixmap.fill( TQt::white );
229  TQPainter p( &numberPixmap );
230  p.setFont( countFont );
231  p.setPen( TQt::blue );
232  p.drawText( numberPixmap.rect(), TQt::AlignCenter, countString );
233  numberPixmap.setMask( numberPixmap.createHeuristicMask() );
234  TQImage numberImage = numberPixmap.convertToImage();
235 
236  // Overlay the light KMail icon with the number image
237  TQImage iconWithNumberImage = mLightIconImage.copy();
238  KIconEffect::overlay( iconWithNumberImage, numberImage );
239 
240  TQPixmap iconWithNumber;
241  iconWithNumber.convertFromImage( iconWithNumberImage );
242  setPixmap( iconWithNumber );
243  } else
244  {
245  setPixmap( mDefaultIcon );
246  }
247 }
248 
254 {
259  mFoldersWithUnread.clear();
260  mCount = 0;
261 
262  if ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ) {
263  hide();
264  }
265 
267  disconnect(this, TQT_SLOT(updateNewMessageNotification(KMFolder *)));
268 
269  TQStringList folderNames;
270  TQValueList<TQGuardedPtr<KMFolder> > folderList;
271  kmkernel->folderMgr()->createFolderList(&folderNames, &folderList);
272  kmkernel->imapFolderMgr()->createFolderList(&folderNames, &folderList);
273  kmkernel->dimapFolderMgr()->createFolderList(&folderNames, &folderList);
274  kmkernel->searchFolderMgr()->createFolderList(&folderNames, &folderList);
275 
276  TQStringList::iterator strIt = folderNames.begin();
277 
278  for(TQValueList<TQGuardedPtr<KMFolder> >::iterator it = folderList.begin();
279  it != folderList.end() && strIt != folderNames.end(); ++it, ++strIt)
280  {
281  KMFolder * currentFolder = *it;
282  TQString currentName = *strIt;
283 
284  if ( ((!currentFolder->isSystemFolder() || (currentFolder->name().lower() == "inbox")) ||
285  (currentFolder->folderType() == KMFolderTypeImap)) &&
286  !currentFolder->ignoreNewMail() )
287  {
289  connect(currentFolder, TQT_SIGNAL(numUnreadMsgsChanged(KMFolder *)),
290  this, TQT_SLOT(updateNewMessageNotification(KMFolder *)));
291 
293  updateNewMessageNotification(currentFolder);
294  }
295  else {
296  disconnect(currentFolder, TQT_SIGNAL(numUnreadMsgsChanged(KMFolder *)), this, TQT_SLOT(updateNewMessageNotification(KMFolder *)) );
297  }
298  }
299 }
300 
305 void KMSystemTray::mousePressEvent(TQMouseEvent *e)
306 {
307  // switch to kmail on left mouse button
308  if( e->button() == Qt::LeftButton )
309  {
310  if( mParentVisible && mainWindowIsOnCurrentDesktop() )
311  hideKMail();
312  else
313  showKMail();
314  }
315 
316  // open popup menu on right mouse button
317  if( e->button() == Qt::RightButton )
318  {
319  mPopupFolders.clear();
320  mPopupFolders.reserve( mFoldersWithUnread.count() );
321 
322  // Rebuild popup menu at click time to minimize race condition if
323  // the base KMainWidget is closed.
324  buildPopupMenu();
325 
326  if(mNewMessagePopupId != -1)
327  {
328  mPopupMenu->removeItem(mNewMessagePopupId);
329  }
330 
331  if(mFoldersWithUnread.count() > 0)
332  {
333  KPopupMenu *newMessagesPopup = new KPopupMenu();
334 
335  TQMap<TQGuardedPtr<KMFolder>, int>::Iterator it = mFoldersWithUnread.begin();
336  for(uint i=0; it != mFoldersWithUnread.end(); ++i)
337  {
338  kdDebug(5006) << "Adding folder" << endl;
339  mPopupFolders.append( it.key() );
340  TQString item = prettyName(it.key()) + " (" + TQString::number(it.data()) + ")";
341  newMessagesPopup->insertItem(item, this, TQT_SLOT(selectedAccount(int)), 0, i);
342  ++it;
343  }
344 
345  mNewMessagePopupId = mPopupMenu->insertItem(i18n("New Messages In"),
346  newMessagesPopup, mNewMessagePopupId, 3);
347 
348  kdDebug(5006) << "Folders added" << endl;
349  }
350 
351  mPopupMenu->popup(e->globalPos());
352  }
353 
354 }
355 
361 {
362  TQString rvalue = fldr->label();
363  if(fldr->folderType() == KMFolderTypeImap)
364  {
365  KMFolderImap * imap = dynamic_cast<KMFolderImap*> (fldr->storage());
366  assert(imap);
367 
368  if((imap->account() != 0) &&
369  (imap->account()->name() != 0) )
370  {
371  kdDebug(5006) << "IMAP folder, prepend label with type" << endl;
372  rvalue = imap->account()->name() + "->" + rvalue;
373  }
374  }
375 
376  kdDebug(5006) << "Got label " << rvalue << endl;
377 
378  return rvalue;
379 }
380 
381 
382 bool KMSystemTray::mainWindowIsOnCurrentDesktop()
383 {
384  KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
385  if ( !mainWidget )
386  return false;
387 
388  TQWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget();
389  if ( !mainWin )
390  return false;
391 
392  return KWin::windowInfo( mainWin->winId(),
393  NET::WMDesktop ).isOnCurrentDesktop();
394 }
395 
401 {
402  if (!kmkernel->getKMMainWidget())
403  return;
404  TQWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget();
405  assert(mainWin);
406  if(mainWin)
407  {
408  KWin::WindowInfo cur = KWin::windowInfo( mainWin->winId(), NET::WMDesktop );
409  if ( cur.valid() ) mDesktopOfMainWin = cur.desktop();
410  // switch to appropriate desktop
411  if ( mDesktopOfMainWin != NET::OnAllDesktops )
412  KWin::setCurrentDesktop( mDesktopOfMainWin );
413  if ( !mParentVisible ) {
414  if ( mDesktopOfMainWin == NET::OnAllDesktops )
415  KWin::setOnAllDesktops( mainWin->winId(), true );
416  mainWin->move( mPosOfMainWin );
417  mainWin->show();
418  }
419  KWin::activateWindow( mainWin->winId() );
420  mParentVisible = true;
421  }
422  kmkernel->raise();
423 
424  //Fake that the folders have changed so that the icon status is correct
425  foldersChanged();
426 }
427 
428 void KMSystemTray::hideKMail()
429 {
430  if (!kmkernel->getKMMainWidget())
431  return;
432  TQWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget();
433  assert(mainWin);
434  if(mainWin)
435  {
436  mDesktopOfMainWin = KWin::windowInfo( mainWin->winId(),
437  NET::WMDesktop ).desktop();
438  mPosOfMainWin = mainWin->pos();
439  // iconifying is unnecessary, but it looks cooler
440  KWin::iconifyWindow( mainWin->winId() );
441  mainWin->hide();
442  mParentVisible = false;
443  }
444 }
445 
452 void KMSystemTray::updateNewMessageNotification(KMFolder * fldr)
453 {
454  //We don't want to count messages from search folders as they
455  // already counted as part of their original folders
456  if( !fldr ||
457  fldr->folderType() == KMFolderTypeSearch )
458  {
459  // kdDebug(5006) << "Null or a search folder, can't mess with that" << endl;
460  return;
461  }
462 
463  mPendingUpdates[ fldr ] = true;
464  if ( time( 0 ) - mLastUpdate > 2 ) {
465  mUpdateTimer->stop();
466  updateNewMessages();
467  }
468  else {
469  mUpdateTimer->start(150, true);
470  }
471 }
472 
473 void KMSystemTray::updateNewMessages()
474 {
475  for ( TQMap<TQGuardedPtr<KMFolder>, bool>::Iterator it = mPendingUpdates.begin();
476  it != mPendingUpdates.end(); ++it)
477  {
478  KMFolder *fldr = it.key();
479  if ( !fldr ) // deleted folder
480  continue;
481 
483  int unread = fldr->countUnread();
484 
485  TQMap<TQGuardedPtr<KMFolder>, int>::Iterator unread_it =
486  mFoldersWithUnread.find(fldr);
487  bool unmapped = (unread_it == mFoldersWithUnread.end());
488 
491  if(unmapped) mCount += unread;
492  /* Otherwise, get the difference between the numUnread in the folder and
493  * our last known version, and adjust mCount with that difference */
494  else
495  {
496  int diff = unread - unread_it.data();
497  mCount += diff;
498  }
499 
500  if(unread > 0)
501  {
503  mFoldersWithUnread.insert(fldr, unread);
504  //kdDebug(5006) << "There are now " << mFoldersWithUnread.count() << " folders with unread" << endl;
505  }
506 
512  if(unmapped)
513  {
515  if(unread == 0) continue;
516 
518  if ( ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread )
519  && isHidden() ) {
520  show();
521  }
522 
523  } else
524  {
525 
526  if(unread == 0)
527  {
528  kdDebug(5006) << "Removing folder from internal store " << fldr->name() << endl;
529 
531  mFoldersWithUnread.remove(fldr);
532 
534  if(mFoldersWithUnread.count() == 0)
535  {
536  mPopupFolders.clear();
537  disconnect(this, TQT_SLOT(selectedAccount(int)));
538 
539  mCount = 0;
540 
541  if ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ) {
542  hide();
543  }
544  }
545  }
546  }
547 
548  }
549  mPendingUpdates.clear();
550  updateCount();
551 
553  TQToolTip::remove(this);
554  TQToolTip::add(this, mCount == 0 ?
555  i18n("There are no unread messages")
556  : i18n("There is 1 unread message.",
557  "There are %n unread messages.",
558  mCount));
559 
560  mLastUpdate = time( 0 );
561 }
562 
568 void KMSystemTray::selectedAccount(int id)
569 {
570  showKMail();
571 
572  KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
573  if (!mainWidget)
574  {
575  kmkernel->openReader();
576  mainWidget = kmkernel->getKMMainWidget();
577  }
578 
579  assert(mainWidget);
580 
582  KMFolder * fldr = mPopupFolders.at(id);
583  if(!fldr) return;
584  KMFolderTree * ft = mainWidget->folderTree();
585  if(!ft) return;
586  TQListViewItem * fldrIdx = ft->indexOfFolder(fldr);
587  if(!fldrIdx) return;
588 
589  ft->setCurrentItem(fldrIdx);
590  ft->selectCurrentFolder();
591 }
592 
593 bool KMSystemTray::hasUnreadMail() const
594 {
595  return ( mCount != 0 );
596 }
597 
598 #include "kmsystemtray.moc"