X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/f2641bc2bffc818b44979292a238144bea620cda..ea8ca011a351c6aaa4009bedbf0cec19a7c57d98:/src/mac/carbon/taskbar.cpp diff --git a/src/mac/carbon/taskbar.cpp b/src/mac/carbon/taskbar.cpp index e59ddefa04..ad7cdd33da 100644 --- a/src/mac/carbon/taskbar.cpp +++ b/src/mac/carbon/taskbar.cpp @@ -1,17 +1,23 @@ -///////////////////////////////////////////////////////////////////////////// -// Name: taskbar.cpp -// Purpose: wxTaskBarIcon OSX Implementation +/////////////////////////////////////////////////////////////////////////////// +// Name: taskbar.cpp +// Purpose: wxTaskBarIcon - OSX implementation // Author: Ryan Norton // Modified by: // Created: 09/25/2004 // RCS-ID: $Id$ // Copyright: (c) 2004 Ryan Norton // Licence: wxWindows licence -///////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// -#include "wx/wxprec.h" +//============================================================================= +// Declarations +//============================================================================= + +//----------------------------------------------------------------------------- +// Includes +//----------------------------------------------------------------------------- -#include "wx/defs.h" +#include "wx/wxprec.h" #ifdef wxHAS_TASK_BAR_ICON @@ -20,191 +26,534 @@ #include "wx/taskbar.h" #include "wx/menu.h" #include "wx/icon.h" +#include "wx/dcmemory.h" + +//----------------------------------------------------------------------------- +// +// wxTaskBarIconImpl +// +// Superclass of wxTaskBarIcon implementations +//----------------------------------------------------------------------------- -#if 0 +class wxTaskBarIconImpl +{ +public: + wxTaskBarIconImpl(wxTaskBarIcon* parent); + virtual ~wxTaskBarIconImpl(); -#include "wx/frame.h" -#include "wx/dialog.h" + 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; -#endif + wxMenu* CreatePopupMenu() + { return m_parent->CreatePopupMenu(); } -IMPLEMENT_DYNAMIC_CLASS(wxTaskBarIcon, wxEvtHandler) + wxTaskBarIcon* m_parent; + class wxTaskBarIconWindow* m_menuEventWindow; + + DECLARE_NO_COPY_CLASS(wxTaskBarIconImpl) +}; + +//----------------------------------------------------------------------------- +// +// wxTaskBarIconWindow +// +// 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, -1, wxT("")), 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; +}; -pascal OSStatus wxDockEventHandler( EventHandlerCallRef inHandlerCallRef, - EventRef inEvent, void* pData) +//----------------------------------------------------------------------------- +// +// wxDockBarIconImpl +// +//----------------------------------------------------------------------------- + +class wxDockTaskBarIcon : public wxTaskBarIconImpl { - wxTaskBarIcon*& pTB = (wxTaskBarIcon*&) pData; +public: + wxDockTaskBarIcon(wxTaskBarIcon* parent); + virtual ~wxDockTaskBarIcon(); + + 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); + +//============================================================================= +// +// Implementation +// +//============================================================================= + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// wxTaskBarIconImpl +// +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +//----------------------------------------------------------------------------- +// wxTaskBarIconImpl Constructor +// +// Initializes members and creates the event window +//----------------------------------------------------------------------------- +wxTaskBarIconImpl::wxTaskBarIconImpl(wxTaskBarIcon* parent) + : m_parent(parent), m_menuEventWindow(new wxTaskBarIconWindow(this)) +{ +} + +//----------------------------------------------------------------------------- +// wxTaskBarIconImpl Destructor +// +// Cleans up the event window +//----------------------------------------------------------------------------- +wxTaskBarIconImpl::~wxTaskBarIconImpl() +{ + 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 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) - { - 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 ) + + // 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)) { - 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) + return eventNotHandledErr; + + // + // This is the real reason why we need this. Normally menus + // get handled in wxMacAppEventHandler + // + // pascal OSStatus wxMacAppEventHandler(EventHandlerCallRef handler, + // EventRef event, void *data) + // + // However, in the case of a taskbar menu call + // command.menu.menuRef IS NULL! + // Which causes the wxApp handler just to skip it. + // + MenuRef taskbarMenuRef = MAC_WXHMENU(pTB->m_pMenu->GetHMenu()); + OSErr err; + + // get the HICommand from the event + HICommand command; + err = GetEventParameter(inEvent, kEventParamDirectObject, + typeHICommand, NULL, + sizeof(HICommand), NULL, &command); + if (err == noErr) { - wxMenu* menu = NULL ; - item = mbar->FindItem( id , &menu ) ; - } - } - else if (id != 0) - GetMenuItemRefCon( hMenu , menuItemIndex , (UInt32*) &item ) ; + // 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; - if ( item ) - { - if (item->IsCheckable()) + err = GetIndMenuItemWithCommandID(taskbarMenuRef, + command.commandID, + 1, &menuRef, &menuItemIndex); + if (err == noErr) + { + MenuCommand id = command.commandID; + wxMenuItem* item = NULL; + + if (id != 0) // get the wxMenuItem reference from the MenuRef + GetMenuItemRefCon(menuRef, menuItemIndex, (UInt32*) &item); + + if (item) { - item->Check( !item->IsChecked() ) ; + // Handle items that are checkable + // FIXME: Doesn't work (at least on 10.2)! + if (item->IsCheckable()) + item->Check( !item->IsChecked() ) ; + + // send the wxEvent to the wxMenu + item->GetMenu()->SendEvent(id, + item->IsCheckable() ? + item->IsChecked() : -1 + ); + err = noErr; // successfully handled the event } + } + } //end if noErr on getting HICommand from event - item->GetMenu()->SendEvent( id , item->IsCheckable() ? item->IsChecked() : -1 ) ; - result = noErr ; + // return whether we handled the event or not + return err; } - return result ; + + // We better have a kEventClassApplication/kEventAppGetDockTileMenu combo here, + // otherwise something is truly funky + wxASSERT(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(); + + OSStatus err = eventNotHandledErr; + + 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 + RetainMenu(hMenu); + + // set the actual dock menu + err = SetEventParameter(inEvent, kEventParamMenuRef, + typeMenuRef, sizeof(MenuRef), &hMenu); + wxASSERT(err == noErr); } - - 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 ); - -wxTaskBarIcon::wxTaskBarIcon(const wxTaskBarIconType& nType) - : m_nType(nType), m_pEvent(NULL), m_pMenu(NULL), m_iconAdded(false) -{ - //Register the events that will return the dock menu - EventTypeSpec tbEventList[] = { { kEventClassCommand, kEventProcessCommand }, - { kEventClassApplication, kEventAppGetDockTileMenu } }; - - OSStatus err = InstallApplicationEventHandler( - GetwxDockEventHandlerUPP(), - GetEventTypeCount(tbEventList), tbEventList, - this, NULL); - - wxASSERT(err == noErr); + return err; } -wxTaskBarIcon::~wxTaskBarIcon() + +//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +// 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) { - //TODO:uninstall event handler + if (!menu) + 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->GetText(), // 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; } -void wxTaskBarIcon::SetInternalEvent(void* pEvent) +//----------------------------------------------------------------------------- +// 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) { - m_pEvent = pEvent; + // register the events that will return the dock menu + EventTypeSpec tbEventList[] = + { + { kEventClassCommand, kEventProcessCommand }, + { kEventClassApplication, kEventAppGetDockTileMenu } + }; + + m_eventupp = NewEventHandlerUPP(wxDockEventHandler); + wxASSERT(m_eventupp != NULL); + +#ifdef __WXDEBUG__ + OSStatus err = +#endif + InstallApplicationEventHandler( + m_eventupp, + GetEventTypeCount(tbEventList), tbEventList, + this, &m_eventHandlerRef); + wxASSERT( err == noErr ); } -wxMenu* wxTaskBarIcon::GetCurrentMenu() +//----------------------------------------------------------------------------- +// wxDockTaskBarIcon Destructor +// +// Cleans up mac events and restores the old icon to the dock +//----------------------------------------------------------------------------- +wxDockTaskBarIcon::~wxDockTaskBarIcon() { - return m_pMenu; + // clean up event handler and event UPP + RemoveEventHandler(m_eventHandlerRef); + DisposeEventHandlerUPP(m_eventupp); + + // restore old icon and menu to the dock + RemoveIcon(); } -// Operations: -bool wxTaskBarIcon::SetIcon(const wxIcon& icon, const wxString& tooltip) +//----------------------------------------------------------------------------- +// wxDockTaskBarIcon::DoCreatePopupMenu +// +// Helper function that handles a request from the dock event handler +// to get the menu for the dock +//----------------------------------------------------------------------------- +wxMenu* wxDockTaskBarIcon::DoCreatePopupMenu() { - #if 0 - wxASSERT(wxTheApp); - wxWindow* pTopWindow = wxTheApp->GetTopWindow(); - - wxASSERT(pTopWindow); - - if(pTopWindow->IsKindOf(CLASSINFO(wxDialog))) - ((wxDialog*)pTopWindow)->SetIcon(icon); - else + // get the menu from the parent + wxMenu* theNewMenu = CreatePopupMenu(); + + if (theNewMenu) { - wxASSERT(pTopWindow->IsKindOf(CLASSINFO(wxFrame))); - ((wxFrame*)pTopWindow)->SetIcon(icon); + if (m_pMenu) + delete m_pMenu; + m_pMenu = theNewMenu; + m_pMenu->SetInvokingWindow(m_menuEventWindow); } - - return true; - #else - //TODO: Educated guess - - 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); - - m_iconAdded = true; - return true; - #endif + + // 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; } - -bool wxTaskBarIcon::RemoveIcon() + +//----------------------------------------------------------------------------- +// wxDockTaskBarIcon::IsIconInstalled +// +// Returns whether or not the dock is not using the default image +//----------------------------------------------------------------------------- +bool wxDockTaskBarIcon::IsIconInstalled() const { - //TODO: Not tested - OSStatus err = RestoreApplicationDockTileImage(); - 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& 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.CGImageCreate(); + wxASSERT( pImage != NULL ); - return true; + // actually set the dock image + OSStatus err = SetApplicationDockTileImage( pImage ); + wxASSERT( err == noErr ); + + // free the CGImage, now that it's referenced by the dock + if (pImage != NULL) + CGImageRelease( pImage ); + + bool success = (err == noErr); + m_iconAdded = success; + + return success; } - -bool wxTaskBarIcon::PopupMenu(wxMenu *menu) + +//----------------------------------------------------------------------------- +// wxDockTaskBarIcon::RemoveIcon +// +// Restores the old image for the dock via RestoreApplicationDockTileImage +//----------------------------------------------------------------------------- +bool wxDockTaskBarIcon::RemoveIcon() +{ + if (m_pMenu) + { + delete m_pMenu; + m_pMenu = NULL; + } + + // restore old icon to the dock + OSStatus err = RestoreApplicationDockTileImage(); + wxASSERT(err == noErr); + + // restore the old menu to the dock + SetApplicationDockTileMenu(m_theLastMenu); + + bool success = (err == noErr); + m_iconAdded = !success; + + return success; +} + +//----------------------------------------------------------------------------- +// 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 true; + + //start copy of menu + m_pMenu = wxDeepCopyMenu(menu); + + //finish up + m_pMenu->SetInvokingWindow(m_menuEventWindow); + + return true; } +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// 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") + wxT("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 //wxHAS_TASK_BAR_ICON