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"
void showKMail()
Shows and raises the first KMMainWidget and switches to the appropriate virtual desktop.
KMFolderType folderType() const
Returns the type of this folder.
Definition: kmfolder.cpp:233
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.
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
void updateCount()
Update the count of unread messages.
virtual TQString label() const
Returns the label of the folder for visualization.
Definition: kmfolder.cpp:581
TQString prettyName(KMFolder *)
Return the name of the folder in which the mail is deposited, prepended with the account name if the ...
The account manager is responsible for creating accounts of various types via the factory method crea...
bool isSystemFolder() const
Returns true if the folder is a kmail system folder.
Definition: kmfolder.h:369
~KMSystemTray()
destructor