wxCocoa: Added wxTaskBarIcon
[wxWidgets.git] / src / cocoa / taskbar.mm
1 /////////////////////////////////////////////////////////////////////////
2 // File:        src/cocoa/taskbar.mm
3 // Purpose:     Implements wxTaskBarIcon class
4 // Author:      David Elliott
5 // Modified by:
6 // Created:     2004/01/24
7 // RCS-ID:      $Id$
8 // Copyright:   (c) 2004 David Elliott
9 // Licence:     wxWindows licence
10 /////////////////////////////////////////////////////////////////////////
11
12 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
13 #pragma implementation "taskbar.h"
14 #endif
15
16 #include "wx/wxprec.h"
17 #ifdef wxHAS_TASK_BAR_ICON
18
19 #ifndef WX_PRECOMP
20     #include "wx/menu.h"
21     #include "wx/icon.h"
22     #include "wx/log.h"
23     #include "wx/dcclient.h"
24 #endif
25
26 #include "wx/taskbar.h"
27
28 #import <AppKit/NSApplication.h>
29 #import <AppKit/NSImage.h>
30 #import <AppKit/NSMenu.h>
31 #import <AppKit/NSMenuItem.h>
32 #import <AppKit/NSStatusBar.h>
33 #import <AppKit/NSStatusItem.h>
34 #import <AppKit/NSView.h>
35 #import <Foundation/NSArray.h>
36 #import <Foundation/NSEnumerator.h>
37
38 #import <AppKit/NSEvent.h>
39 #import <AppKit/NSWindow.h>
40 #import <AppKit/NSGraphicsContext.h>
41
42 #include "wx/cocoa/NSApplication.h"
43 #include "wx/cocoa/autorelease.h"
44
45 class wxTaskBarIconWindow;
46
47 // ============================================================================
48 // wxTaskBarIconCocoaImpl
49 //     Base class for the various Cocoa implementations.
50 // ============================================================================
51 class wxTaskBarIconCocoaImpl
52 {
53 public:
54     wxTaskBarIconCocoaImpl(wxTaskBarIcon *taskBarIcon)
55     :   m_taskBarIcon(taskBarIcon)
56     ,   m_iconWindow(NULL)
57     {}
58     virtual bool SetIcon(const wxIcon& icon, const wxString& tooltip = wxEmptyString) = 0;
59     virtual bool RemoveIcon() = 0;
60     virtual bool PopupMenu(wxMenu *menu) = 0;
61     virtual ~wxTaskBarIconCocoaImpl();
62     inline wxTaskBarIcon* GetTaskBarIcon() { return m_taskBarIcon; }
63 protected:
64     wxTaskBarIcon *m_taskBarIcon;
65     wxTaskBarIconWindow *m_iconWindow;
66 private:
67     wxTaskBarIconCocoaImpl();
68 };
69
70 // ============================================================================
71 // wxTaskBarIconDockImpl
72 //     An implementation using the Dock icon.
73 // ============================================================================
74 class wxTaskBarIconDockImpl: public wxTaskBarIconCocoaImpl
75 {
76 public:
77     wxTaskBarIconDockImpl(wxTaskBarIcon *taskBarIcon);
78     virtual ~wxTaskBarIconDockImpl();
79     virtual bool SetIcon(const wxIcon& icon, const wxString& tooltip = wxEmptyString);
80     virtual bool RemoveIcon();
81     virtual bool PopupMenu(wxMenu *menu);
82
83     static WX_NSMenu CocoaGetDockNSMenu();
84 protected:
85     WX_NSMenu CocoaDoGetDockNSMenu();
86     WX_NSImage m_originalDockIcon;
87     // There can be only one Dock icon, so make sure we keep it that way
88     static wxTaskBarIconDockImpl *sm_dockIcon;
89 private:
90     wxTaskBarIconDockImpl();
91 };
92
93 // ============================================================================
94 // wxTaskBarIconCustomStatusItemImpl
95 //     An implementation using an NSStatusItem with a custom NSView
96 // ============================================================================
97 class wxTaskBarIconCustomStatusItemImpl: public wxTaskBarIconCocoaImpl
98 {
99 public:
100     wxTaskBarIconCustomStatusItemImpl(wxTaskBarIcon *taskBarIcon);
101     virtual ~wxTaskBarIconCustomStatusItemImpl();
102     virtual bool SetIcon(const wxIcon& icon, const wxString& tooltip = wxEmptyString);
103     virtual bool RemoveIcon();
104     virtual bool PopupMenu(wxMenu *menu);
105 protected:
106     NSStatusItem *m_cocoaNSStatusItem;
107 private:
108     wxTaskBarIconCustomStatusItemImpl();
109 };
110
111 // ============================================================================
112 // wxTaskBarIconWindow
113 //     Used by all implementations to forward events from the wxMenu
114 // ============================================================================
115 class wxTaskBarIconWindow: public wxWindow
116 {
117     DECLARE_EVENT_TABLE()
118 public:
119     wxTaskBarIconWindow(wxTaskBarIconCocoaImpl *taskBarIconImpl)
120     :   wxWindow(NULL,-1)
121     ,   m_taskBarIconImpl(taskBarIconImpl)
122     {   wxASSERT(m_taskBarIconImpl); }
123
124     void OnMenuEvent(wxCommandEvent& event);
125 protected:
126     wxTaskBarIconCocoaImpl *m_taskBarIconImpl;
127 };
128
129 // ============================================================================
130 // wxTaskBarIconWindowCustom
131 //     Used by the CustomStatusIcon implementation for the custom NSView.
132 // ============================================================================
133 class wxTaskBarIconWindowCustom: public wxTaskBarIconWindow
134 {
135     DECLARE_EVENT_TABLE()
136 public:
137     wxTaskBarIconWindowCustom(wxTaskBarIconCocoaImpl *taskBarIconImpl)
138     :   wxTaskBarIconWindow(taskBarIconImpl)
139     {}
140     void SetIcon(const wxIcon& icon)
141     {   m_icon = icon; }
142     void OnMouseEvent(wxMouseEvent &event);
143     void OnPaint(wxPaintEvent &event);
144 protected:
145     wxIcon m_icon;
146 };
147
148 // ============================================================================
149 // wxTaskBarIcon implementation
150 //     The facade class.
151 // ============================================================================
152 IMPLEMENT_DYNAMIC_CLASS(wxTaskBarIcon, wxEvtHandler)
153
154 wxTaskBarIcon::wxTaskBarIcon(wxTaskBarIconType iconType)
155 {
156     if(iconType == DOCK)
157         m_impl = new wxTaskBarIconDockImpl(this);
158     else if(iconType == CUSTOM_STATUSITEM)
159         m_impl = new wxTaskBarIconCustomStatusItemImpl(this);
160     else
161     {   m_impl = NULL;
162         wxFAIL_MSG(wxT("Invalid wxTaskBarIcon type"));
163     }
164 }
165
166 wxTaskBarIcon::~wxTaskBarIcon()
167 {
168     delete m_impl;
169 }
170
171 // Operations
172 bool wxTaskBarIcon::SetIcon(const wxIcon& icon, const wxString& tooltip)
173 {
174     return m_impl->SetIcon(icon,tooltip);
175 }
176
177 bool wxTaskBarIcon::RemoveIcon()
178 {
179     return m_impl->RemoveIcon();
180 }
181
182 bool wxTaskBarIcon::PopupMenu(wxMenu *menu)
183 {
184     return m_impl->PopupMenu(menu);
185 }
186
187 // ============================================================================
188 // wxTaskBarIconCocoaImpl
189 // ============================================================================
190
191 #if 0
192 wxTaskBarIconCocoaImpl::wxTaskBarIconCocoaImpl(wxTaskBarIcon *taskBarIcon)
193 :   m_taskBarIcon(taskBarIcon)
194 ,   m_iconWindow(NULL)
195 {
196 }
197 #endif
198
199 wxTaskBarIconCocoaImpl::~wxTaskBarIconCocoaImpl()
200 {
201 //    wxAutoNSAutoreleasePool pool;
202     delete m_iconWindow;
203 }
204
205 // ============================================================================
206 // wxTaskBarIconDockImpl
207 // ============================================================================
208 wxTaskBarIconDockImpl *wxTaskBarIconDockImpl::sm_dockIcon = NULL;
209
210 wxTaskBarIconDockImpl::wxTaskBarIconDockImpl(wxTaskBarIcon *taskBarIcon)
211 :   wxTaskBarIconCocoaImpl(taskBarIcon)
212 {
213     m_originalDockIcon = nil;
214     wxASSERT_MSG(!sm_dockIcon,"You should never have more than one dock icon!");
215     sm_dockIcon = this;
216 }
217
218 wxTaskBarIconDockImpl::~wxTaskBarIconDockImpl()
219 {
220 //    wxAutoNSAutoreleasePool pool;
221     if(sm_dockIcon == this)
222         sm_dockIcon = NULL;
223 }
224
225 WX_NSMenu wxTaskBarIconDockImpl::CocoaGetDockNSMenu()
226 {
227     if(sm_dockIcon)
228         return sm_dockIcon->CocoaDoGetDockNSMenu();
229     return nil;
230 }
231
232 WX_NSMenu wxTaskBarIconDockImpl::CocoaDoGetDockNSMenu()
233 {
234     wxMenu *dockMenu = m_taskBarIcon->CreatePopupMenu();
235     if(!dockMenu)
236         return nil;
237     if(!m_iconWindow)
238         m_iconWindow = new wxTaskBarIconWindow(this);
239     dockMenu->SetInvokingWindow(m_iconWindow);
240     dockMenu->UpdateUI();
241     dockMenu->SetCocoaDeletes(true);
242     return dockMenu->GetNSMenu();
243 }
244
245 bool wxTaskBarIconDockImpl::SetIcon(const wxIcon& icon, const wxString& tooltip)
246 {
247     wxAutoNSAutoreleasePool pool;
248     m_originalDockIcon = [[[NSApplication sharedApplication] applicationIconImage] retain];
249     [[NSApplication sharedApplication] setApplicationIconImage:icon.GetNSImage()];
250     return true;
251 }
252
253 bool wxTaskBarIconDockImpl::RemoveIcon()
254 {
255     [[NSApplication sharedApplication] setApplicationIconImage:m_originalDockIcon];
256     [m_originalDockIcon release];
257     return true;
258 }
259
260 bool wxTaskBarIconDockImpl::PopupMenu(wxMenu *menu)
261 {
262     wxFAIL_MSG(wxT("You cannot force the Dock icon menu to popup"));
263     return false;
264 }
265
266
267 // ============================================================================
268 // wxTaskBarIconCustomStatusItemImpl
269 // ============================================================================
270 wxTaskBarIconCustomStatusItemImpl::wxTaskBarIconCustomStatusItemImpl(wxTaskBarIcon *taskBarIcon)
271 :   wxTaskBarIconCocoaImpl(taskBarIcon)
272 {
273     m_cocoaNSStatusItem = nil;
274 }
275
276 wxTaskBarIconCustomStatusItemImpl::~wxTaskBarIconCustomStatusItemImpl()
277 {
278 }
279
280 bool wxTaskBarIconCustomStatusItemImpl::SetIcon(const wxIcon& icon, const wxString& tooltip)
281 {
282     wxAutoNSAutoreleasePool pool;
283     if(!m_cocoaNSStatusItem)
284     {
285         m_cocoaNSStatusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
286         [m_cocoaNSStatusItem retain];
287     }
288     if(!m_iconWindow)
289         m_iconWindow= new wxTaskBarIconWindowCustom(this);
290     static_cast<wxTaskBarIconWindowCustom*>(m_iconWindow)->SetIcon(icon);
291     // FIXME: no less than 10 because most icon types don't work yet
292     // and this allows us to see how task bar icons would work
293     [m_iconWindow->GetNSView() setFrame:NSMakeRect(0.0,0.0,wxMax(10,icon.GetWidth()),[[NSStatusBar systemStatusBar] thickness])];
294     [m_cocoaNSStatusItem setView:m_iconWindow->GetNSView()];
295     return true;
296 }
297
298 bool wxTaskBarIconCustomStatusItemImpl::RemoveIcon()
299 {
300     [m_cocoaNSStatusItem release];
301     m_cocoaNSStatusItem = nil;
302     delete m_iconWindow;
303     m_iconWindow = NULL;
304     return true;
305 }
306
307 bool wxTaskBarIconCustomStatusItemImpl::PopupMenu(wxMenu *menu)
308 {
309     wxASSERT(menu);
310     menu->SetInvokingWindow(m_iconWindow);
311     menu->UpdateUI();
312
313     if([m_cocoaNSStatusItem respondsToSelector:@selector(popUpStatusItemMenu:)])
314     {   // OS X >= 10.3
315         [m_cocoaNSStatusItem popUpStatusItemMenu:menu->GetNSMenu()];
316     }
317     else
318     {   // pretty good fake for OS X < 10.3
319         NSEvent *nsevent = [NSEvent mouseEventWithType:NSLeftMouseDown
320             location:NSMakePoint(-1.0,-4.0) modifierFlags:0 timestamp:0
321             windowNumber:[[m_iconWindow->GetNSView() window] windowNumber]
322             context:[NSGraphicsContext currentContext]
323             eventNumber:0 clickCount:1 pressure:0.0];
324         [NSMenu popUpContextMenu:menu->GetNSMenu() withEvent:nsevent forView:m_iconWindow->GetNSView()];
325     }
326     menu->SetInvokingWindow(NULL);
327     return true;
328 }
329
330 // ============================================================================
331 // wxTaskBarIconWindow
332 // ============================================================================
333 BEGIN_EVENT_TABLE(wxTaskBarIconWindow, wxWindow)
334     EVT_MENU(-1, wxTaskBarIconWindow::OnMenuEvent)
335 END_EVENT_TABLE()
336
337 void wxTaskBarIconWindow::OnMenuEvent(wxCommandEvent &event)
338 {
339     m_taskBarIconImpl->GetTaskBarIcon()->ProcessEvent(event);
340 }
341
342 // ============================================================================
343 // wxTaskBarIconWindowCustom
344 // ============================================================================
345 BEGIN_EVENT_TABLE(wxTaskBarIconWindowCustom, wxTaskBarIconWindow)
346     EVT_MOUSE_EVENTS(wxTaskBarIconWindowCustom::OnMouseEvent)
347     EVT_PAINT(wxTaskBarIconWindowCustom::OnPaint)
348 END_EVENT_TABLE()
349
350 void wxTaskBarIconWindowCustom::OnMouseEvent(wxMouseEvent &event)
351 {
352     wxEventType tbEventType = 0;
353     if(event.GetEventType() == wxEVT_MOTION)
354         tbEventType = wxEVT_TASKBAR_MOVE;
355     else if(event.GetEventType() == wxEVT_LEFT_DOWN)
356         tbEventType = wxEVT_TASKBAR_LEFT_DOWN;
357     else if(event.GetEventType() == wxEVT_LEFT_UP)
358         tbEventType = wxEVT_TASKBAR_LEFT_UP;
359     else if(event.GetEventType() == wxEVT_RIGHT_DOWN)
360         tbEventType = wxEVT_TASKBAR_RIGHT_DOWN;
361     else if(event.GetEventType() == wxEVT_RIGHT_UP)
362         tbEventType = wxEVT_TASKBAR_RIGHT_UP;
363     else if(event.GetEventType() == wxEVT_LEFT_DCLICK)
364         tbEventType = wxEVT_TASKBAR_LEFT_DCLICK;
365     else if(event.GetEventType() == wxEVT_RIGHT_DCLICK)
366         tbEventType = wxEVT_TASKBAR_RIGHT_DCLICK;
367     else
368         return;
369     wxTaskBarIconEvent tbiEvent(tbEventType,m_taskBarIconImpl->GetTaskBarIcon());
370     m_taskBarIconImpl->GetTaskBarIcon()->ProcessEvent(tbiEvent);
371 }
372
373 void wxTaskBarIconWindowCustom::OnPaint(wxPaintEvent &event)
374 {
375     wxPaintDC dc(this);
376     // FIXME: This is a temporary hack until we can see real icons
377     dc.SetBackground(wxBrush(*wxBLUE));
378     dc.Clear();
379     dc.DrawIcon(m_icon,0,0);
380 }
381
382 // ============================================================================
383 // wxTaskBarIconNSApplicationDelegateCategory
384 // ============================================================================
385
386 // This neatly solves the problem of DLL separation.  If the wxAdvanced
387 // library (which this file is part of) is loaded then this category is
388 // defined and we get dock menu behavior without app.mm ever having to
389 // know we exist.  C++ did sucketh so. :-)
390
391 @interface wxNSApplicationDelegate(wxTaskBarIconNSApplicationDelegateCategory)
392 - (NSMenu*)applicationDockMenu:(NSApplication *)sender;
393 @end
394
395 @implementation wxNSApplicationDelegate(wxTaskBarIconNSApplicationDelegateCategory)
396 - (NSMenu*)applicationDockMenu:(NSApplication *)sender
397 {
398     return wxTaskBarIconDockImpl::CocoaGetDockNSMenu();
399 }
400 @end
401
402 #endif //def wxHAS_TASK_BAR_ICON