/////////////////////////////////////////////////////////////////////////
// File:        src/cocoa/taskbar.mm
// Purpose:     Implements wxTaskBarIcon class
// Author:      David Elliott
// Modified by:
// Created:     2004/01/24
// Copyright:   (c) 2004 David Elliott
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////

#include "wx/wxprec.h"
#ifdef wxHAS_TASK_BAR_ICON

#ifndef WX_PRECOMP
    #include "wx/menu.h"
    #include "wx/icon.h"
    #include "wx/log.h"
    #include "wx/dcclient.h"
#endif

#include "wx/taskbar.h"

#import <AppKit/NSApplication.h>
#import <AppKit/NSImage.h>
#import <AppKit/NSMenu.h>
#import <AppKit/NSMenuItem.h>
#import <AppKit/NSStatusBar.h>
#import <AppKit/NSStatusItem.h>
#import <AppKit/NSView.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSEnumerator.h>

#import <AppKit/NSEvent.h>
#import <AppKit/NSWindow.h>
#import <AppKit/NSGraphicsContext.h>

#include "wx/cocoa/NSApplication.h"
#include "wx/cocoa/autorelease.h"

// A category for methods that are only present in Panther's SDK
@interface NSStatusItem(wxNSStatusItemPrePantherCompatibility)
- (void)popUpStatusItemMenu:(NSMenu *)menu;
@end

class wxTaskBarIconWindow;

// ============================================================================
// wxTaskBarIconCocoaImpl
//     Base class for the various Cocoa implementations.
// ============================================================================
class wxTaskBarIconCocoaImpl
{
public:
    wxTaskBarIconCocoaImpl(wxTaskBarIcon *taskBarIcon)
    :   m_taskBarIcon(taskBarIcon)
    ,   m_iconWindow(NULL)
    {}
    virtual bool SetIcon(const wxIcon& icon, const wxString& tooltip = wxEmptyString) = 0;
    virtual bool RemoveIcon() = 0;
    virtual bool PopupMenu(wxMenu *menu) = 0;
    virtual ~wxTaskBarIconCocoaImpl();
    inline wxTaskBarIcon* GetTaskBarIcon() { return m_taskBarIcon; }
protected:
    inline wxMenu* CreatePopupMenu()
    {   wxASSERT(m_taskBarIcon);
        return m_taskBarIcon->CreatePopupMenu();
    }
    wxTaskBarIcon *m_taskBarIcon;
    wxTaskBarIconWindow *m_iconWindow;
private:
    wxTaskBarIconCocoaImpl();
};

// ============================================================================
// wxTaskBarIconDockImpl
//     An implementation using the Dock icon.
// ============================================================================
class wxTaskBarIconDockImpl: public wxTaskBarIconCocoaImpl
{
public:
    wxTaskBarIconDockImpl(wxTaskBarIcon *taskBarIcon);
    virtual ~wxTaskBarIconDockImpl();
    virtual bool SetIcon(const wxIcon& icon, const wxString& tooltip = wxEmptyString);
    virtual bool RemoveIcon();
    virtual bool PopupMenu(wxMenu *menu);

    static WX_NSMenu CocoaGetDockNSMenu();
protected:
    WX_NSMenu CocoaDoGetDockNSMenu();
    WX_NSImage m_originalDockIcon;
    // There can be only one Dock icon, so make sure we keep it that way
    static wxTaskBarIconDockImpl *sm_dockIcon;
private:
    wxTaskBarIconDockImpl();
};

// ============================================================================
// wxTaskBarIconCustomStatusItemImpl
//     An implementation using an NSStatusItem with a custom NSView
// ============================================================================
class wxTaskBarIconCustomStatusItemImpl: public wxTaskBarIconCocoaImpl
{
public:
    wxTaskBarIconCustomStatusItemImpl(wxTaskBarIcon *taskBarIcon);
    virtual ~wxTaskBarIconCustomStatusItemImpl();
    virtual bool SetIcon(const wxIcon& icon, const wxString& tooltip = wxEmptyString);
    virtual bool RemoveIcon();
    virtual bool PopupMenu(wxMenu *menu);
protected:
    NSStatusItem *m_cocoaNSStatusItem;
private:
    wxTaskBarIconCustomStatusItemImpl();
};

