fix tricky regression noticed by Vyacheslav Tokarev on Google Reader.
[kdelibs.git] / kdeui / util / ksystemtrayicon.cpp
blobe386977ae262ae1969be6cd1e26bc1a964dbb471
1 /* This file is part of the KDE libraries
3 Copyright (C) 1999 Matthias Ettrich (ettrich@kde.org)
4 Copyright (c) 2007 by Charles Connell <charles@connells.org>
5 Copyright (C) 2008 Lukas Appelhans <l.appelhans@gmx.de>
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
23 #include "ksystemtrayicon.h"
24 #include "kaboutdata.h"
25 #include "kaction.h"
26 #include "kcomponentdata.h"
27 #include "klocale.h"
28 #include "kmenu.h"
29 #include "kmessagebox.h"
30 #include "kshortcut.h"
31 #include "kactioncollection.h"
32 #include "kstandardaction.h"
33 #include <kwindowsystem.h>
35 #ifdef Q_WS_X11
36 #include <QX11Info>
37 #endif
38 #ifdef Q_WS_WIN
39 #include <windows.h>
40 #endif
42 #include <kiconloader.h>
43 #include <kapplication.h>
44 #include <kconfiggroup.h>
46 #include <QMouseEvent>
47 #include <QToolButton>
48 #include <QMovie>
49 #include <QPointer>
51 #ifdef Q_WS_WIN
52 class KSystemTrayIconPrivate : public QObject
53 #else
54 class KSystemTrayIconPrivate
55 #endif
57 public:
58 KSystemTrayIconPrivate(KSystemTrayIcon* trayIcon, QWidget* parent)
59 : q(trayIcon)
61 actionCollection = new KActionCollection( trayIcon );
62 hasQuit = false;
63 onAllDesktops = false;
64 window = parent;
65 movie = 0;
66 #ifdef Q_WS_WIN
67 if ( window ) {
68 window->installEventFilter( this );
70 #endif
73 ~KSystemTrayIconPrivate()
75 #ifdef Q_WS_WIN
76 if ( window ) {
77 window->removeEventFilter( this );
79 #endif
80 delete actionCollection;
81 delete menu;
85 void _k_slotNewFrame()
87 q->setIcon(QIcon(movie->currentPixmap()));
90 #ifdef Q_WS_WIN
91 bool eventFilter(QObject *obj, QEvent *ev)
93 if(ev->type() == QEvent::ActivationChange) {
94 dwTickCount = GetTickCount();
96 return QObject::eventFilter(obj, ev);
98 DWORD dwTickCount;
99 #endif
101 KSystemTrayIcon* q;
102 KActionCollection* actionCollection;
103 KMenu* menu;
104 QWidget* window;
105 QAction* titleAction;
106 bool onAllDesktops : 1; // valid only when the parent widget was hidden
107 bool hasQuit : 1;
108 QPointer<QMovie> movie;
111 KSystemTrayIcon::KSystemTrayIcon( QWidget* parent )
112 : QSystemTrayIcon( parent ),
113 d( new KSystemTrayIconPrivate( this, parent ) )
115 init( parent );
118 KSystemTrayIcon::KSystemTrayIcon( const QString& icon, QWidget* parent )
119 : QSystemTrayIcon( loadIcon( icon ), parent ),
120 d( new KSystemTrayIconPrivate( this, parent ) )
122 init( parent );
125 KSystemTrayIcon::KSystemTrayIcon( const QIcon& icon, QWidget* parent )
126 : QSystemTrayIcon( icon, parent ),
127 d( new KSystemTrayIconPrivate( this, parent ) )
129 init( parent );
132 KSystemTrayIcon::KSystemTrayIcon(QMovie* movie, QWidget *parent)
133 : QSystemTrayIcon(parent),
134 d( new KSystemTrayIconPrivate( this, parent ) )
136 init(parent);
137 setMovie(movie);
140 void KSystemTrayIcon::init( QWidget* parent )
142 // Ensure that closing the last KMainWindow doesn't exit the application
143 // if a system tray icon is still present.
144 KGlobal::ref();
145 d->menu = new KMenu( parent );
146 d->titleAction = d->menu->addTitle( qApp->windowIcon(), KGlobal::caption() );
147 d->menu->setTitle( KGlobal::mainComponent().aboutData()->programName() );
148 connect( d->menu, SIGNAL( aboutToShow() ), this, SLOT( contextMenuAboutToShow() ) );
149 setContextMenu( d->menu );
151 KStandardAction::quit( this, SLOT( maybeQuit() ), d->actionCollection );
153 if ( parent )
155 QAction *action = d->actionCollection->addAction("minimizeRestore");
156 action->setText(i18n("Minimize"));
157 connect( action, SIGNAL( triggered( bool ) ), this, SLOT( minimizeRestoreAction() ) );
159 #ifdef Q_WS_X11
160 KWindowInfo info = KWindowSystem::windowInfo( parent->winId(), NET::WMDesktop );
161 d->onAllDesktops = info.onAllDesktops();
162 #else
163 d->onAllDesktops = false;
164 #endif
166 else
168 d->onAllDesktops = false;
171 connect( this, SIGNAL( activated( QSystemTrayIcon::ActivationReason ) ),
172 SLOT( activateOrHide( QSystemTrayIcon::ActivationReason ) ) );
175 QWidget *KSystemTrayIcon::parentWidget() const
177 return d->window;
180 KSystemTrayIcon::~KSystemTrayIcon()
182 delete d;
183 KGlobal::deref();
186 void KSystemTrayIcon::contextMenuAboutToShow( )
188 if ( !d->hasQuit )
190 // we need to add the actions to the menu afterwards so that these items
191 // appear at the _END_ of the menu
192 d->menu->addSeparator();
193 QAction* action = d->actionCollection->action( "minimizeRestore" );
195 if ( action )
197 d->menu->addAction( action );
200 action = d->actionCollection->action( KStandardAction::name( KStandardAction::Quit ) );
202 if ( action )
204 d->menu->addAction( action );
207 d->hasQuit = true;
210 if ( d->window )
212 QAction* action = d->actionCollection->action("minimizeRestore");
213 if ( d->window->isVisible() )
215 action->setText( i18n("&Minimize") );
217 else
219 action->setText( i18n("&Restore") );
224 // called from the popup menu - always do what the menu entry says,
225 // i.e. if the window is shown, no matter if active or not, the menu
226 // entry is "minimize", otherwise it's "restore"
227 void KSystemTrayIcon::minimizeRestoreAction()
229 if ( d->window )
231 bool restore = !( d->window->isVisible() );
232 minimizeRestore( restore );
236 void KSystemTrayIcon::maybeQuit()
238 QString caption = KGlobal::caption();
239 QString query = i18n("<qt>Are you sure you want to quit <b>%1</b>?</qt>",
240 caption);
241 if (KMessageBox::warningContinueCancel(d->window, query,
242 i18n("Confirm Quit From System Tray"),
243 KStandardGuiItem::quit(),
244 KStandardGuiItem::cancel(),
245 QString("systemtrayquit%1")
246 .arg(caption)) !=
247 KMessageBox::Continue)
249 return;
252 emit quitSelected();
253 qApp->quit();
256 // if the window is not the active one, show it if needed, and activate it
257 // (just like taskbar); otherwise hide it
258 void KSystemTrayIcon::activateOrHide( QSystemTrayIcon::ActivationReason reasonCalled )
260 if ( reasonCalled != QSystemTrayIcon::Trigger )
262 return;
265 QWidget *pw = d->window;
266 if ( !pw )
268 return;
270 #ifdef Q_WS_WIN
271 // the problem is that we lose focus when the systray icon is activated
272 // and we don't know the former active window
273 // therefore we watch for activation event and use our stopwatch :)
274 if( GetTickCount() - d->dwTickCount < 300 ) {
275 // we were active in the last 300ms -> hide it
276 minimizeRestore( false );
277 } else {
278 minimizeRestore( true );
280 #elif defined(Q_WS_X11)
281 KWindowInfo info1 = KWindowSystem::windowInfo( pw->winId(), NET::XAWMState | NET::WMState );
282 // mapped = visible (but possibly obscured)
283 bool mapped = (info1.mappingState() == NET::Visible) && !info1.isMinimized();
284 // - not mapped -> show, raise, focus
285 // - mapped
286 // - obscured -> raise, focus
287 // - not obscured -> hide
288 if( !mapped )
289 minimizeRestore( true );
290 else
292 QListIterator< WId > it (KWindowSystem::stackingOrder());
293 it.toBack();
294 while( it.hasPrevious() )
296 WId id = it.previous();
297 if( id == pw->winId() )
298 break;
299 KWindowInfo info2 = KWindowSystem::windowInfo( id,
300 NET::WMGeometry | NET::XAWMState | NET::WMState | NET::WMWindowType );
301 if( info2.mappingState() != NET::Visible )
302 continue; // not visible on current desktop -> ignore
303 if( !info2.geometry().intersects( pw->geometry()))
304 continue; // not obscuring the window -> ignore
305 if( !info1.hasState( NET::KeepAbove ) && info2.hasState( NET::KeepAbove ))
306 continue; // obscured by window kept above -> ignore
307 NET::WindowType type = info2.windowType( NET::NormalMask | NET::DesktopMask
308 | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask
309 | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask );
310 if( type == NET::Dock || type == NET::TopMenu )
311 continue; // obscured by dock or topmenu -> ignore
312 pw->raise();
313 KWindowSystem::activateWindow( pw->winId());
314 return;
316 minimizeRestore( false ); // hide
318 #endif
321 void KSystemTrayIcon::minimizeRestore( bool restore )
323 QWidget* pw = d->window;
324 if (!pw)
325 return;
326 #ifdef Q_WS_X11
327 KWindowInfo info = KWindowSystem::windowInfo(pw->winId(), NET::WMGeometry | NET::WMDesktop);
328 if (restore) {
329 if (d->onAllDesktops) {
330 KWindowSystem::setOnAllDesktops(pw->winId(), true);
331 } else {
332 KWindowSystem::setCurrentDesktop(info.desktop());
334 pw->move(info.geometry().topLeft()); // avoid placement policies
335 pw->show();
336 pw->raise();
337 KWindowSystem::activateWindow(pw->winId());
338 } else {
339 d->onAllDesktops = info.onAllDesktops();
340 pw->hide();
342 #else
343 if ( restore )
345 pw->show();
346 pw->raise();
347 KWindowSystem::forceActiveWindow( pw->winId() );
348 } else {
349 pw->hide();
351 #endif
354 KActionCollection* KSystemTrayIcon::actionCollection()
356 return d->actionCollection;
359 QIcon KSystemTrayIcon::loadIcon(const QString &icon, const KComponentData &componentData)
361 KConfigGroup cg(componentData.config(), "System Tray");
362 const int iconWidth = cg.readEntry("systrayIconWidth", 22);
363 return KIconLoader::global()->loadIcon( icon, KIconLoader::Panel, iconWidth );
366 void KSystemTrayIcon::toggleActive()
368 activateOrHide( QSystemTrayIcon::Trigger );
371 bool KSystemTrayIcon::parentWidgetTrayClose() const
373 if( kapp != NULL && kapp->sessionSaving())
374 return false; // normal close
375 return true;
378 void KSystemTrayIcon::setContextMenuTitle(QAction *action)
380 // can never be null, and is always the requsted type, so no need to do null checks after casts.
381 QToolButton *button = static_cast<QToolButton*>((static_cast<QWidgetAction*>(d->titleAction))->defaultWidget());
382 button->setDefaultAction(action);
385 QAction *KSystemTrayIcon::contextMenuTitle() const
387 QToolButton *button = static_cast<QToolButton*>((static_cast<QWidgetAction*>(d->titleAction))->defaultWidget());
388 return button->defaultAction();
391 void KSystemTrayIcon::setMovie(QMovie* m)
393 delete d->movie;
394 m->setParent(this);
395 d->movie = m;
396 connect(d->movie, SIGNAL(frameChanged(int)), this, SLOT(_k_slotNewFrame()));
397 d->movie->setCacheMode(QMovie::CacheAll);
400 const QMovie* KSystemTrayIcon::movie() const
402 return d->movie;
405 #include "ksystemtrayicon.moc"