Use a single taskbar icon for all notifications in wxMSW.
[wxWidgets.git] / src / msw / notifmsg.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/notifmsg.cpp
3 // Purpose: implementation of wxNotificationMessage for Windows
4 // Author: Vadim Zeitlin
5 // Created: 2007-12-01
6 // RCS-ID: $Id$
7 // Copyright: (c) 2007 Vadim Zeitlin <vadim@wxwindows.org>
8 // Licence: wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
10
11 // ============================================================================
12 // declarations
13 // ============================================================================
14
15 // ----------------------------------------------------------------------------
16 // headers
17 // ----------------------------------------------------------------------------
18
19 // for compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
21
22 #ifdef __BORLANDC__
23 #pragma hdrstop
24 #endif
25
26 // we can only use the native implementation if we have a working
27 // wxTaskBarIcon::ShowBalloon() method
28 #if wxUSE_NOTIFICATION_MESSAGE && \
29 wxUSE_TASKBARICON && wxUSE_TASKBARICON_BALLOONS
30
31 #include "wx/notifmsg.h"
32
33 #ifndef WX_PRECOMP
34 #include "wx/toplevel.h"
35 #include "wx/app.h"
36 #include "wx/string.h"
37 #endif // WX_PRECOMP
38
39 #include "wx/generic/notifmsg.h"
40
41 #include "wx/taskbar.h"
42
43 // ----------------------------------------------------------------------------
44 // different implementations used by wxNotificationMessage
45 // ----------------------------------------------------------------------------
46
47 // base class for all available implementations
48 class wxNotifMsgImpl
49 {
50 public:
51 wxNotifMsgImpl() { }
52 virtual ~wxNotifMsgImpl() { }
53
54 virtual bool DoShow(const wxString& title,
55 const wxString& message,
56 int timeout,
57 int flags) = 0;
58 virtual bool DoClose() = 0;
59
60 private:
61 wxDECLARE_NO_COPY_CLASS(wxNotifMsgImpl);
62 };
63
64 // implementation which is simply a bridge to wxGenericNotificationMessage
65 class wxGenericNotifMsgImpl : public wxNotifMsgImpl
66 {
67 public:
68 wxGenericNotifMsgImpl() : m_notif(new wxGenericNotificationMessage) { }
69 virtual ~wxGenericNotifMsgImpl() { delete m_notif; }
70
71 virtual bool DoShow(const wxString& title,
72 const wxString& message,
73 int timeout,
74 int flags)
75 {
76 m_notif->SetTitle(title);
77 m_notif->SetMessage(message);
78 m_notif->SetFlags(flags);
79 return m_notif->Show(timeout);
80 }
81
82 virtual bool DoClose()
83 {
84 return m_notif->Close();
85 }
86
87 private:
88 wxGenericNotificationMessage * const m_notif;
89 };
90
91 // common base class for implementations using a taskbar icon and balloons
92 class wxBalloonNotifMsgImpl : public wxNotifMsgImpl
93 {
94 public:
95 // Ctor creates the associated taskbar icon (using the icon of the top
96 // level parent of the given window) unless UseTaskBarIcon() had been
97 // previously called which can be used to show an attached balloon later
98 // by the derived classes.
99 wxBalloonNotifMsgImpl(wxWindow *win) { SetUpIcon(win); }
100
101 // implementation of wxNotificationMessage method with the same name
102 static wxTaskBarIcon *UseTaskBarIcon(wxTaskBarIcon *icon);
103
104 virtual bool DoShow(const wxString& title,
105 const wxString& message,
106 int timeout,
107 int flags);
108
109
110 // Returns true if we're using our own icon or false if we're hitching a
111 // ride on the application icon provided to us via UseTaskBarIcon().
112 static bool IsUsingOwnIcon()
113 {
114 return ms_refCountIcon != -1;
115 }
116
117 // Indicates that the taskbar icon we're using has been hidden and can be
118 // deleted.
119 //
120 // This is only called by wxNotificationIconEvtHandler and should only be
121 // called when using our own icon (as opposed to the one passed to us via
122 // UseTaskBarIcon()).
123 static void ReleaseIcon()
124 {
125 wxASSERT_MSG( ms_refCountIcon != -1,
126 wxS("Must not be called when not using own icon") );
127
128 if ( !--ms_refCountIcon )
129 {
130 delete ms_icon;
131 ms_icon = NULL;
132 }
133 }
134
135 protected:
136 // Creates a new icon if necessary, see the comment below.
137 void SetUpIcon(wxWindow *win);
138
139
140 // We need an icon to show the notification in a balloon attached to it.
141 // It may happen that the main application already shows an icon in the
142 // taskbar notification area in which case it should call our
143 // UseTaskBarIcon() and we just use this icon without ever allocating nor
144 // deleting it and ms_refCountIcon is -1 and never changes. Otherwise, we
145 // create the icon when we need it the first time but reuse it if we need
146 // to show subsequent notifications while this icon is still alive. This is
147 // needed in order to avoid 2 or 3 or even more identical icons if a couple
148 // of notifications are shown in a row (which happens quite easily in
149 // practice because Windows helpfully buffers all the notifications that
150 // were generated while the user was away -- i.e. the screensaver was
151 // active -- and then shows them all at once when the user comes back). In
152 // this case, ms_refCountIcon is used as a normal reference counter, i.e.
153 // the icon is only destroyed when it reaches 0.
154 static wxTaskBarIcon *ms_icon;
155 static int ms_refCountIcon;
156 };
157
158 // implementation for automatically hidden notifications
159 class wxAutoNotifMsgImpl : public wxBalloonNotifMsgImpl
160 {
161 public:
162 wxAutoNotifMsgImpl(wxWindow *win);
163
164 virtual bool DoShow(const wxString& title,
165 const wxString& message,
166 int timeout,
167 int flags);
168
169 // can't close automatic notification [currently]
170 virtual bool DoClose() { return false; }
171 };
172
173 // implementation for manually closed notifications
174 class wxManualNotifMsgImpl : public wxBalloonNotifMsgImpl
175 {
176 public:
177 wxManualNotifMsgImpl(wxWindow *win);
178 virtual ~wxManualNotifMsgImpl();
179
180 virtual bool DoShow(const wxString& title,
181 const wxString& message,
182 int timeout,
183 int flags);
184 virtual bool DoClose();
185
186 private:
187 // store ctor parameter as we need it to recreate the icon later if we're
188 // closed and shown again
189 wxWindow * const m_win;
190 };
191
192 // ----------------------------------------------------------------------------
193 // custom event handler for task bar icons
194 // ----------------------------------------------------------------------------
195
196 // normally we'd just use a custom taskbar icon class but this is impossible
197 // because we can be asked to attach the notifications to an existing icon
198 // which we didn't create, hence we install a special event handler allowing us
199 // to get the events we need (and, crucially, to delete the icon when it's not
200 // needed any more) in any case
201
202 class wxNotificationIconEvtHandler : public wxEvtHandler
203 {
204 public:
205 wxNotificationIconEvtHandler(wxTaskBarIcon *icon);
206
207 private:
208 void OnTimeout(wxTaskBarIconEvent& event);
209 void OnClick(wxTaskBarIconEvent& event);
210
211 void OnIconHidden();
212
213
214 wxTaskBarIcon * const m_icon;
215
216 wxDECLARE_NO_COPY_CLASS(wxNotificationIconEvtHandler);
217 };
218
219 // ============================================================================
220 // implementation
221 // ============================================================================
222
223 // ----------------------------------------------------------------------------
224 // wxNotificationIconEvtHandler
225 // ----------------------------------------------------------------------------
226
227 wxNotificationIconEvtHandler::wxNotificationIconEvtHandler(wxTaskBarIcon *icon)
228 : m_icon(icon)
229 {
230 m_icon->Connect
231 (
232 wxEVT_TASKBAR_BALLOON_TIMEOUT,
233 wxTaskBarIconEventHandler(wxNotificationIconEvtHandler::OnTimeout),
234 NULL,
235 this
236 );
237
238 m_icon->Connect
239 (
240 wxEVT_TASKBAR_BALLOON_CLICK,
241 wxTaskBarIconEventHandler(wxNotificationIconEvtHandler::OnClick),
242 NULL,
243 this
244 );
245 }
246
247 void wxNotificationIconEvtHandler::OnIconHidden()
248 {
249 wxBalloonNotifMsgImpl::ReleaseIcon();
250
251 delete this;
252 }
253
254 void
255 wxNotificationIconEvtHandler::OnTimeout(wxTaskBarIconEvent& WXUNUSED(event))
256 {
257 OnIconHidden();
258 }
259
260 void wxNotificationIconEvtHandler::OnClick(wxTaskBarIconEvent& WXUNUSED(event))
261 {
262 // TODO: generate an event notifying the user code?
263
264 OnIconHidden();
265 }
266
267 // ----------------------------------------------------------------------------
268 // wxBalloonNotifMsgImpl
269 // ----------------------------------------------------------------------------
270
271 wxTaskBarIcon *wxBalloonNotifMsgImpl::ms_icon = NULL;
272 int wxBalloonNotifMsgImpl::ms_refCountIcon = 0;
273
274 /* static */
275 wxTaskBarIcon *wxBalloonNotifMsgImpl::UseTaskBarIcon(wxTaskBarIcon *icon)
276 {
277 wxTaskBarIcon * const iconOld = ms_icon;
278 ms_icon = icon;
279
280 // Don't use reference counting for the provided icon, we don't own it.
281 ms_refCountIcon = icon ? -1 : 0;
282
283 return iconOld;
284 }
285
286 void wxBalloonNotifMsgImpl::SetUpIcon(wxWindow *win)
287 {
288 if ( ms_icon )
289 {
290 // Increment the reference count if we manage the icon on our own.
291 if ( ms_refCountIcon != -1 )
292 ms_refCountIcon++;
293 }
294 else // Create a new icon.
295 {
296 wxASSERT_MSG( ms_refCountIcon == 0,
297 wxS("Shouldn't reference not existent icon") );
298
299 ms_icon = new wxTaskBarIcon;
300 ms_refCountIcon = 1;
301
302 // use the icon of the associated (or main, if none) frame
303 wxIcon icon;
304 if ( win )
305 win = wxGetTopLevelParent(win);
306 if ( !win )
307 win = wxTheApp->GetTopWindow();
308 if ( win )
309 {
310 const wxTopLevelWindow * const
311 tlw = wxDynamicCast(win, wxTopLevelWindow);
312 if ( tlw )
313 icon = tlw->GetIcon();
314 }
315
316 if ( !icon.IsOk() )
317 {
318 // we really must have some icon
319 icon = wxIcon(wxT("wxICON_AAA"));
320 }
321
322 ms_icon->SetIcon(icon);
323 }
324 }
325
326 bool
327 wxBalloonNotifMsgImpl::DoShow(const wxString& title,
328 const wxString& message,
329 int timeout,
330 int flags)
331 {
332 if ( !ms_icon->IsIconInstalled() )
333 {
334 // If we failed to install the icon (which does happen sometimes,
335 // although only in unusual circumstances, e.g. it happens regularly,
336 // albeit not constantly, if we're used soon after resume from suspend
337 // under Windows 7), we should not call ShowBalloon() because it would
338 // just assert and return and we must delete the icon ourselves because
339 // otherwise its associated wxTaskBarIconWindow would remain alive
340 // forever because we're not going to receive a notification about icon
341 // disappearance from the system if we failed to install it in the
342 // first place.
343 delete ms_icon;
344 ms_icon = NULL;
345
346 return false;
347 }
348
349 timeout *= 1000; // Windows expresses timeout in milliseconds
350
351 return ms_icon->ShowBalloon(title, message, timeout, flags);
352 }
353
354 // ----------------------------------------------------------------------------
355 // wxManualNotifMsgImpl
356 // ----------------------------------------------------------------------------
357
358 wxManualNotifMsgImpl::wxManualNotifMsgImpl(wxWindow *win)
359 : wxBalloonNotifMsgImpl(win),
360 m_win(win)
361 {
362 }
363
364 wxManualNotifMsgImpl::~wxManualNotifMsgImpl()
365 {
366 if ( ms_icon )
367 DoClose();
368 }
369
370 bool
371 wxManualNotifMsgImpl::DoShow(const wxString& title,
372 const wxString& message,
373 int WXUNUSED_UNLESS_DEBUG(timeout),
374 int flags)
375 {
376 wxASSERT_MSG( timeout == wxNotificationMessage::Timeout_Never,
377 wxT("shouldn't be used") );
378
379 // base class creates the icon for us initially but we could have destroyed
380 // it in DoClose(), recreate it if this was the case
381 if ( !ms_icon )
382 SetUpIcon(m_win);
383
384 // use maximal (in current Windows versions) timeout (but it will still
385 // disappear on its own)
386 return wxBalloonNotifMsgImpl::DoShow(title, message, 30, flags);
387 }
388
389 bool wxManualNotifMsgImpl::DoClose()
390 {
391 if ( IsUsingOwnIcon() )
392 {
393 // we don't need the icon any more
394 ReleaseIcon();
395 }
396 else // using an existing icon
397 {
398 // just hide the balloon
399 ms_icon->ShowBalloon("", "");
400 }
401
402 return true;
403 }
404
405 // ----------------------------------------------------------------------------
406 // wxAutoNotifMsgImpl
407 // ----------------------------------------------------------------------------
408
409 wxAutoNotifMsgImpl::wxAutoNotifMsgImpl(wxWindow *win)
410 : wxBalloonNotifMsgImpl(win)
411 {
412 if ( ms_refCountIcon != -1 )
413 {
414 // This object will self-destruct and decrease the ref count of the
415 // icon when the notification is hidden.
416 new wxNotificationIconEvtHandler(ms_icon);
417 }
418 }
419
420 bool
421 wxAutoNotifMsgImpl::DoShow(const wxString& title,
422 const wxString& message,
423 int timeout,
424 int flags)
425 {
426 wxASSERT_MSG( timeout != wxNotificationMessage::Timeout_Never,
427 wxT("shouldn't be used") );
428
429 if ( timeout == wxNotificationMessage::Timeout_Auto )
430 {
431 // choose a value more or less in the middle of the allowed range
432 timeout = 1;
433 }
434
435 return wxBalloonNotifMsgImpl::DoShow(title, message, timeout, flags);
436 }
437
438 // ----------------------------------------------------------------------------
439 // wxNotificationMessage
440 // ----------------------------------------------------------------------------
441
442 /* static */
443 bool wxNotificationMessage::ms_alwaysUseGeneric = false;
444
445 /* static */
446 wxTaskBarIcon *wxNotificationMessage::UseTaskBarIcon(wxTaskBarIcon *icon)
447 {
448 return wxBalloonNotifMsgImpl::UseTaskBarIcon(icon);
449 }
450
451 bool wxNotificationMessage::Show(int timeout)
452 {
453 if ( !m_impl )
454 {
455 if ( !ms_alwaysUseGeneric && wxTheApp->GetShell32Version() >= 500 )
456 {
457 if ( timeout == Timeout_Never )
458 m_impl = new wxManualNotifMsgImpl(GetParent());
459 else
460 m_impl = new wxAutoNotifMsgImpl(GetParent());
461 }
462 else // no support for balloon tooltips
463 {
464 m_impl = new wxGenericNotifMsgImpl;
465 }
466 }
467 //else: reuse the same implementation for the subsequent calls, it would
468 // be too confusing if it changed
469
470 return m_impl->DoShow(GetTitle(), GetMessage(), timeout, GetFlags());
471 }
472
473 bool wxNotificationMessage::Close()
474 {
475 wxCHECK_MSG( m_impl, false, "must show the notification first" );
476
477 return m_impl->DoClose();
478 }
479
480 wxNotificationMessage::~wxNotificationMessage()
481 {
482 delete m_impl;
483 }
484
485 #endif // wxUSE_NOTIFICATION_MESSAGE && wxUSE_TASKBARICON