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