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"
void showKMail()
Shows and raises the first KMMainWidget and switches to the appropriate virtual desktop.
KMSystemTray(TQWidget *parent=0, const char *name=0)
construtor
int countUnread()
Number of new or unread messages in this folder.
Definition: kmfolder.cpp:450
void foldersChanged()
Refreshes the list of folders we are monitoring.
Mail folder.
Definition: kmfolder.h:68
void mousePressEvent(TQMouseEvent *)
On left mouse click, switch focus to the first KMMainWidget.
virtual TQString label() const
Returns the label of the folder for visualization.
Definition: kmfolder.cpp:581
bool isSystemFolder() const
Returns true if the folder is a kmail system folder.
Definition: kmfolder.h:369
void updateCount()
Update the count of unread messages.
TQString prettyName(KMFolder *)
Return the name of the folder in which the mail is deposited, prepended with the account name if the ...
bool ignoreNewMail() const
Returns true if the user doesn&#39;t want to get notified about new mail in this folder.
Definition: kmfolder.h:526
The account manager is responsible for creating accounts of various types via the factory method crea...
KMFolderType folderType() const
Returns the type of this folder.
Definition: kmfolder.cpp:233
~KMSystemTray()
destructor