X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/cc3388ae63420668587b30ba4fd33b71565fe27a..9d5507f7a2701395e1d5c121bd877bb9066ee6ea:/src/mac/carbon/taskbar.cpp diff --git a/src/mac/carbon/taskbar.cpp b/src/mac/carbon/taskbar.cpp index 0eeca244c5..9bb02f5124 100644 --- a/src/mac/carbon/taskbar.cpp +++ b/src/mac/carbon/taskbar.cpp @@ -1,228 +1,507 @@ -///////////////////////////////////////////////////////////////////////////// -// Name: taskbar.cpp -// Purpose: wxTaskBarIcon OSX Implementation +/////////////////////////////////////////////////////////////////////////////// +// Name: src/mac/carbon/taskbar.cpp +// Purpose: wxTaskBarIcon // Author: Ryan Norton // Modified by: // Created: 09/25/2004 // RCS-ID: $Id$ // Copyright: (c) 2004 Ryan Norton // Licence: wxWindows licence -///////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// #include "wx/wxprec.h" -#include "wx/defs.h" +#if wxUSE_TASKBARICON -#ifdef wxHAS_TASK_BAR_ICON +#include "wx/taskbar.h" + +#ifndef WX_PRECOMP + #include "wx/dcmemory.h" + #include "wx/menu.h" + #include "wx/toplevel.h" + #include "wx/icon.h" +#endif #include "wx/mac/private.h" -#include "wx/taskbar.h" -#include "wx/menu.h" -#include "wx/icon.h" +class wxTaskBarIconImpl +{ +public: + wxTaskBarIconImpl(wxTaskBarIcon* parent); + virtual ~wxTaskBarIconImpl(); -#if 0 + virtual bool IsIconInstalled() const = 0; + virtual bool SetIcon(const wxIcon& icon, const wxString& tooltip) = 0; + virtual bool RemoveIcon() = 0; + virtual bool PopupMenu(wxMenu *menu) = 0; -#include "wx/frame.h" -#include "wx/dialog.h" + wxMenu * CreatePopupMenu() + { return m_parent->CreatePopupMenu(); } -#endif + wxTaskBarIcon *m_parent; + class wxTaskBarIconWindow *m_menuEventWindow; + + DECLARE_NO_COPY_CLASS(wxTaskBarIconImpl) +}; +//----------------------------------------------------------------------------- // -//TODO: Implement Apple Software Guidelines - show the top window it it's not shown, -//and force it to be unminimized - and all unminimized windows should be brought to -//the front +// wxTaskBarIconWindow // -//TODO: -IMPLEMENT_DYNAMIC_CLASS(wxTaskBarIcon, wxEvtHandler) +// Event handler for menus +// NB: Since wxWindows in Mac HAVE to have parents we need this to be +// a top level window... +//----------------------------------------------------------------------------- + +class wxTaskBarIconWindow : public wxTopLevelWindow +{ +public: + wxTaskBarIconWindow(wxTaskBarIconImpl *impl) + : wxTopLevelWindow(NULL, wxID_ANY, wxEmptyString), m_impl(impl) + { + Connect( + -1, wxEVT_COMMAND_MENU_SELECTED, + wxCommandEventHandler(wxTaskBarIconWindow::OnMenuEvent) ); + } + + void OnMenuEvent(wxCommandEvent& event) + { + m_impl->m_parent->ProcessEvent(event); + } + +private: + wxTaskBarIconImpl *m_impl; +}; + +class wxDockTaskBarIcon : public wxTaskBarIconImpl +{ +public: + wxDockTaskBarIcon(wxTaskBarIcon* parent); + virtual ~wxDockTaskBarIcon(); -pascal OSStatus wxDockEventHandler( EventHandlerCallRef inHandlerCallRef, - EventRef inEvent, void* pData) + virtual bool IsIconInstalled() const; + virtual bool SetIcon(const wxIcon& icon, const wxString& tooltip); + virtual bool RemoveIcon(); + virtual bool PopupMenu(wxMenu *menu); + + wxMenu* DoCreatePopupMenu(); + + EventHandlerRef m_eventHandlerRef; + EventHandlerUPP m_eventupp; + wxWindow *m_eventWindow; + wxMenu *m_pMenu; + MenuRef m_theLastMenu; + bool m_iconAdded; +}; + +// Forward declarations for utility functions for dock implementation +pascal OSStatus wxDockEventHandler( + EventHandlerCallRef inHandlerCallRef, + EventRef inEvent, void* pData ); +wxMenu * wxDeepCopyMenu( wxMenu *menu ); + + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// wxTaskBarIconImpl +// +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +wxTaskBarIconImpl::wxTaskBarIconImpl(wxTaskBarIcon* parent) + : m_parent(parent), m_menuEventWindow(new wxTaskBarIconWindow(this)) +{ +} + +wxTaskBarIconImpl::~wxTaskBarIconImpl() { - wxTaskBarIcon*& pTB = (wxTaskBarIcon*&) pData; + delete m_menuEventWindow; +} +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// wxDockTaskBarIcon +// +// OS X Dock implementation of wxTaskBarIcon using Carbon +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +//----------------------------------------------------------------------------- +// wxDockEventHandler +// +// This is the global Mac/Carbon event handler for the dock. +// We need this for two reasons: +// 1) To handle wxTaskBarIcon menu events (see below for why) +// 2) To handle events from the dock when it requests a menu +//----------------------------------------------------------------------------- +pascal OSStatus +wxDockEventHandler(EventHandlerCallRef WXUNUSED(inHandlerCallRef), + EventRef inEvent, + void *pData) +{ + // Get the parameters we want from the event + wxDockTaskBarIcon* pTB = (wxDockTaskBarIcon*) pData; const UInt32 eventClass = GetEventClass(inEvent); const UInt32 eventKind = GetEventKind(inEvent); - - if (eventClass == kEventClassCommand && eventKind == kEventCommandProcess) - { - //TODO: - //TODO: This is a complete copy of - //static pascal OSStatus wxMacAppCommandEventHandler( EventHandlerCallRef handler , EventRef event , void *data ) - //FIND A WAY TO EXTERN THIS AND USE THAT HERE INSTEAD!! - //TODO: - MenuRef hMenu = MAC_WXHMENU(pTB->GetCurrentMenu()->GetHMenu()); - OSStatus result = eventNotHandledErr ; - - HICommand command ; - OSErr err; - err = GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, - NULL, sizeof(HICommand), NULL, &command); - wxASSERT(err == noErr); - - MenuItemIndex menuItemIndex; - err = GetIndMenuItemWithCommandID(hMenu, command.commandID, 1, NULL, &menuItemIndex); - wxASSERT(err == noErr); - - - MenuCommand id = command.commandID ; - wxMenuItem* item = NULL; - // for items we don't really control - if ( id == kHICommandPreferences ) + OSStatus err = eventNotHandledErr; + + // Handle wxTaskBar menu events (note that this is a global event handler + // so it will actually get called by all commands/menus) + if ((eventClass == kEventClassCommand) && (eventKind == kEventCommandProcess || eventKind == kEventCommandUpdateStatus )) { - id = wxApp::s_macPreferencesMenuItemId ; - - wxMenuBar* mbar = wxMenuBar::MacGetInstalledMenuBar() ; - if ( mbar ) + // if we have no taskbar menu quickly pass it back to wxApp + if (pTB->m_pMenu != NULL) { - wxMenu* menu = NULL ; - item = mbar->FindItem( id , &menu ) ; - } - } - else if (id != 0) - GetMenuItemRefCon( hMenu , menuItemIndex , (UInt32*) &item ) ; + // This is the real reason why we need this. Normally menus + // get handled in wxMacAppEventHandler + // However, in the case of a taskbar menu call + // command.menu.menuRef IS NULL! + // Which causes the wxApp handler just to skip it. - if ( item ) - { - if (item->IsCheckable()) + // get the HICommand from the event + HICommand command; + if (GetEventParameter(inEvent, kEventParamDirectObject, + typeHICommand, NULL,sizeof(HICommand), NULL, &command ) == noErr) + { + // Obtain the REAL menuRef and the menuItemIndex in the real menuRef + // + // NOTE: menuRef is generally used here for submenus, as + // GetMenuItemRefCon could give an incorrect wxMenuItem if we pass + // just the top level wxTaskBar menu + MenuItemIndex menuItemIndex; + MenuRef menuRef; + MenuRef taskbarMenuRef = MAC_WXHMENU(pTB->m_pMenu->GetHMenu()); + + // the next command is only successful if it was a command from the taskbar menu + // otherwise we pass it on + if (GetIndMenuItemWithCommandID(taskbarMenuRef,command.commandID, + 1, &menuRef, &menuItemIndex ) == noErr) { - item->Check( !item->IsChecked() ) ; - } + wxMenu* itemMenu = wxFindMenuFromMacMenu( menuRef ) ; + int id = wxMacCommandToId( command.commandID ) ; + wxMenuItem *item = NULL; + + if (id != 0) // get the wxMenuItem reference from the MenuRef + GetMenuItemRefCon( menuRef, menuItemIndex, (URefCon*) &item ); - item->GetMenu()->SendEvent( id , item->IsCheckable() ? item->IsChecked() : -1 ) ; - result = noErr ; + if (item && itemMenu ) + { + if ( eventKind == kEventCommandProcess ) + err = itemMenu->MacHandleCommandProcess( item, id ); + else if ( eventKind == kEventCommandUpdateStatus ) + err = itemMenu->MacHandleCommandUpdateStatus( item, id ); + } + } + } + } //end if noErr on getting HICommand from event } - return result ; + else if ((eventClass == kEventClassApplication) && (eventKind == kEventAppGetDockTileMenu )) + { + // process the right click events + // NB: This may result in double or even triple-creation of the menus + // We need to do this for 2.4 compat, however + wxTaskBarIconEvent downevt(wxEVT_TASKBAR_RIGHT_DOWN, NULL); + pTB->m_parent->ProcessEvent(downevt); + + wxTaskBarIconEvent upevt(wxEVT_TASKBAR_RIGHT_UP, NULL); + pTB->m_parent->ProcessEvent(upevt); + + // create popup menu + wxMenu* menu = pTB->DoCreatePopupMenu(); + + if (menu != NULL) + { + // note to self - a MenuRef *is* a MenuHandle + MenuRef hMenu = MAC_WXHMENU(menu->GetHMenu()); + + // When SetEventParameter is called it will decrement + // the reference count of the menu - we need to make + // sure it stays around in the wxMenu class here + CFRetain(hMenu); + + // set the actual dock menu + err = SetEventParameter( + inEvent, kEventParamMenuRef, + typeMenuRef, sizeof(MenuRef), &hMenu ); + verify_noerr( err ); + } } - - wxASSERT(eventClass == kEventClassApplication && eventKind == kEventAppGetDockTileMenu); - - //set the internal event - pTB->SetInternalEvent(inEvent); - - //process the right click event - wxTaskBarIconEvent evt(wxEVT_TASKBAR_RIGHT_UP,NULL); - pTB->ProcessEvent(evt); - - //set the internal event - pTB->SetInternalEvent(NULL); - - return noErr; -} -DEFINE_ONE_SHOT_HANDLER_GETTER( wxDockEventHandler ); + return err; +} -wxTaskBarIcon::wxTaskBarIcon(const wxTaskBarIconType& nType) - : m_nType(nType), m_pEvent(NULL), m_pEventHandlerRef(NULL), m_pMenu(NULL), m_iconAdded(false) +//----------------------------------------------------------------------------- +// wxDeepCopyMenu +// +// Performs a top-to-bottom copy of the input menu and all of its +// submenus. +// +// This is mostly needed for 2.4 compatability. However wxPython and others +// still use this way of setting the taskbarmenu. +//----------------------------------------------------------------------------- +wxMenu * wxDeepCopyMenu( wxMenu *menu ) { - //Register the events that will return the dock menu - EventTypeSpec tbEventList[] = { { kEventClassCommand, kEventProcessCommand }, - { kEventClassApplication, kEventAppGetDockTileMenu } }; - -#ifdef __WXDEBUG__ - OSStatus err = -#endif - InstallApplicationEventHandler( - GetwxDockEventHandlerUPP(), - GetEventTypeCount(tbEventList), tbEventList, - this, (&(EventHandlerRef&)m_pEventHandlerRef)); - - wxASSERT(err == noErr); + if (menu == NULL) + return NULL; + + // NB: Here we have to perform a deep copy of the menu, + // copying each and every menu item from menu to m_pMenu. + // Other implementations use wxWindow::PopupMenu here, + // which idle execution until the user selects something, + // but since the Mac handles this internally, we can't - + // and have no way at all to idle it while the dock menu + // is being shown before menu goes out of scope (it may + // not be on the heap, and may expire right after this function + // is done - we need it to last until the carbon event is triggered - + // that's when the user right clicks). + // + // Also, since there is no equal (assignment) operator + // on either wxMenu or wxMenuItem, we have to do all the + // dirty work ourselves. + + // perform a deep copy of the menu + wxMenuItemList& theList = menu->GetMenuItems(); + wxMenuItemList::compatibility_iterator theNode = theList.GetFirst(); + + // create the main menu + wxMenu *m_pMenu = new wxMenu(menu->GetTitle()); + + while (theNode != NULL) + { + wxMenuItem* theItem = theNode->GetData(); + m_pMenu->Append( + new wxMenuItem( + m_pMenu, // parent menu + theItem->GetId(), // id + theItem->GetItemLabel(), // text label + theItem->GetHelp(), // status bar help string + theItem->GetKind(), // menu flags - checkable, separator, etc. + wxDeepCopyMenu(theItem->GetSubMenu()) )); // submenu + + theNode = theNode->GetNext(); + } + + return m_pMenu; } -wxTaskBarIcon::~wxTaskBarIcon() +//----------------------------------------------------------------------------- +// wxDockTaskBarIcon ctor +// +// Initializes the dock implementation of wxTaskBarIcon. +// +// Here we create some Mac-specific event handlers and UPPs. +//----------------------------------------------------------------------------- +wxDockTaskBarIcon::wxDockTaskBarIcon(wxTaskBarIcon* parent) + : wxTaskBarIconImpl(parent), + m_eventHandlerRef(NULL), m_pMenu(NULL), + m_theLastMenu(GetApplicationDockTileMenu()), m_iconAdded(false) { - RemoveEventHandler((EventHandlerRef&)m_pEventHandlerRef); + // register the events that will return the dock menu + EventTypeSpec tbEventList[] = + { + { kEventClassCommand, kEventProcessCommand }, + { kEventClassCommand, kEventCommandUpdateStatus }, + { kEventClassApplication, kEventAppGetDockTileMenu } + }; + + m_eventupp = NewEventHandlerUPP(wxDockEventHandler); + wxASSERT(m_eventupp != NULL); + + OSStatus err = InstallApplicationEventHandler( + m_eventupp, + GetEventTypeCount(tbEventList), tbEventList, + this, &m_eventHandlerRef); + verify_noerr( err ); } -void wxTaskBarIcon::SetInternalEvent(void* pEvent) +//----------------------------------------------------------------------------- +// wxDockTaskBarIcon Destructor +// +// Cleans up mac events and restores the old icon to the dock +//----------------------------------------------------------------------------- +wxDockTaskBarIcon::~wxDockTaskBarIcon() { - m_pEvent = pEvent; + // clean up event handler and event UPP + RemoveEventHandler(m_eventHandlerRef); + DisposeEventHandlerUPP(m_eventupp); + + // restore old icon and menu to the dock + RemoveIcon(); } -wxMenu* wxTaskBarIcon::GetCurrentMenu() +//----------------------------------------------------------------------------- +// wxDockTaskBarIcon::DoCreatePopupMenu +// +// Helper function that handles a request from the dock event handler +// to get the menu for the dock +//----------------------------------------------------------------------------- +wxMenu * wxDockTaskBarIcon::DoCreatePopupMenu() { + // get the menu from the parent + wxMenu* theNewMenu = CreatePopupMenu(); + + if (theNewMenu) + { + if (m_pMenu) + delete m_pMenu; + m_pMenu = theNewMenu; + m_pMenu->SetInvokingWindow(m_menuEventWindow); + } + + // the return here can be one of three things + // (in order of priority): + // 1) User passed a menu from CreatePopupMenu override + // 2) menu sent to and copied from PopupMenu + // 3) If neither (1) or (2), then NULL + // return m_pMenu; } -// Operations: -bool wxTaskBarIcon::SetIcon(const wxIcon& icon, const wxString& tooltip) +//----------------------------------------------------------------------------- +// wxDockTaskBarIcon::IsIconInstalled +// +// Returns whether or not the dock is not using the default image +//----------------------------------------------------------------------------- +bool wxDockTaskBarIcon::IsIconInstalled() const { - #if 0 - wxASSERT(wxTheApp); - wxWindow* pTopWindow = wxTheApp->GetTopWindow(); - - wxASSERT(pTopWindow); - - if(pTopWindow->IsKindOf(CLASSINFO(wxDialog))) - ((wxDialog*)pTopWindow)->SetIcon(icon); - else - { - wxASSERT(pTopWindow->IsKindOf(CLASSINFO(wxFrame))); - ((wxFrame*)pTopWindow)->SetIcon(icon); - } - - return true; - #else - //TODO: (IT WORKS!) Make work without mask - mayby by using a wxDC? - - wxASSERT(icon.GetMask() != NULL); - - CGImageRef pImage; - //create the icon from the bitmap and mask bitmap contained within - OSStatus err = CreateCGImageFromPixMaps( - GetGWorldPixMap(MAC_WXHBITMAP(icon.GetHBITMAP())), - GetGWorldPixMap(MAC_WXHBITMAP(icon.GetMask()->GetMaskBitmap())), - &pImage - ); - - wxASSERT(err == 0); - - err = SetApplicationDockTileImage(pImage); - - wxASSERT(err == 0); - + return m_iconAdded; +} + +//----------------------------------------------------------------------------- +// wxDockTaskBarIcon::SetIcon +// +// Sets the icon for the dock CGImage functions and SetApplicationDockTileImage +//----------------------------------------------------------------------------- +bool wxDockTaskBarIcon::SetIcon(const wxIcon& icon, const wxString& WXUNUSED(tooltip)) +{ + // convert the wxIcon into a wxBitmap so we can perform some + // wxBitmap operations with it + wxBitmap bmp( icon ); + wxASSERT( bmp.Ok() ); + + // get the CGImageRef for the wxBitmap: + // OSX builds only, but then the dock only exists in OSX + CGImageRef pImage = (CGImageRef) bmp.CreateCGImage(); + wxASSERT( pImage != NULL ); + + // actually set the dock image + OSStatus err = SetApplicationDockTileImage( pImage ); + verify_noerr( err ); + + // free the CGImage, now that it's referenced by the dock if (pImage != NULL) - CGImageRelease(pImage); + CGImageRelease( pImage ); + + bool success = (err == noErr); + m_iconAdded = success; - return m_iconAdded = err == noErr; - #endif + return success; } - -bool wxTaskBarIcon::RemoveIcon() + +//----------------------------------------------------------------------------- +// wxDockTaskBarIcon::RemoveIcon +// +// Restores the old image for the dock via RestoreApplicationDockTileImage +//----------------------------------------------------------------------------- +bool wxDockTaskBarIcon::RemoveIcon() { - OSStatus err = RestoreApplicationDockTileImage(); - wxASSERT(err == 0); + if (m_pMenu) + { + delete m_pMenu; + m_pMenu = NULL; + } + + // restore old icon to the dock + OSStatus err = RestoreApplicationDockTileImage(); + verify_noerr( err ); + + // restore the old menu to the dock + SetApplicationDockTileMenu( m_theLastMenu ); - return !(m_iconAdded = !(err == noErr)); + bool success = (err == noErr); + m_iconAdded = !success; + + return success; } - -bool wxTaskBarIcon::PopupMenu(wxMenu *menu) + +//----------------------------------------------------------------------------- +// wxDockTaskBarIcon::PopupMenu +// +// 2.4 and wxPython method that "pops of the menu in the taskbar". +// +// In reality because of the way the dock menu works in carbon +// we just save the menu, and if the user didn't override CreatePopupMenu +// return the menu passed here, thus sort of getting the same effect. +//----------------------------------------------------------------------------- +bool wxDockTaskBarIcon::PopupMenu(wxMenu *menu) { - wxASSERT(m_pEvent != NULL); - + wxASSERT(menu != NULL); + if (m_pMenu) delete m_pMenu; - - m_pMenu = menu; - menu->SetEventHandler(this); - - //note to self - a MenuRef IS A MenuHandle - MenuRef hMenu = MAC_WXHMENU(menu->GetHMenu()); - - //When we call SetEventParameter it will decrement - //the reference count of the menu - we need to make - //sure it stays around in the wxMenu class here - RetainMenu(hMenu); - - //set the actual dock menu - OSStatus err = SetEventParameter((EventRef) m_pEvent, kEventParamMenuRef, - typeMenuRef, sizeof(MenuRef), - &hMenu); - wxASSERT(err == 0); - - return err == noErr; + + // start copy of menu + m_pMenu = wxDeepCopyMenu(menu); + + // finish up + m_pMenu->SetInvokingWindow(m_menuEventWindow); + + return true; } -#endif //wxHAS_TASK_BAR_ICON +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// wxTaskBarIcon +// +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +IMPLEMENT_DYNAMIC_CLASS(wxTaskBarIcon, wxEvtHandler) + +//----------------------------------------------------------------------------- +// wxTaskBarIcon Constructor +// +// Creates the backend +// +// Note that we only support DOCK currently as others require cocoa and +// also some require hacks and other such things. (MenuExtras are +// actually seperate programs that also require a special undocumented id +// hack and other such fun stuff). +//----------------------------------------------------------------------------- +wxTaskBarIcon::wxTaskBarIcon(wxTaskBarIconType nType) +{ + wxASSERT_MSG( + nType == DOCK, + wxT("Only the DOCK implementation of wxTaskBarIcon on Mac-Carbon is currently supported!") ); + + m_impl = new wxDockTaskBarIcon(this); +} + +//----------------------------------------------------------------------------- +// wxTaskBarIcon Destructor +// +// Destroys the backend +//----------------------------------------------------------------------------- +wxTaskBarIcon::~wxTaskBarIcon() +{ + delete m_impl; +} + +//----------------------------------------------------------------------------- +// wxTaskBarIcon::SetIcon +// wxTaskBarIcon::RemoveIcon +// wxTaskBarIcon::PopupMenu +// +// Just calls the backend version of the said function. +//----------------------------------------------------------------------------- +bool wxTaskBarIcon::IsIconInstalled() const +{ return m_impl->IsIconInstalled(); } + +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); } + +#endif // wxUSE_TASKBARICON