// ============================================================================
// wxTaskBarIconWindow
//     Used by all implementations to forward events from the wxMenu
// ============================================================================
class wxTaskBarIconWindow: public wxWindow
{
    DECLARE_EVENT_TABLE()
public:
    wxTaskBarIconWindow(wxTaskBarIconCocoaImpl *taskBarIconImpl)
    :   wxWindow(NULL,-1)
    ,   m_taskBarIconImpl(taskBarIconImpl)
    {   wxASSERT(m_taskBarIconImpl); }

    void OnMenuEvent(wxCommandEvent& event);
protected:
    wxTaskBarIconCocoaImpl *m_taskBarIconImpl;
};

// ============================================================================
// wxTaskBarIconWindowCustom
//     Used by the CustomStatusIcon implementation for the custom NSView.
// ============================================================================
class wxTaskBarIconWindowCustom: public wxTaskBarIconWindow
{
    DECLARE_EVENT_TABLE()
public:
    wxTaskBarIconWindowCustom(wxTaskBarIconCocoaImpl *taskBarIconImpl)
    :   wxTaskBarIconWindow(taskBarIconImpl)
    {}
    void SetIcon(const wxIcon& icon)
    {   m_icon = icon; }
    void OnMouseEvent(wxMouseEvent &event);
    void OnPaint(wxPaintEvent &event);
protected:
    wxIcon m_icon;
};

// ============================================================================
// wxTaskBarIcon implementation
//     The facade class.
// ============================================================================
IMPLEMENT_DYNAMIC_CLASS(wxTaskBarIcon, wxEvtHandler)

wxTaskBarIcon::wxTaskBarIcon(wxTaskBarIconType iconType)
{
    if(iconType == DOCK)
        m_impl = new wxTaskBarIconDockImpl(this);
    else if(iconType == CUSTOM_STATUSITEM)
        m_impl = new wxTaskBarIconCustomStatusItemImpl(this);
    else
    {   m_impl = NULL;
        wxFAIL_MSG(wxT("Invalid wxTaskBarIcon type"));
    }
}

wxTaskBarIcon::~wxTaskBarIcon()
{
    delete m_impl;
}

// Operations
bool wxTaskBarIcon::SetIcon(const wxIcon& icon, const wxString& tooltip)
{
    return m_impl->SetIcon(icon,tooltip);
}

bool wxTaskBarIcon::RemoveIcon()
{
    return m_impl->RemoveIcon();
}

bool wxTaskBarIcon::PopupMenu(wxMenu *menu)
{
    return m_impl->PopupMenu(menu);
}

// ============================================================================
// wxTaskBarIconCocoaImpl
// ============================================================================

#if 0
wxTaskBarIconCocoaImpl::wxTaskBarIconCocoaImpl(wxTaskBarIcon *taskBarIcon)
:   m_taskBarIcon(taskBarIcon)
,   m_iconWindow(NULL)
{
}
#endif

wxTaskBarIconCocoaImpl::~wxTaskBarIconCocoaImpl()
{
//    wxAutoNSAutoreleasePool pool;
    delete m_iconWindow;
}

// ============================================================================
// wxTaskBarIconDockImpl
// ============================================================================
wxTaskBarIconDockImpl *wxTaskBarIconDockImpl::sm_dockIcon = NULL;

wxTaskBarIconDockImpl::wxTaskBarIconDockImpl(wxTaskBarIcon *taskBarIcon)
:   wxTaskBarIconCocoaImpl(taskBarIcon)
{
    m_originalDockIcon = nil;
    wxASSERT_MSG(!sm_dockIcon, wxT("You should never have more than one dock icon!"));
    sm_dockIcon = this;
}

