]> git.saurik.com Git - wxWidgets.git/blob - src/msw/taskbar.cpp
wxTLW: don't assert when setting null icons initially (#10138)
[wxWidgets.git] / src / msw / taskbar.cpp
1 /////////////////////////////////////////////////////////////////////////
2 // File: src/msw/taskbar.cpp
3 // Purpose: Implements wxTaskBarIcon class for manipulating icons on
4 // the Windows task bar.
5 // Author: Julian Smart
6 // Modified by: Vaclav Slavik
7 // Created: 24/3/98
8 // RCS-ID: $Id$
9 // Copyright: (c)
10 // Licence: wxWindows licence
11 /////////////////////////////////////////////////////////////////////////
12
13 // For compilers that support precompilation, includes "wx.h".
14 #include "wx/wxprec.h"
15
16 #ifdef __BORLANDC__
17 #pragma hdrstop
18 #endif
19
20 #if wxUSE_TASKBARICON
21
22 #ifndef WX_PRECOMP
23 #include "wx/window.h"
24 #include "wx/frame.h"
25 #include "wx/utils.h"
26 #include "wx/menu.h"
27 #endif
28
29 #include "wx/msw/wrapshl.h"
30
31 #include <string.h>
32 #include "wx/taskbar.h"
33 #include "wx/dynlib.h"
34
35 #ifndef NIN_BALLOONTIMEOUT
36 #define NIN_BALLOONTIMEOUT 0x0404
37 #define NIN_BALLOONUSERCLICK 0x0405
38 #endif
39
40 #ifndef NIM_SETVERSION
41 #define NIM_SETVERSION 0x00000004
42 #endif
43
44 #ifndef NIF_INFO
45 #define NIF_INFO 0x00000010
46 #endif
47
48
49 // initialized on demand
50 static UINT gs_msgTaskbar = 0;
51 static UINT gs_msgRestartTaskbar = 0;
52
53
54 IMPLEMENT_DYNAMIC_CLASS(wxTaskBarIcon, wxEvtHandler)
55
56 // ============================================================================
57 // implementation
58 // ============================================================================
59
60 // wrapper around Shell_NotifyIcon(): this function is not present in Win95
61 // shell32.dll so load it dynamically to allow programs using wxTaskBarIcon to
62 // start under this OS
63 static BOOL wxShellNotifyIcon(DWORD dwMessage, NOTIFYICONDATA *pData)
64 {
65 #if wxUSE_DYNLIB_CLASS
66 typedef BOOL (WINAPI *Shell_NotifyIcon_t)(DWORD, NOTIFYICONDATA *);
67
68 static Shell_NotifyIcon_t s_pfnShell_NotifyIcon = NULL;
69 static bool s_initialized = false;
70 if ( !s_initialized )
71 {
72 s_initialized = true;
73
74 wxLogNull noLog;
75 wxDynamicLibrary dllShell("shell32.dll");
76 if ( dllShell.IsLoaded() )
77 {
78 wxDL_INIT_FUNC_AW(s_pfn, Shell_NotifyIcon, dllShell);
79 }
80
81 // NB: it's ok to destroy dllShell here, we link to shell32.dll
82 // implicitly so it won't be unloaded
83 }
84
85 return s_pfnShell_NotifyIcon ? (*s_pfnShell_NotifyIcon)(dwMessage, pData)
86 : FALSE;
87 #else // !wxUSE_DYNLIB_CLASS
88 return Shell_NotifyIcon(dwMessage, pData);
89 #endif // wxUSE_DYNLIB_CLASS/!wxUSE_DYNLIB_CLASS
90 }
91
92 // ----------------------------------------------------------------------------
93 // wxTaskBarIconWindow: helper window
94 // ----------------------------------------------------------------------------
95
96 // NB: this class serves two purposes:
97 // 1. win32 needs a HWND associated with taskbar icon, this provides it
98 // 2. we need wxTopLevelWindow so that the app doesn't exit when
99 // last frame is closed but there still is a taskbar icon
100 class wxTaskBarIconWindow : public wxFrame
101 {
102 public:
103 wxTaskBarIconWindow(wxTaskBarIcon *icon)
104 : wxFrame(NULL, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0),
105 m_icon(icon)
106 {
107 }
108
109 WXLRESULT MSWWindowProc(WXUINT msg,
110 WXWPARAM wParam, WXLPARAM lParam)
111 {
112 if (msg == gs_msgRestartTaskbar || msg == gs_msgTaskbar)
113 {
114 return m_icon->WindowProc(msg, wParam, lParam);
115 }
116 else
117 {
118 return wxFrame::MSWWindowProc(msg, wParam, lParam);
119 }
120 }
121
122 private:
123 wxTaskBarIcon *m_icon;
124 };
125
126
127 // ----------------------------------------------------------------------------
128 // NotifyIconData: wrapper around NOTIFYICONDATA
129 // ----------------------------------------------------------------------------
130
131 struct NotifyIconData : public NOTIFYICONDATA
132 {
133 NotifyIconData(WXHWND hwnd)
134 {
135 memset(this, 0, sizeof(NOTIFYICONDATA));
136 cbSize = sizeof(NOTIFYICONDATA);
137 hWnd = (HWND) hwnd;
138 uCallbackMessage = gs_msgTaskbar;
139 uFlags = NIF_MESSAGE;
140
141 // we use the same id for all taskbar icons as we don't need it to
142 // distinguish between them
143 uID = 99;
144 }
145 };
146
147 // ----------------------------------------------------------------------------
148 // wxTaskBarIcon
149 // ----------------------------------------------------------------------------
150
151 wxTaskBarIcon::wxTaskBarIcon()
152 {
153 m_win = NULL;
154 m_iconAdded = false;
155 RegisterWindowMessages();
156 }
157
158 wxTaskBarIcon::~wxTaskBarIcon()
159 {
160 if ( m_iconAdded )
161 RemoveIcon();
162
163 if ( m_win )
164 {
165 // we must use delete and not Destroy() here because the latter will
166 // only schedule the window to be deleted during the next idle event
167 // processing but we may not get any idle events if there are no other
168 // windows left in the program
169 delete m_win;
170 }
171 }
172
173 // Operations
174 bool wxTaskBarIcon::SetIcon(const wxIcon& icon, const wxString& tooltip)
175 {
176 // NB: we have to create the window lazily because of backward compatibility,
177 // old applications may create a wxTaskBarIcon instance before wxApp
178 // is initialized (as samples/taskbar used to do)
179 if (!m_win)
180 {
181 m_win = new wxTaskBarIconWindow(this);
182 }
183
184 m_icon = icon;
185 m_strTooltip = tooltip;
186
187 NotifyIconData notifyData(GetHwndOf(m_win));
188
189 if (icon.Ok())
190 {
191 notifyData.uFlags |= NIF_ICON;
192 notifyData.hIcon = GetHiconOf(icon);
193 }
194
195 // set NIF_TIP even for an empty tooltip: otherwise it would be impossible
196 // to remove an existing tooltip using this function
197 notifyData.uFlags |= NIF_TIP;
198 if ( !tooltip.empty() )
199 {
200 wxStrncpy(notifyData.szTip, tooltip.wx_str(), WXSIZEOF(notifyData.szTip));
201 }
202
203 bool ok = wxShellNotifyIcon(m_iconAdded ? NIM_MODIFY
204 : NIM_ADD, &notifyData) != 0;
205
206 if ( !m_iconAdded && ok )
207 m_iconAdded = true;
208
209 return ok;
210 }
211
212 #if wxUSE_TASKBARICON_BALLOONS
213
214 bool
215 wxTaskBarIcon::ShowBalloon(const wxString& title,
216 const wxString& text,
217 unsigned msec,
218 int flags)
219 {
220 wxCHECK_MSG( m_iconAdded, false,
221 _T("can't be used before the icon is created") );
222
223 const HWND hwnd = GetHwndOf(m_win);
224
225 // we need to enable version 5.0 behaviour to receive notifications about
226 // the balloon disappearance
227 NotifyIconData notifyData(hwnd);
228 notifyData.uFlags = 0;
229 notifyData.uVersion = 3 /* NOTIFYICON_VERSION for Windows XP */;
230
231 wxShellNotifyIcon(NIM_SETVERSION, &notifyData);
232
233
234 // do show the balloon now
235 notifyData = NotifyIconData(hwnd);
236 notifyData.uFlags |= NIF_INFO;
237 notifyData.uTimeout = msec;
238 wxStrncpy(notifyData.szInfo, text.wx_str(), WXSIZEOF(notifyData.szInfo));
239 wxStrncpy(notifyData.szInfoTitle, title.wx_str(),
240 WXSIZEOF(notifyData.szInfoTitle));
241
242 if ( flags & wxICON_INFORMATION )
243 notifyData.dwInfoFlags |= NIIF_INFO;
244 else if ( flags & wxICON_WARNING )
245 notifyData.dwInfoFlags |= NIIF_WARNING;
246 else if ( flags & wxICON_ERROR )
247 notifyData.dwInfoFlags |= NIIF_ERROR;
248
249 return wxShellNotifyIcon(NIM_MODIFY, &notifyData) != 0;
250 }
251
252 #endif // wxUSE_TASKBARICON_BALLOONS
253
254 bool wxTaskBarIcon::RemoveIcon()
255 {
256 if (!m_iconAdded)
257 return false;
258
259 m_iconAdded = false;
260
261 NotifyIconData notifyData(GetHwndOf(m_win));
262
263 return wxShellNotifyIcon(NIM_DELETE, &notifyData) != 0;
264 }
265
266 #if wxUSE_MENUS
267 bool wxTaskBarIcon::PopupMenu(wxMenu *menu)
268 {
269 wxASSERT_MSG( m_win != NULL, _T("taskbar icon not initialized") );
270
271 static bool s_inPopup = false;
272
273 if (s_inPopup)
274 return false;
275
276 s_inPopup = true;
277
278 int x, y;
279 wxGetMousePosition(&x, &y);
280
281 m_win->Move(x, y);
282
283 m_win->PushEventHandler(this);
284
285 menu->UpdateUI();
286
287 // the SetForegroundWindow() and PostMessage() calls are needed to work
288 // around Win32 bug with the popup menus shown for the notifications as
289 // documented at http://support.microsoft.com/kb/q135788/
290 ::SetForegroundWindow(GetHwndOf(m_win));
291
292 bool rval = m_win->PopupMenu(menu, 0, 0);
293
294 ::PostMessage(GetHwndOf(m_win), WM_NULL, 0, 0L);
295
296 m_win->PopEventHandler(false);
297
298 s_inPopup = false;
299
300 return rval;
301 }
302 #endif // wxUSE_MENUS
303
304 void wxTaskBarIcon::RegisterWindowMessages()
305 {
306 static bool s_registered = false;
307
308 if ( !s_registered )
309 {
310 // Taskbar restart msg will be sent to us if the icon needs to be redrawn
311 gs_msgRestartTaskbar = RegisterWindowMessage(wxT("TaskbarCreated"));
312
313 // Also register the taskbar message here
314 gs_msgTaskbar = ::RegisterWindowMessage(wxT("wxTaskBarIconMessage"));
315
316 s_registered = true;
317 }
318 }
319
320 // ----------------------------------------------------------------------------
321 // wxTaskBarIcon window proc
322 // ----------------------------------------------------------------------------
323
324 long wxTaskBarIcon::WindowProc(unsigned int msg,
325 unsigned int WXUNUSED(wParam),
326 long lParam)
327 {
328 if ( msg == gs_msgRestartTaskbar ) // does the icon need to be redrawn?
329 {
330 m_iconAdded = false;
331 SetIcon(m_icon, m_strTooltip);
332 return 0;
333 }
334
335 // this function should only be called for gs_msg(Restart)Taskbar messages
336 wxASSERT( msg == gs_msgTaskbar );
337
338 wxEventType eventType = 0;
339 switch ( lParam )
340 {
341 case WM_LBUTTONDOWN:
342 eventType = wxEVT_TASKBAR_LEFT_DOWN;
343 break;
344
345 case WM_LBUTTONUP:
346 eventType = wxEVT_TASKBAR_LEFT_UP;
347 break;
348
349 case WM_RBUTTONDOWN:
350 eventType = wxEVT_TASKBAR_RIGHT_DOWN;
351 break;
352
353 case WM_RBUTTONUP:
354 eventType = wxEVT_TASKBAR_RIGHT_UP;
355 break;
356
357 case WM_LBUTTONDBLCLK:
358 eventType = wxEVT_TASKBAR_LEFT_DCLICK;
359 break;
360
361 case WM_RBUTTONDBLCLK:
362 eventType = wxEVT_TASKBAR_RIGHT_DCLICK;
363 break;
364
365 case WM_MOUSEMOVE:
366 eventType = wxEVT_TASKBAR_MOVE;
367 break;
368
369 case NIN_BALLOONTIMEOUT:
370 eventType = wxEVT_TASKBAR_BALLOON_TIMEOUT;
371 break;
372
373 case NIN_BALLOONUSERCLICK:
374 eventType = wxEVT_TASKBAR_BALLOON_CLICK;
375 break;
376 }
377
378 if ( eventType )
379 {
380 wxTaskBarIconEvent event(eventType, this);
381
382 ProcessEvent(event);
383 }
384
385 return 0;
386 }
387
388 #endif // wxUSE_TASKBARICON
389