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