// This file is part of BOINC. // http://boinc.berkeley.edu // Copyright (C) 2008 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License // as published by the Free Software Foundation, // either version 3 of the License, or (at your option) any later version. // // BOINC is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with BOINC. If not, see . #if defined(__GNUG__) && !defined(__APPLE__) #pragma implementation "MacSysMenu.h" #endif #include "stdwx.h" #include "BOINCGUIApp.h" #include "BOINCBaseFrame.h" #include "MainDocument.h" #include "MacSysMenu.h" #include "DlgAbout.h" #include "Events.h" #include "miofile.h" #include "SkinManager.h" pascal OSStatus SysMenuEventHandler( EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void* pData); EventTypeSpec myEvents[] = { {kEventClassCommand, kEventCommandProcess}, { kEventClassApplication, kEventAppHidden}, { kEventClassApplication, kEventAppShown}, {kEventClassMenu, kEventMenuOpening} }; #if wxCHECK_VERSION(2,8,0) // Class declarations copied from wxMac-2.8.0/src/mac/carbon/taskbar.cpp // We had to copy these because they are not in a header file. class wxTaskBarIconImpl { public: wxTaskBarIconImpl(wxTaskBarIcon* parent); virtual ~wxTaskBarIconImpl(); virtual bool IsIconInstalled() const = 0; virtual bool SetIcon(const wxIcon& icon) = 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) }; //----------------------------------------------------------------------------- // // 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, 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(); virtual bool IsIconInstalled() const; virtual bool SetIcon(const wxIcon& icon); 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; }; #endif CMacSystemMenu::CMacSystemMenu(wxString title, wxIcon* icon, wxIcon* iconDisconnected, wxIcon* iconSnooze) : CTaskBarIcon(title, icon, iconDisconnected, iconSnooze) { CFBundleRef SysMenuBundle = NULL; m_bOpeningAboutDlg = false; m_bNeedRebuildMenu = false; LoadPrivateFrameworkBundle( CFSTR("SystemMenu.bundle"), &SysMenuBundle ); if ( SysMenuBundle != NULL ) { SetUpSystemMenu = (SetUpSystemMenuProc) CFBundleGetFunctionPointerForName( SysMenuBundle, CFSTR("SetUpSystemMenu") ); SetSystemMenuIcon = (SetSystemMenuIconProc) CFBundleGetFunctionPointerForName( SysMenuBundle, CFSTR("SetSystemMenuIcon") ); BuildMenu(); // The base class wxTaskBarIcon will install the wxDockEventHandler for // each instance of the derived classes CTaskBarIcon and CMacSystemMenu. // We remove that handler and substitute our own. #if wxCHECK_VERSION(2,8,0) RemoveEventHandler((EventHandlerRef&)(((wxDockTaskBarIcon*)m_impl)->m_eventHandlerRef)); InstallApplicationEventHandler(NewEventHandlerUPP(SysMenuEventHandler), sizeof(myEvents) / sizeof(EventTypeSpec), myEvents, this, (EventHandlerRef*)&(((wxDockTaskBarIcon*)m_impl)->m_eventHandlerRef)); #else RemoveEventHandler((EventHandlerRef&)m_pEventHandlerRef); InstallApplicationEventHandler(NewEventHandlerUPP(SysMenuEventHandler), sizeof(myEvents) / sizeof(EventTypeSpec), myEvents, this, (EventHandlerRef*)&m_pEventHandlerRef); #endif } } CMacSystemMenu::~CMacSystemMenu() { // Remove the System Menu (StatusItem) from menu bar if (SetSystemMenuIcon != NULL ) SetSystemMenuIcon(NULL); } // Set the System Menu Icon from XPM data bool CMacSystemMenu::SetMacMenuIcon(const wxIcon& icon) { wxBitmap theBits; // For unknown reasons, menus won't work if we call BuildMenu() directly // from CTaskBarIcon::OnReloadSkin(), so it sets a flag to call it here if (m_bNeedRebuildMenu) { CMainDocument* pDoc = wxGetApp().GetDocument(); wxASSERT(pDoc); wxASSERT(wxDynamicCast(pDoc, CMainDocument)); if (pDoc->IsConnected() && m_bNeedRebuildMenu) { // For unknown reasons, Menubar Icon menu doesn't work without this BuildMenu(); } } m_bNeedRebuildMenu = false; theBits.CopyFromIcon(icon); CGImageRef imageRef = (CGImageRef)theBits.CGImageCreate(); if ( (SetSystemMenuIcon != NULL) && (imageRef != NULL) ) { SetSystemMenuIcon(imageRef); CGImageRelease( imageRef ); return true; } if(imageRef != NULL) CGImageRelease( imageRef ); return false; } void CMacSystemMenu::BuildMenu() { wxBitmap theBits; wxMenu *themenu; CSkinAdvanced* pSkinAdvanced = wxGetApp().GetSkinManager()->GetAdvanced(); wxASSERT(pSkinAdvanced); wxASSERT(wxDynamicCast(pSkinAdvanced, CSkinAdvanced)); m_iconTaskBarNormal = *pSkinAdvanced->GetApplicationIcon(); m_iconTaskBarDisconnected = *pSkinAdvanced->GetApplicationDisconnectedIcon(); m_iconTaskBarSnooze = *pSkinAdvanced->GetApplicationSnoozeIcon(); theBits.CopyFromIcon(m_iconTaskBarNormal); CGImageRef imageRef = (CGImageRef)theBits.CGImageCreate(); if ( (SetUpSystemMenu != NULL ) && (imageRef != NULL) ) { // Currently, the system menu is the same as the Dock menu with the addition of // the Quit menu item. If in the future you wish to make the system menu different // from the Dock menu, override CTaskBarIcon::BuildContextMenu() and // CTaskBarIcon::AdjustMenuItems(). #if wxCHECK_VERSION(2,8,0) delete ((wxDockTaskBarIcon*)m_impl)->m_pMenu; ((wxDockTaskBarIcon*)m_impl)->m_pMenu = BuildContextMenu(); themenu = ((wxDockTaskBarIcon*)m_impl)->m_pMenu; #else delete m_pMenu; m_pMenu = BuildContextMenu(); themenu = m_pMenu; #endif // These should appear in the Mac System Menu but not the Dock themenu->AppendSeparator(); themenu->Append( wxID_EXIT, _("E&xit"), wxEmptyString ); themenu->SetEventHandler(this); SetUpSystemMenu((MenuRef)(themenu->GetHMenu()), imageRef); } if(imageRef != NULL) CGImageRelease( imageRef ); } #if wxCHECK_VERSION(2,8,0) wxMenu* CMacSystemMenu::GetCurrentMenu() { return ((wxDockTaskBarIcon*)m_impl)->m_pMenu; } #endif // Utility routine to load a bundle from the application's Frameworks folder. // i.e. : "BOINC.app/Contents/Frameworks/SystemMenu.bundle" void CMacSystemMenu::LoadPrivateFrameworkBundle( CFStringRef framework, CFBundleRef *bundlePtr ) { CFURLRef baseURL = NULL; CFURLRef bundleURL = NULL; CFBundleRef myAppsBundle = NULL; if ( bundlePtr == NULL ) goto Bail; *bundlePtr = NULL; myAppsBundle = CFBundleGetMainBundle(); // Get our application's main bundle from Core Foundation if ( myAppsBundle == NULL ) goto Bail; baseURL = CFBundleCopyPrivateFrameworksURL( myAppsBundle ); if ( baseURL == NULL ) goto Bail; bundleURL = CFURLCreateCopyAppendingPathComponent( kCFAllocatorSystemDefault, baseURL, framework, false ); if ( bundleURL == NULL ) goto Bail; *bundlePtr = CFBundleCreate( kCFAllocatorSystemDefault, bundleURL ); if ( *bundlePtr == NULL ) goto Bail; if ( ! CFBundleLoadExecutable( *bundlePtr ) ) { CFRelease( *bundlePtr ); *bundlePtr = NULL; } Bail: // Clean up. if ( bundleURL != NULL ) CFRelease( bundleURL ); if ( baseURL != NULL ) CFRelease( baseURL ); } pascal OSStatus SysMenuEventHandler( EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void* pData) { HICommand command; MenuRef baseMenuRef, sysMenuRef; Str255 theMenuTitle; short i, n, m; CharParameter markChar; const UInt32 eventClass = GetEventClass(inEvent); const UInt32 eventKind = GetEventKind(inEvent); CMacSystemMenu* pMSM; wxMenu* baseMenu; MenuCommand commandID; MenuItemIndex menuItemIndex; wxMenuItem* item = NULL; OSStatus err; switch (eventClass) { case kEventClassCommand: if (eventKind != kEventCommandProcess) return eventNotHandledErr; GetEventParameter (inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &command); commandID = command.commandID; if (commandID == 0) return eventNotHandledErr; pMSM = wxGetApp().GetMacSystemMenu(); if (!pMSM) break; // wxMac "helpfully" converts wxID_ABOUT to kHICommandAbout, wxID_EXIT to kHICommandQuit, // wxID_PREFERENCES to kHICommandPreferences switch (commandID) { case kHICommandAbout: commandID = wxID_ABOUT; break; case kHICommandPreferences: { CBOINCBaseFrame* pFrame = wxGetApp().GetFrame(); wxCommandEvent evt(wxEVT_COMMAND_MENU_SELECTED, ID_PREFERENCES); pFrame->AddPendingEvent(evt); return noErr ; } case kHICommandQuit: if (wxGetApp().ConfirmExit()) { commandID = wxID_EXIT; } else { commandID = 0; return noErr; } break; } if (commandID == wxID_ABOUT) pMSM->SetOpeningAboutDlg(true); // If not our system menu, pass event on to next event handler if (command.menu.menuRef != (MenuRef)'BNC!') { // Used only in OS 10.5 if (PLstrcmp("\pBOINC!", (GetMenuTitle((command.menu.menuRef), theMenuTitle) ))) { return eventNotHandledErr; } } // The following code is adapted from wxTaskBarIcon's wxDockEventHandler(). // Work with our base menu instead of the cloned System Menu baseMenu = (pMSM->GetCurrentMenu()); baseMenuRef = (MenuRef)(baseMenu->GetHMenu()); err = GetIndMenuItemWithCommandID(baseMenuRef, command.commandID, 1, NULL, &menuItemIndex); if (err) return eventNotHandledErr; // Command not found in our menu GetMenuItemRefCon( baseMenuRef, menuItemIndex, (UInt32*) &item ) ; if ( item ) { if (item->IsCheckable()) item->Check( !item->IsChecked() ); item->GetMenu()->SendEvent( commandID , item->IsCheckable() ? item->IsChecked() : -1 ); // Under wxWidgets 2.8.0, the task bar icons must be deleted for app to // exit its main loop // Note that if the main window is open, CBOINCBaseFrame::OnExit() will be // called and SysMenuEventHandler() (i.e., this code) will not be called. if (commandID == wxID_EXIT) { wxGetApp().DeleteMacSystemMenu(); wxGetApp().DeleteTaskBarIcon(); } return noErr ; } return eventNotHandledErr; case kEventClassMenu: if (eventKind != kEventMenuOpening) return eventNotHandledErr; GetEventParameter (inEvent, kEventParamDirectObject, typeMenuRef, NULL, sizeof(sysMenuRef), NULL, &sysMenuRef); // If not our system menu, pass event on to next event handler if (PLstrcmp("\pBOINC!", (GetMenuTitle((sysMenuRef), theMenuTitle) ))) return eventNotHandledErr; pMSM = wxGetApp().GetMacSystemMenu(); baseMenu = (pMSM->GetCurrentMenu()); pMSM->AdjustMenuItems(baseMenu); // Copy checkmark and enabled status of each item from Dock menu baseMenuRef = (MenuRef)(baseMenu->GetHMenu()); n = CountMenuItems(sysMenuRef); m = CountMenuItems(baseMenuRef); if (m < n) n = m; for (i=1; i<=n; i++) { GetMenuItemCommandID(baseMenuRef, i, &commandID); SetMenuItemCommandID(sysMenuRef, i, commandID); GetMenuItemText(baseMenuRef, i, theMenuTitle); SetMenuItemText(sysMenuRef, i, theMenuTitle); GetItemMark(baseMenuRef, i, &markChar); SetItemMark(sysMenuRef, i, markChar); if( IsMenuItemEnabled(baseMenuRef, i) ) EnableMenuItem(sysMenuRef, i); else DisableMenuItem(sysMenuRef, i); } return noErr; // Event handling to open or close our main window when applicaiton // is shown or hidden. This probably should go in BOINCGUIApp.cpp // or MainFrame.cpp, but it is more eficient to put it here since // we already have this Mac Event Handler installed. case kEventClassApplication: CBOINCBaseFrame* pFrame = wxGetApp().GetFrame(); pMSM = wxGetApp().GetMacSystemMenu(); switch (eventKind) { case kEventAppHidden: if (pFrame) pFrame->Hide(); pMSM->SetOpeningAboutDlg(false); break; case kEventAppShown: // Don't open main window if "About" Dialog from task bar menu. if (pMSM->IsOpeningAboutDlg()) { pMSM->SetOpeningAboutDlg(false); break; } if (ActiveNonFloatingWindow()) // Prevent infinite loop break; if (pFrame) { pFrame->Show(); pFrame->SendSizeEvent(); } // If a modal dialog was open, make sure it remains in front WindowRef win = GetWindowList(); WindowModality modality = kWindowModalityNone; while (win) { GetWindowModality(win, &modality, NULL); if (modality == kWindowModalityAppModal) BringToFront(win); win = GetNextWindow(win); } break; } return eventNotHandledErr; } // End switch (eventClass) return eventNotHandledErr; }