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