wxTaskBarIconDockImpl::~wxTaskBarIconDockImpl()
{
//    wxAutoNSAutoreleasePool pool;
    if(sm_dockIcon == this)
        sm_dockIcon = NULL;
}

WX_NSMenu wxTaskBarIconDockImpl::CocoaGetDockNSMenu()
{
    if(sm_dockIcon)
        return sm_dockIcon->CocoaDoGetDockNSMenu();
    return nil;
}

WX_NSMenu wxTaskBarIconDockImpl::CocoaDoGetDockNSMenu()
{
    wxMenu *dockMenu = CreatePopupMenu();
    if(!dockMenu)
        return nil;
    if(!m_iconWindow)
        m_iconWindow = new wxTaskBarIconWindow(this);
    dockMenu->SetInvokingWindow(m_iconWindow);
    dockMenu->UpdateUI();
    dockMenu->SetCocoaDeletes(true);
    return dockMenu->GetNSMenu();
}

bool wxTaskBarIconDockImpl::SetIcon(const wxIcon& icon, const wxString& tooltip)
{
    wxAutoNSAutoreleasePool pool;
    m_originalDockIcon = [[[NSApplication sharedApplication] applicationIconImage] retain];
    [[NSApplication sharedApplication] setApplicationIconImage:icon.GetNSImage()];
    return true;
}

bool wxTaskBarIconDockImpl::RemoveIcon()
{
    [[NSApplication sharedApplication] setApplicationIconImage:m_originalDockIcon];
    [m_originalDockIcon release];
    return true;
}

bool wxTaskBarIconDockImpl::PopupMenu(wxMenu *menu)
{
    wxFAIL_MSG(wxT("You cannot force the Dock icon menu to popup"));
    return false;
}


// ============================================================================
// wxTaskBarIconCustomStatusItemImpl
// ============================================================================
wxTaskBarIconCustomStatusItemImpl::wxTaskBarIconCustomStatusItemImpl(wxTaskBarIcon *taskBarIcon)
:   wxTaskBarIconCocoaImpl(taskBarIcon)
{
    m_cocoaNSStatusItem = nil;
}

wxTaskBarIconCustomStatusItemImpl::~wxTaskBarIconCustomStatusItemImpl()
{
}

bool wxTaskBarIconCustomStatusItemImpl::SetIcon(const wxIcon& icon, const wxString& tooltip)
{
    wxAutoNSAutoreleasePool pool;
    if(!m_cocoaNSStatusItem)
    {
        m_cocoaNSStatusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
        [m_cocoaNSStatusItem retain];
    }
    if(!m_iconWindow)
        m_iconWindow= new wxTaskBarIconWindowCustom(this);
    static_cast<wxTaskBarIconWindowCustom*>(m_iconWindow)->SetIcon(icon);
    // FIXME: no less than 10 because most icon types don't work yet
    // and this allows us to see how task bar icons would work
    [m_iconWindow->GetNSView() setFrame:NSMakeRect(0.0,0.0,wxMax(10,icon.GetWidth()),[[NSStatusBar systemStatusBar] thickness])];
    [m_cocoaNSStatusItem setView:m_iconWindow->GetNSView()];
    return true;
}

bool wxTaskBarIconCustomStatusItemImpl::RemoveIcon()
{
    [m_cocoaNSStatusItem release];
    m_cocoaNSStatusItem = nil;
    delete m_iconWindow;
    m_iconWindow = NULL;
    return true;
}

bool wxTaskBarIconCustomStatusItemImpl::PopupMenu(wxMenu *menu)
{
    wxCHECK_MSG(menu, false, "can't popup a NULL menu");

    wxMenuInvokingWindowSetter setInvokingWin(*menu, m_iconWindow);
    menu->UpdateUI();

    if([m_cocoaNSStatusItem respondsToSelector:@selector(popUpStatusItemMenu:)])
    {   // OS X >= 10.3
        [m_cocoaNSStatusItem popUpStatusItemMenu:menu->GetNSMenu()];
    }
    else
    {   // pretty good fake for OS X < 10.3
        NSEvent *nsevent = [NSEvent mouseEventWithType:NSLeftMouseDown
            location:NSMakePoint(-1.0,-4.0) modifierFlags:0 timestamp:0
            windowNumber:[[m_iconWindow->GetNSView() window] windowNumber]
            context:[NSGraphicsContext currentContext]
            eventNumber:0 clickCount:1 pressure:0.0];
        [NSMenu popUpContextMenu:menu->GetNSMenu() withEvent:nsevent forView:m_iconWindow->GetNSView()];
    }
    return true;
}

