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 <tdeapplication.h>
32 #include <tdemainwindow.h>
33 #include <tdeglobalsettings.h>
34 #include <kiconloader.h>
35 #include <kiconeffect.h>
36 #include <twin.h>
37 #include <kdebug.h>
38 #include <tdepopupmenu.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 TDEPopupMenu();
115  KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
116  if ( !mainWidget )
117  return;
118 
119  mPopupMenu->insertTitle(*(this->pixmap()), "KMail");
120  TDEAction * 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  mPopupMenu->insertItem( SmallIcon("system-log-out"), i18n("&Quit"), this, TQT_SLOT(maybeQuit()) );
137 }
138 
139 void KMSystemTray::tray_quit()
140 {
141  // Quit all of KMail
142  kapp->quit();
143 }
144 
146 {
147  // unregister the applet
148  kmkernel->unregisterSystemTrayApplet( this );
149 
150  delete mPopupMenu;
151  mPopupMenu = 0;
152 }
153 
154 void KMSystemTray::setMode(int newMode)
155 {
156  if(newMode == mMode) return;
157 
158  kdDebug(5006) << "Setting systray mMode to " << newMode << endl;
159  mMode = newMode;
160 
161  switch ( mMode ) {
162  case GlobalSettings::EnumSystemTrayPolicy::ShowAlways:
163  if ( isHidden() )
164  show();
165  break;
166  case GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread:
167  if ( mCount == 0 && !isHidden() )
168  hide();
169  else if ( mCount > 0 && isHidden() )
170  show();
171  break;
172  default:
173  kdDebug(5006) << k_funcinfo << " Unknown systray mode " << mMode << endl;
174  }
175 }
176 
177 int KMSystemTray::mode() const
178 {
179  return mMode;
180 }
181 
182 void KMSystemTray::resizeEvent(TQResizeEvent *)
183 {
184  updateCount();
185 }
186 
193 {
194  if(mCount != 0)
195  {
196  int oldPixmapWidth = pixmap()->size().width();
197  int oldPixmapHeight = pixmap()->size().height();
198 
199  TQString countString = TQString::number( mCount );
200  TQFont countFont = TDEGlobalSettings::generalFont();
201  countFont.setBold(true);
202 
203  // increase the size of the font for the number of unread messages if the
204  // icon size is less than 22 pixels
205  // see bug 1251
206  int realIconHeight = height();
207  if (realIconHeight < 22) {
208  countFont.setPointSizeFloat( countFont.pointSizeFloat() * 2.0 );
209  }
210 
211  // decrease the size of the font for the number of unread messages if the
212  // number doesn't fit into the available space
213  float countFontSize = countFont.pointSizeFloat();
214  TQFontMetrics qfm( countFont );
215  int width = qfm.width( countString );
216  if( width > oldPixmapWidth )
217  {
218  countFontSize *= float( oldPixmapWidth ) / float( width );
219  countFont.setPointSizeFloat( countFontSize );
220  }
221 
222  // Create an image which represents the number of unread messages
223  // and which has a transparent background.
224  // Unfortunately this required the following twisted code because for some
225  // reason text that is drawn on a transparent pixmap is invisible
226  // (apparently the alpha channel isn't changed when the text is drawn).
227  // Therefore I have to draw the text on a solid background and then remove
228  // the background by making it transparent with TQPixmap::setMask. This
229  // involves the slow createHeuristicMask() function (from the API docs:
230  // "This function is slow because it involves transformation to a TQImage,
231  // non-trivial computations and a transformation back to a TQBitmap."). Then
232  // I have to convert the resulting TQPixmap to a TQImage in order to overlay
233  // the light KMail icon with the number (because TDEIconEffect::overlay only
234  // works with TQImage). Finally the resulting TQImage has to be converted
235  // back to a TQPixmap.
236  // That's a lot of work for overlaying the KMail icon with the number of
237  // unread messages, but every other approach I tried failed miserably.
238  // IK, 2003-09-22
239  TQPixmap numberPixmap( oldPixmapWidth, oldPixmapHeight );
240  numberPixmap.fill( TQt::white );
241  TQPainter p( &numberPixmap );
242  p.setFont( countFont );
243  p.setPen( TQt::blue );
244  p.drawText( numberPixmap.rect(), TQt::AlignCenter, countString );
245  numberPixmap.setMask( numberPixmap.createHeuristicMask() );
246  TQImage numberImage = numberPixmap.convertToImage();
247 
248  // Overlay the light KMail icon with the number image
249  TQImage iconWithNumberImage = mLightIconImage.copy();
250  TDEIconEffect::overlay( iconWithNumberImage, numberImage );
251 
252  TQPixmap iconWithNumber;
253  iconWithNumber.convertFromImage( iconWithNumberImage );
254  setPixmap( iconWithNumber );
255  } else
256  {
257  setPixmap( mDefaultIcon );
258  }
259 }
260 
266 {
271  mFoldersWithUnread.clear();
272  mCount = 0;
273 
274  if ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ) {
275  hide();
276  }
277 
279  disconnect(this, TQT_SLOT(updateNewMessageNotification(KMFolder *)));
280 
281  TQStringList folderNames;
282  TQValueList<TQGuardedPtr<KMFolder> > folderList;
283  kmkernel->folderMgr()->createFolderList(&folderNames, &folderList);
284  kmkernel->imapFolderMgr()->createFolderList(&folderNames, &folderList);
285  kmkernel->dimapFolderMgr()->createFolderList(&folderNames, &folderList);
286  kmkernel->searchFolderMgr()->createFolderList(&folderNames, &folderList);
287 
288  TQStringList::iterator strIt = folderNames.begin();
289 
290  for(TQValueList<TQGuardedPtr<KMFolder> >::iterator it = folderList.begin();
291  it != folderList.end() && strIt != folderNames.end(); ++it, ++strIt)
292  {
293  KMFolder * currentFolder = *it;
294  TQString currentName = *strIt;
295 
296  if ( ((!currentFolder->isSystemFolder() || (currentFolder->name().lower() == "inbox")) ||
297  (currentFolder->folderType() == KMFolderTypeImap)) &&
298  !currentFolder->ignoreNewMail() )
299  {
301  connect(currentFolder, TQT_SIGNAL(numUnreadMsgsChanged(KMFolder *)),
302  this, TQT_SLOT(updateNewMessageNotification(KMFolder *)));
303 
305  updateNewMessageNotification(currentFolder);
306  }
307  else {
308  disconnect(currentFolder, TQT_SIGNAL(numUnreadMsgsChanged(KMFolder *)), this, TQT_SLOT(updateNewMessageNotification(KMFolder *)) );
309  }
310  }
311 }
312 
317 void KMSystemTray::mousePressEvent(TQMouseEvent *e)
318 {
319  // switch to kmail on left mouse button
320  if( e->button() == Qt::LeftButton )
321  {
322  if( mParentVisible && mainWindowIsOnCurrentDesktop() )
323  hideKMail();
324  else
325  showKMail();
326  }
327 
328  // open popup menu on right mouse button
329  if( e->button() == Qt::RightButton )
330  {
331  mPopupFolders.clear();
332  mPopupFolders.reserve( mFoldersWithUnread.count() );
333 
334  // Rebuild popup menu at click time to minimize race condition if
335  // the base TDEMainWidget is closed.
336  buildPopupMenu();
337 
338  if(mNewMessagePopupId != -1)
339  {
340  mPopupMenu->removeItem(mNewMessagePopupId);
341  }
342 
343  if(mFoldersWithUnread.count() > 0)
344  {
345  TDEPopupMenu *newMessagesPopup = new TDEPopupMenu();
346 
347  TQMap<TQGuardedPtr<KMFolder>, int>::Iterator it = mFoldersWithUnread.begin();
348  for(uint i=0; it != mFoldersWithUnread.end(); ++i)
349  {
350  kdDebug(5006) << "Adding folder" << endl;
351  mPopupFolders.append( it.key() );
352  TQString item = prettyName(it.key()) + " (" + TQString::number(it.data()) + ")";
353  newMessagesPopup->insertItem(item, this, TQT_SLOT(selectedAccount(int)), 0, i);
354  ++it;
355  }
356 
357  mNewMessagePopupId = mPopupMenu->insertItem(i18n("New Messages In"),
358  newMessagesPopup, mNewMessagePopupId, 3);
359 
360  kdDebug(5006) << "Folders added" << endl;
361  }
362 
363  mPopupMenu->popup(e->globalPos());
364  }
365 
366 }
367 
373 {
374  TQString rvalue = fldr->label();
375  if(fldr->folderType() == KMFolderTypeImap)
376  {
377  KMFolderImap * imap = dynamic_cast<KMFolderImap*> (fldr->storage());
378  assert(imap);
379 
380  if((imap->account() != 0) &&
381  (imap->account()->name() != 0) )
382  {
383  kdDebug(5006) << "IMAP folder, prepend label with type" << endl;
384  rvalue = imap->account()->name() + "->" + rvalue;
385  }
386  }
387 
388  kdDebug(5006) << "Got label " << rvalue << endl;
389 
390  return rvalue;
391 }
392 
393 
394 bool KMSystemTray::mainWindowIsOnCurrentDesktop()
395 {
396  KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
397  if ( !mainWidget )
398  return false;
399 
400  TQWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget();
401  if ( !mainWin )
402  return false;
403 
404  return KWin::windowInfo( mainWin->winId(),
405  NET::WMDesktop ).isOnCurrentDesktop();
406 }
407 
413 {
414  if (!kmkernel->getKMMainWidget())
415  return;
416  TQWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget();
417  assert(mainWin);
418  if(mainWin)
419  {
420  KWin::WindowInfo cur = KWin::windowInfo( mainWin->winId(), NET::WMDesktop );
421  if ( cur.valid() ) mDesktopOfMainWin = cur.desktop();
422  // switch to appropriate desktop
423  if ( mDesktopOfMainWin != NET::OnAllDesktops )
424  KWin::setCurrentDesktop( mDesktopOfMainWin );
425  if ( !mParentVisible ) {
426  if ( mDesktopOfMainWin == NET::OnAllDesktops )
427  KWin::setOnAllDesktops( mainWin->winId(), true );
428  mainWin->move( mPosOfMainWin );
429  mainWin->show();
430  }
431  KWin::activateWindow( mainWin->winId() );
432  mParentVisible = true;
433  }
434  kmkernel->raise();
435 
436  //Fake that the folders have changed so that the icon status is correct
437  foldersChanged();
438 }
439 
440 void KMSystemTray::hideKMail()
441 {
442  if (!kmkernel->getKMMainWidget())
443  return;
444  TQWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget();
445  assert(mainWin);
446  if(mainWin)
447  {
448  mDesktopOfMainWin = KWin::windowInfo( mainWin->winId(),
449  NET::WMDesktop ).desktop();
450  mPosOfMainWin = mainWin->pos();
451  // iconifying is unnecessary, but it looks cooler
452  KWin::iconifyWindow( mainWin->winId() );
453  mainWin->hide();
454  mParentVisible = false;
455  }
456 }
457 
464 void KMSystemTray::updateNewMessageNotification(KMFolder * fldr)
465 {
466  //We don't want to count messages from search folders as they
467  // already counted as part of their original folders
468  if( !fldr ||
469  fldr->folderType() == KMFolderTypeSearch )
470  {
471  // kdDebug(5006) << "Null or a search folder, can't mess with that" << endl;
472  return;
473  }
474 
475  mPendingUpdates[ fldr ] = true;
476  if ( time( 0 ) - mLastUpdate > 2 ) {
477  mUpdateTimer->stop();
478  updateNewMessages();
479  }
480  else {
481  mUpdateTimer->start(150, true);
482  }
483 }
484 
485 void KMSystemTray::updateNewMessages()
486 {
487  for ( TQMap<TQGuardedPtr<KMFolder>, bool>::Iterator it = mPendingUpdates.begin();
488  it != mPendingUpdates.end(); ++it)
489  {
490  KMFolder *fldr = it.key();
491  if ( !fldr ) // deleted folder
492  continue;
493 
495  int unread = fldr->countUnread();
496 
497  TQMap<TQGuardedPtr<KMFolder>, int>::Iterator unread_it =
498  mFoldersWithUnread.find(fldr);
499  bool unmapped = (unread_it == mFoldersWithUnread.end());
500 
503  if(unmapped) mCount += unread;
504  /* Otherwise, get the difference between the numUnread in the folder and
505  * our last known version, and adjust mCount with that difference */
506  else
507  {
508  int diff = unread - unread_it.data();
509  mCount += diff;
510  }
511 
512  if(unread > 0)
513  {
515  mFoldersWithUnread.insert(fldr, unread);
516  //kdDebug(5006) << "There are now " << mFoldersWithUnread.count() << " folders with unread" << endl;
517  }
518 
524  if(unmapped)
525  {
527  if(unread == 0) continue;
528 
530  if ( ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread )
531  && isHidden() ) {
532  show();
533  }
534 
535  } else
536  {
537 
538  if(unread == 0)
539  {
540  kdDebug(5006) << "Removing folder from internal store " << fldr->name() << endl;
541 
543  mFoldersWithUnread.remove(fldr);
544 
546  if(mFoldersWithUnread.count() == 0)
547  {
548  mPopupFolders.clear();
549  disconnect(this, TQT_SLOT(selectedAccount(int)));
550 
551  mCount = 0;
552 
553  if ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ) {
554  hide();
555  }
556  }
557  }
558  }
559 
560  }
561  mPendingUpdates.clear();
562  updateCount();
563 
565  TQToolTip::remove(this);
566  TQToolTip::add(this, mCount == 0 ?
567  i18n("There are no unread messages")
568  : i18n("There is 1 unread message.",
569  "There are %n unread messages.",
570  mCount));
571 
572  mLastUpdate = time( 0 );
573 }
574 
580 void KMSystemTray::selectedAccount(int id)
581 {
582  showKMail();
583 
584  KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
585  if (!mainWidget)
586  {
587  kmkernel->openReader();
588  mainWidget = kmkernel->getKMMainWidget();
589  }
590 
591  assert(mainWidget);
592 
594  KMFolder * fldr = mPopupFolders.at(id);
595  if(!fldr) return;
596  KMFolderTree * ft = mainWidget->folderTree();
597  if(!ft) return;
598  TQListViewItem * fldrIdx = ft->indexOfFolder(fldr);
599  if(!fldrIdx) return;
600 
601  ft->setCurrentItem(fldrIdx);
602  ft->selectCurrentFolder();
603 }
604 
605 bool KMSystemTray::hasUnreadMail() const
606 {
607  return ( mCount != 0 );
608 }
609 
610 #include "kmsystemtray.moc"