kmail

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