1/////////////////////////////////////////////////////////////////////////
2// File:        src/osx/cocoa/taskbar.mm
3// Purpose:     Implements wxTaskBarIcon class
4// Author:      David Elliott, Stefan Csomor
5// Modified by:
6// Created:     2004/01/24
7// Copyright:   (c) 2004 David Elliott, Stefan Csomor
8// Licence:     wxWindows licence
9/////////////////////////////////////////////////////////////////////////
10
11#include "wx/wxprec.h"
12#if wxUSE_TASKBARICON
13
14#ifndef WX_PRECOMP
15    #include "wx/toplevel.h"
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#include "wx/osx/private.h"
25
26class wxTaskBarIconWindow;
27
28//-----------------------------------------------------------------------------
29//
30//  wxTaskBarIconWindow
31//
32//  Event handler for menus
33//  NB: Since wxWindows in Mac HAVE to have parents we need this to be
34//  a top level window...
35//-----------------------------------------------------------------------------
36
37class wxTaskBarIconWindow : public wxTopLevelWindow
38{
39public:
40    wxTaskBarIconWindow(wxTaskBarIconImpl *impl);
41
42    void OnMenuEvent(wxCommandEvent& event);
43    void OnUpdateUIEvent(wxUpdateUIEvent& event);
44
45private:
46    wxTaskBarIconImpl *m_impl;
47    DECLARE_EVENT_TABLE()
48};
49
50// ============================================================================
51// wxTaskBarIconImpl
52//     Base class for the various Cocoa implementations.
53// ============================================================================
54class wxTaskBarIconImpl
55{
56public:
57    wxTaskBarIconImpl(wxTaskBarIcon *taskBarIcon);
58
59    virtual bool IsStatusItem() const { return false; }
60
61    virtual bool SetIcon(const wxIcon& icon, const wxString& tooltip = wxEmptyString) = 0;
62    virtual bool RemoveIcon() = 0;
63
64    bool IsIconInstalled() const { return m_icon.IsOk(); }
65
66    virtual bool PopupMenu(wxMenu *menu) = 0;
67    virtual ~wxTaskBarIconImpl();
68    inline wxTaskBarIcon* GetTaskBarIcon() { return m_taskBarIcon; }
69    wxMenu * CreatePopupMenu()
70    { return m_taskBarIcon->CreatePopupMenu(); }
71
72    wxDECLARE_NO_COPY_CLASS(wxTaskBarIconImpl);
73
74protected:
75    wxTaskBarIcon *m_taskBarIcon;
76    wxBitmap m_icon;
77    wxTaskBarIconWindow *m_eventWindow;
78private:
79    wxTaskBarIconImpl();
80};
81
82// ============================================================================
83// wxTaskBarIconDockImpl
84//     An implementation using the Dock icon.
85// ============================================================================
86class wxTaskBarIconDockImpl: public wxTaskBarIconImpl
87{
88public:
89    wxTaskBarIconDockImpl(wxTaskBarIcon *taskBarIcon);
90    virtual ~wxTaskBarIconDockImpl();
91    virtual bool SetIcon(const wxIcon& icon, const wxString& tooltip = wxEmptyString);
92    virtual bool RemoveIcon();
93    virtual bool PopupMenu(wxMenu *menu);
94
95    static WX_NSMenu OSXGetDockHMenu();
96protected:
97    WX_NSMenu OSXDoGetDockHMenu();
98    // There can be only one Dock icon, so make sure we keep it that way
99    static wxTaskBarIconDockImpl *sm_dockIcon;
100private:
101    wxTaskBarIconDockImpl();
102    wxMenu             *m_pMenu;
103};
104
105class wxTaskBarIconCustomStatusItemImpl;
106
107@interface wxOSXStatusItemTarget : NSObject
108{
109    wxTaskBarIconCustomStatusItemImpl* impl;
110}
111@end
112
113// ============================================================================
114// wxTaskBarIconCustomStatusItemImpl
115//     An implementation using an NSStatusItem with a custom NSView
116// ============================================================================
117class wxTaskBarIconCustomStatusItemImpl: public wxTaskBarIconImpl
118{
119public:
120    wxTaskBarIconCustomStatusItemImpl(wxTaskBarIcon *taskBarIcon);
121    virtual ~wxTaskBarIconCustomStatusItemImpl();
122
123    virtual bool IsStatusItem() const { return true; }
124
125    virtual bool SetIcon(const wxIcon& icon, const wxString& tooltip = wxEmptyString);
126    virtual bool RemoveIcon();
127    virtual bool PopupMenu(wxMenu *menu);
128protected:
129    NSStatusItem *m_statusItem;
130    wxOSXStatusItemTarget *m_target;
131private:
132    wxTaskBarIconCustomStatusItemImpl();
133};
134
135// ============================================================================
136// wxTaskBarIcon implementation
137//     The facade class.
138// ============================================================================
139IMPLEMENT_DYNAMIC_CLASS(wxTaskBarIcon, wxEvtHandler)
140
141wxTaskBarIcon::wxTaskBarIcon(wxTaskBarIconType iconType)
142{
143    if(iconType == wxTBI_DOCK)
144        m_impl = new wxTaskBarIconDockImpl(this);
145    else if(iconType == wxTBI_CUSTOM_STATUSITEM)
146        m_impl = new wxTaskBarIconCustomStatusItemImpl(this);
147    else
148    {   m_impl = NULL;
149        wxFAIL_MSG(wxT("Invalid wxTaskBarIcon type"));
150    }
151}
152
153wxTaskBarIcon::~wxTaskBarIcon()
154{
155    if ( m_impl )
156    {
157        if ( m_impl->IsIconInstalled() )
158            m_impl->RemoveIcon();
159        delete m_impl;
160        m_impl = NULL;
161    }
162}
163
164bool wxTaskBarIcon::OSXIsStatusItem()
165{
166    if ( m_impl )
167        return m_impl->IsStatusItem();
168
169    return false;
170}
171
172// Operations
173
174bool wxTaskBarIcon::IsIconInstalled() const
175{
176    if ( m_impl )
177        return m_impl->IsIconInstalled();
178
179    return false;
180}
181
182bool wxTaskBarIcon::SetIcon(const wxIcon& icon, const wxString& tooltip)
183{
184    if ( m_impl )
185        return m_impl->SetIcon(icon,tooltip);
186
187    return false;
188}
189
190bool wxTaskBarIcon::RemoveIcon()
191{
192    if ( m_impl )
193        return m_impl->RemoveIcon();
194
195    return false;
196}
197
198bool wxTaskBarIcon::PopupMenu(wxMenu *menu)
199{
200    if ( m_impl )
201        return m_impl->PopupMenu(menu);
202
203    return false;
204}
205
206// ============================================================================
207// wxTaskBarIconImpl
208// ============================================================================
209
210wxTaskBarIconImpl::wxTaskBarIconImpl(wxTaskBarIcon* taskBarIcon)
211 : m_taskBarIcon(taskBarIcon), m_eventWindow(new wxTaskBarIconWindow(this))
212{
213}
214
215wxTaskBarIconImpl::~wxTaskBarIconImpl()
216{
217    delete m_eventWindow;
218}
219
220// ============================================================================
221// wxTaskBarIconDockImpl
222// ============================================================================
223wxTaskBarIconDockImpl *wxTaskBarIconDockImpl::sm_dockIcon = NULL;
224
225wxTaskBarIconDockImpl::wxTaskBarIconDockImpl(wxTaskBarIcon *taskBarIcon)
226:   wxTaskBarIconImpl(taskBarIcon)
227{
228    wxASSERT_MSG(!sm_dockIcon, wxT("You should never have more than one dock icon!"));
229    sm_dockIcon = this;
230    m_pMenu = NULL;
231}
232
233wxTaskBarIconDockImpl::~wxTaskBarIconDockImpl()
234{
235    if(sm_dockIcon == this)
236        sm_dockIcon = NULL;
237}
238
239WX_NSMenu wxTaskBarIconDockImpl::OSXGetDockHMenu()
240{
241    if(sm_dockIcon)
242        return sm_dockIcon->OSXDoGetDockHMenu();
243
244    return nil;
245}
246
247WX_NSMenu wxTaskBarIconDockImpl::OSXDoGetDockHMenu()
248{
249    wxMenu *dockMenu = CreatePopupMenu();
250
251    if(!dockMenu)
252        return nil;
253
254    wxDELETE(m_pMenu);
255
256    m_pMenu = dockMenu;
257
258    m_pMenu->SetInvokingWindow(m_eventWindow);
259
260    m_pMenu->UpdateUI();
261
262    return (WX_NSMenu)dockMenu->GetHMenu();
263}
264
265bool wxTaskBarIconDockImpl::SetIcon(const wxIcon& icon, const wxString& WXUNUSED(tooltip))
266{
267    m_icon.CopyFromIcon(icon);
268    [[NSApplication sharedApplication] setApplicationIconImage:m_icon.GetNSImage()];
269    return true;
270}
271
272bool wxTaskBarIconDockImpl::RemoveIcon()
273{
274    wxDELETE(m_pMenu);
275    m_icon = wxBitmap();
276    [[NSApplication sharedApplication] setApplicationIconImage:nil];
277    return true;
278}
279
280bool wxTaskBarIconDockImpl::PopupMenu(wxMenu *WXUNUSED(menu))
281{
282    wxFAIL_MSG(wxT("You cannot force the Dock icon menu to popup"));
283    return false;
284}
285
286@interface wxNSAppController(wxTaskBarIconNSApplicationDelegateCategory)
287- (NSMenu*)applicationDockMenu:(NSApplication *)sender;
288@end
289
290@implementation wxNSAppController(wxTaskBarIconNSApplicationDelegateCategory)
291- (NSMenu*)applicationDockMenu:(NSApplication *)sender
292{
293    wxUnusedVar(sender);
294
295    return wxTaskBarIconDockImpl::OSXGetDockHMenu();
296}
297@end
298
299// ============================================================================
300// wxTaskBarIconCustomStatusItemImpl
301// ============================================================================
302
303@implementation wxOSXStatusItemTarget
304
305- (void) clickedAction: (id) sender
306{
307    wxUnusedVar(sender);
308    wxMenu *menu = impl->CreatePopupMenu();
309    if (menu)
310    {
311        impl->PopupMenu(menu);
312        delete menu;
313    }
314}
315
316- (void)setImplementation: (wxTaskBarIconCustomStatusItemImpl *) theImplementation
317{
318    impl = theImplementation;
319}
320
321- (wxTaskBarIconCustomStatusItemImpl*) implementation
322{
323    return impl;
324}
325
326@end
327
328
329wxTaskBarIconCustomStatusItemImpl::wxTaskBarIconCustomStatusItemImpl(wxTaskBarIcon *taskBarIcon)
330:   wxTaskBarIconImpl(taskBarIcon)
331{
332    m_statusItem = nil;
333    m_target = nil;
334}
335
336wxTaskBarIconCustomStatusItemImpl::~wxTaskBarIconCustomStatusItemImpl()
337{
338}
339
340bool wxTaskBarIconCustomStatusItemImpl::SetIcon(const wxIcon& icon, const wxString& tooltip)
341{
342    if(!m_statusItem)
343    {
344        m_statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
345        [m_statusItem retain];
346
347        m_target = [[wxOSXStatusItemTarget alloc] init];
348        [m_target setImplementation:this];
349        [m_statusItem setHighlightMode:YES];
350        [m_statusItem setTarget:m_target];
351        [m_statusItem setAction:@selector(clickedAction:)];
352        [m_statusItem sendActionOn:NSLeftMouseDownMask];
353    }
354
355    m_icon.CopyFromIcon(icon);
356
357    // status item doesn't scale automatically
358    // first scale to optimal pixel resolution
359
360    int dimension = wxMax( m_icon.GetHeight(), m_icon.GetWidth() );
361    int target_dimension = 16 * wxOSXGetMainScreenContentScaleFactor();
362    if ( dimension > target_dimension )
363    {
364        wxImage img = m_icon.ConvertToImage();
365        int factor = (dimension+(target_dimension-1))/target_dimension;
366        m_icon = img.ShrinkBy(factor, factor);
367    }
368
369    NSImage* nsimage = m_icon.GetNSImage();
370    NSSize size = [nsimage size];
371
372    // then scale to optimal point resolution
373
374    dimension = wxMax(size.width,size.height);
375    if ( dimension > 16 )
376    {
377        int factor = (dimension+15)/16;
378        size.width /= factor;
379        size.height /= factor;
380        [nsimage setSize:size];
381    }
382    [m_statusItem setImage:nsimage];
383
384    wxCFStringRef cfTooltip(tooltip);
385    [m_statusItem setToolTip:cfTooltip.AsNSString()];
386    return true;
387}
388
389bool wxTaskBarIconCustomStatusItemImpl::RemoveIcon()
390{
391    [m_statusItem release];
392    m_statusItem = nil;
393    [m_target release];
394    m_target = nil;
395
396    m_icon = wxBitmap();
397
398    return true;
399}
400
401bool wxTaskBarIconCustomStatusItemImpl::PopupMenu(wxMenu *menu)
402{
403    wxASSERT(menu);
404
405    menu->SetInvokingWindow(m_eventWindow);
406    menu->UpdateUI();
407
408    [m_statusItem popUpStatusItemMenu:(NSMenu*)menu->GetHMenu()];
409
410    menu->SetInvokingWindow(NULL);
411    return true;
412}
413
414// ============================================================================
415// wxTaskBarIconWindow
416// ============================================================================
417
418BEGIN_EVENT_TABLE(wxTaskBarIconWindow, wxWindow)
419EVT_MENU(-1, wxTaskBarIconWindow::OnMenuEvent)
420EVT_UPDATE_UI(-1, wxTaskBarIconWindow::OnUpdateUIEvent)
421END_EVENT_TABLE()
422
423wxTaskBarIconWindow::wxTaskBarIconWindow(wxTaskBarIconImpl *impl)
424: m_impl(impl)
425{
426}
427
428void wxTaskBarIconWindow::OnMenuEvent(wxCommandEvent& event)
429{
430    m_impl->GetTaskBarIcon()->ProcessEvent(event);
431}
432
433void wxTaskBarIconWindow::OnUpdateUIEvent(wxUpdateUIEvent& event)
434{
435    m_impl->GetTaskBarIcon()->ProcessEvent(event);
436}
437
438#endif //def wxHAS_TASK_BAR_ICON
439