X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/e768548a129b7fee654beead98b80223054c190f..af86380553169606c985e0053a1630e9514e18b6:/src/mac/carbon/taskbar.cpp?ds=inline diff --git a/src/mac/carbon/taskbar.cpp b/src/mac/carbon/taskbar.cpp index 66fd95ada0..bce496ab28 100644 --- a/src/mac/carbon/taskbar.cpp +++ b/src/mac/carbon/taskbar.cpp @@ -1,227 +1,524 @@ -///////////////////////////////////////////////////////////////////////////// -// 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" - #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(); + + 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; + + wxMenu * CreatePopupMenu() + { return m_parent->CreatePopupMenu(); } + 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... +//----------------------------------------------------------------------------- -pascal OSStatus wxDockEventHandler( EventHandlerCallRef inHandlerCallRef, - EventRef inEvent, void* pData) +class wxTaskBarIconWindow : public wxTopLevelWindow { - wxTaskBarIcon*& pTB = (wxTaskBarIcon*&) pData; - - const UInt32 eventClass = GetEventClass(inEvent); - const UInt32 eventKind = GetEventKind(inEvent); - - if (eventClass == kEventClassCommand && eventKind == kEventCommandProcess) +public: + wxTaskBarIconWindow(wxTaskBarIconImpl *impl) + : wxTopLevelWindow(NULL, wxID_ANY, wxEmptyString), m_impl(impl) { - //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 ) + Connect( + -1, wxEVT_COMMAND_MENU_SELECTED, + wxCommandEventHandler(wxTaskBarIconWindow::OnMenuEvent) ); + } + + void OnMenuEvent(wxCommandEvent& event) { - id = wxApp::s_macPreferencesMenuItemId ; - - wxMenuBar* mbar = wxMenuBar::MacGetInstalledMenuBar() ; - if ( mbar ) - { - wxMenu* menu = NULL ; - item = mbar->FindItem( id , &menu ) ; - } + m_impl->m_parent->ProcessEvent(event); } - else if (id != 0) - GetMenuItemRefCon( hMenu , menuItemIndex , (UInt32*) &item ) ; - if ( item ) +private: + wxTaskBarIconImpl *m_impl; +}; + +class wxDockTaskBarIcon : public wxTaskBarIconImpl +{ +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 ); + + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// wxTaskBarIconImpl +// +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +wxTaskBarIconImpl::wxTaskBarIconImpl(wxTaskBarIcon* parent) + : m_parent(parent), m_menuEventWindow(new wxTaskBarIconWindow(this)) +{ +} + +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); + + // 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)) { - if (item->IsCheckable()) + // 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()); + OSStatus err; + + // get the HICommand from the event + HICommand command; + err = GetEventParameter( + inEvent, kEventParamDirectObject, + typeHICommand, NULL, + sizeof(HICommand), NULL, &command ); + if (err == noErr) { - item->Check( !item->IsChecked() ) ; - } - - item->GetMenu()->SendEvent( id , item->IsCheckable() ? item->IsChecked() : -1 ) ; - result = noErr ; - } - return result ; + // 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; + + 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) + { + // 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 ); + + // successfully handled the event + err = noErr; + } + } + } //end if noErr on getting HICommand from event + + // return whether we handled the event or not + return err; } - - wxASSERT(eventClass == kEventClassApplication && eventKind == kEventAppGetDockTileMenu); - - //process the right click event - wxTaskBarIconEvent evt(wxEVT_TASKBAR_RIGHT_UP,NULL); - pTB->ProcessEvent(evt); - - //create popup menu + + // 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 = noErr; - if(menu) + OSStatus err = eventNotHandledErr; + + if (menu != NULL) { - //note to self - a MenuRef IS A MenuHandle + // 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); + // 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((EventRef) inEvent, kEventParamMenuRef, - typeMenuRef, sizeof(MenuRef), - &hMenu); - wxASSERT(err == 0); + // set the actual dock menu + err = SetEventParameter( + inEvent, kEventParamMenuRef, + typeMenuRef, sizeof(MenuRef), &hMenu ); + verify_noerr( err ); } return err; } -DEFINE_ONE_SHOT_HANDLER_GETTER( wxDockEventHandler ); - -wxTaskBarIcon::wxTaskBarIcon(const wxTaskBarIconType& nType) - : m_nType(nType), 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->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; } -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 }, + { kEventClassApplication, kEventAppGetDockTileMenu } + }; + + m_eventupp = NewEventHandlerUPP(wxDockEventHandler); + wxASSERT(m_eventupp != NULL); + + OSStatus err = InstallApplicationEventHandler( + m_eventupp, + GetEventTypeCount(tbEventList), tbEventList, + this, &m_eventHandlerRef); + verify_noerr( err ); } -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(); } -wxMenu* wxTaskBarIcon::DoCreatePopupMenu() +//----------------------------------------------------------------------------- +// wxDockTaskBarIcon::DoCreatePopupMenu +// +// Helper function that handles a request from the dock event handler +// to get the menu for the dock +//----------------------------------------------------------------------------- +wxMenu * wxDockTaskBarIcon::DoCreatePopupMenu() { - if (m_pMenu) - delete m_pMenu; + // get the menu from the parent + wxMenu* theNewMenu = CreatePopupMenu(); - m_pMenu = CreatePopupMenu(); - - if (m_pMenu) - m_pMenu->SetEventHandler(this); - + 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 { - wxMask* mask = icon.GetMask(); - if (!mask) - { - // Make a mask with no transparent pixels - wxBitmap bmp(icon.GetWidth(), icon.GetHeight()); - wxMemoryDC dc; - dc.SelectObject(bmp); - dc.SetBackground(*wxBLACK_BRUSH); - dc.Clear(); - dc.SelectObject(wxNullBitmap); - mask = new wxMask(bmp, *wxWHITE); - } - - CGImageRef pImage; - - //create the icon from the bitmap and mask bitmap contained within - OSStatus err = CreateCGImageFromPixMaps( - GetGWorldPixMap(MAC_WXHBITMAP(icon.GetHBITMAP())), - GetGWorldPixMap(MAC_WXHBITMAP(mask->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& 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 ); + + // 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 ); - if (!icon.GetMask()) - delete mask; - - return m_iconAdded = err == noErr; + bool success = (err == noErr); + m_iconAdded = success; + + return success; } - -bool wxTaskBarIcon::RemoveIcon() + +//----------------------------------------------------------------------------- +// 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 == 0); + verify_noerr( err ); + + // restore the old menu to the dock + SetApplicationDockTileMenu( m_theLastMenu ); + + bool success = (err == noErr); + m_iconAdded = !success; - return !(m_iconAdded = !(err == noErr)); + 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(menu != NULL); + if (m_pMenu) delete m_pMenu; - - m_pMenu = menu; - - wxASSERT(menu); - m_pMenu->SetEventHandler(this); - - return SetApplicationDockTileMenu(MAC_WXHMENU(menu->GetHMenu())); + + // 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 // wxHAS_TASK_BAR_ICON