// ============================================================================
// wxTaskBarIconWindow
// ============================================================================
BEGIN_EVENT_TABLE(wxTaskBarIconWindow, wxWindow)
    EVT_MENU(-1, wxTaskBarIconWindow::OnMenuEvent)
END_EVENT_TABLE()

void wxTaskBarIconWindow::OnMenuEvent(wxCommandEvent &event)
{
    m_taskBarIconImpl->GetTaskBarIcon()->ProcessEvent(event);
}

// ============================================================================
// wxTaskBarIconWindowCustom
// ============================================================================
BEGIN_EVENT_TABLE(wxTaskBarIconWindowCustom, wxTaskBarIconWindow)
    EVT_MOUSE_EVENTS(wxTaskBarIconWindowCustom::OnMouseEvent)
    EVT_PAINT(wxTaskBarIconWindowCustom::OnPaint)
END_EVENT_TABLE()

void wxTaskBarIconWindowCustom::OnMouseEvent(wxMouseEvent &event)
{
    wxEventType tbEventType = 0;
    if(event.GetEventType() == wxEVT_MOTION)
        tbEventType = wxEVT_TASKBAR_MOVE;
    else if(event.GetEventType() == wxEVT_LEFT_DOWN)
        tbEventType = wxEVT_TASKBAR_LEFT_DOWN;
    else if(event.GetEventType() == wxEVT_LEFT_UP)
        tbEventType = wxEVT_TASKBAR_LEFT_UP;
    else if(event.GetEventType() == wxEVT_RIGHT_DOWN)
        tbEventType = wxEVT_TASKBAR_RIGHT_DOWN;
    else if(event.GetEventType() == wxEVT_RIGHT_UP)
        tbEventType = wxEVT_TASKBAR_RIGHT_UP;
    else if(event.GetEventType() == wxEVT_LEFT_DCLICK)
        tbEventType = wxEVT_TASKBAR_LEFT_DCLICK;
    else if(event.GetEventType() == wxEVT_RIGHT_DCLICK)
        tbEventType = wxEVT_TASKBAR_RIGHT_DCLICK;
    else
        return;
    wxTaskBarIconEvent tbiEvent(tbEventType,m_taskBarIconImpl->GetTaskBarIcon());
    m_taskBarIconImpl->GetTaskBarIcon()->ProcessEvent(tbiEvent);
}

void wxTaskBarIconWindowCustom::OnPaint(wxPaintEvent &event)
{
    wxPaintDC dc(this);
    // FIXME: This is a temporary hack until we can see real icons
    dc.SetBackground(wxBrush(*wxBLUE));
    dc.Clear();
    dc.DrawIcon(m_icon,0,0);
}

// ============================================================================
// wxTaskBarIconNSApplicationDelegateCategory
// ============================================================================

// This neatly solves the problem of DLL separation.  If the wxAdvanced
// library (which this file is part of) is loaded then this category is
// defined and we get dock menu behaviour without app.mm ever having to
// know we exist.  C++ did sucketh so. :-)

@interface wxNSApplicationDelegate(wxTaskBarIconNSApplicationDelegateCategory)
- (NSMenu*)applicationDockMenu:(NSApplication *)sender;
@end

@implementation wxNSApplicationDelegate(wxTaskBarIconNSApplicationDelegateCategory)
- (NSMenu*)applicationDockMenu:(NSApplication *)sender
{
    return wxTaskBarIconDockImpl::CocoaGetDockNSMenu();
}
@end

#endif //def wxHAS_TASK_BAR_ICON
