#------------------------------------------------------------------------------ # # Python GUI - Application class - PyObjC # #------------------------------------------------------------------------------ import os, sys, traceback import objc from Foundation import NSObject, NSBundle, NSDefaultRunLoopMode, NSData, NSDate import AppKit from AppKit import NSApplication, NSResponder, NSScreen, NSMenu, NSMenuItem, \ NSKeyDown, NSKeyUp, NSMouseMoved, NSLeftMouseDown, NSSystemDefined, \ NSCommandKeyMask, NSPasteboard, NSStringPboardType, NSModalPanelRunLoopMode NSAnyEventMask = 0xffffffff from GUI import Globals, GApplications from GUI import application, export from GUI.GApplications import Application as GApplication from GUI import Event #------------------------------------------------------------------------------ Globals.ns_screen_height = None Globals.ns_last_mouse_moved_event = None Globals.pending_exception = None Globals.ns_application = None ns_distant_future = NSDate.distantFuture() #------------------------------------------------------------------------------ class Application(GApplication): # _ns_app _PyGui_NSApplication # _ns_pasteboard NSPasteboard # _ns_key_window Window _ns_menubar_update_pending = False _ns_files_opened = False _ns_using_clargs = False _ns_menus_updated = False def __init__(self, **kwds): self._ns_app = Globals.ns_application self._ns_app.pygui_app = self self._ns_pasteboard = NSPasteboard.generalPasteboard() self._ns_key_window = None GApplication.__init__(self, **kwds) self.ns_init_application_name() def destroy(self): del self.menus[:] import Windows Windows._ns_zombie_window = None self._ns_app.pygui_app = None self._ns_app = None self._ns_pasteboard = None GApplication.destroy(self) def set_menus(self, menu_list): GApplication.set_menus(self, menu_list) self._update_menubar() def _update_menubar(self): ns_app = self._ns_app ns_menubar = NSMenu.alloc().initWithTitle_("") menu_list = self._effective_menus() for menu in menu_list: ns_item = NSMenuItem.alloc() ns_item.initWithTitle_action_keyEquivalent_(menu.title, '', "") ns_menubar.addItem_(ns_item) ns_menu = menu._ns_menu # An NSMenu can only be a submenu of one menu at a time, so # remove it from the old menubar if necessary. old_supermenu = ns_menu.supermenu() if old_supermenu: i = old_supermenu.indexOfItemWithSubmenu_(ns_menu) old_supermenu.removeItemAtIndex_(i) ns_menubar.setSubmenu_forItem_(ns_menu, ns_item) # The menu you pass to setAppleMenu_ must *also* be a member of the # main menu. ns_app.setMainMenu_(ns_menubar) if menu_list: ns_app_menu = menu_list[0]._ns_menu ns_app.setAppleMenu_(ns_app_menu) def handle_next_event(self, modal_window = None): ns_app = self._ns_app if modal_window: ns_mode = NSModalPanelRunLoopMode ns_modal_window = modal_window._ns_window else: ns_mode = NSDefaultRunLoopMode ns_modal_window = None ns_event = ns_app.nextEventMatchingMask_untilDate_inMode_dequeue_( NSAnyEventMask, ns_distant_future, ns_mode, True) if ns_event: ns_window = ns_event.window() if not ns_window or not ns_modal_window or ns_window == ns_modal_window: ns_app.sendEvent_(ns_event) def get_target_window(self): # NSApplication.keyWindow() isn't reliable enough. We keep track # of the key window ourselves. return self._ns_key_window def zero_windows_allowed(self): return 1 def query_clipboard(self): pb = self._ns_pasteboard pb_types = pb.types() return NSStringPboardType in pb_types def get_clipboard(self): pb = self._ns_pasteboard ns_data = pb.dataForType_(NSStringPboardType) if ns_data: return ns_data.bytes().tobytes() def set_clipboard(self, data): ns_data = NSData.dataWithBytes_length_(data, len(data)) pb = self._ns_pasteboard pb.clearContents() pb.setData_forType_(ns_data, NSStringPboardType) def setup_menus(self, m): m.hide_app_cmd.enabled = True m.hide_other_apps_cmd.enabled = True m.show_all_apps_cmd.enabled = True if not self._ns_app.modalWindow(): GApplication.setup_menus(self, m) def process_args(self, args): # Note: When using py2app, argv_emulation should be disabled. if args and args[0].startswith("-psn"): # Launched from MacOSX Finder -- wait for file open/app launch messages pass else: # Not launched from Finder or using argv emulation self._ns_using_clargs = True GApplication.process_args(self, args) def run(self, fast_exit = True): try: GApplication.run(self) except (KeyboardInterrupt, SystemExit): pass except: traceback.print_exc() # A py2app bundled application seems to crash on exit if we don't # bail out really quickly here (Python 2.3, PyObjC 1.3.7, py2app 0.2.1, # MacOSX 10.4.4) if fast_exit: os._exit(0) def event_loop(self): self._ns_app.run() def _quit(self): self._quit_flag = True self._ns_app.stop_(self._ns_app) def hide_app_cmd(self): self._ns_app.hide_(self) def hide_other_apps_cmd(self): self._ns_app.hideOtherApplications_(self) def show_all_apps_cmd(self): self._ns_app.unhideAllApplications_(self) def ns_process_key_event(self, ns_event): # Perform menu setup before command-key events. # Send non-command key events to associated window if any, # otherwise pass them to the pygui application. This is necessary # because otherwise there is no way of receiving key events when # there are no windows. if ns_event.modifierFlags() & NSCommandKeyMask: NSApplication.sendEvent_(self._ns_app, ns_event) else: ns_window = ns_event.window() if ns_window: ns_window.sendEvent_(ns_event) else: event = Event(ns_event) self.handle(event.kind, event) def ns_menu_needs_update(self, ns_menu): try: if not self._ns_menus_updated: self._perform_menu_setup() self._ns_menus_updated = True except Exception: self.report_exception() def ns_init_application_name(self): # Arrange for the application name to be used as the title # of the application menu. ns_bundle = NSBundle.mainBundle() if ns_bundle: ns_info = ns_bundle.localizedInfoDictionary() if not ns_info: ns_info = ns_bundle.infoDictionary() if ns_info: if ns_info['CFBundleName'] == "Python": #print "GUI.Application: NSBundle infoDictionary =", ns_info ### ns_info['CFBundleName'] = Globals.application_name return #------------------------------------------------------------------------------ _ns_key_event_mask = AppKit.NSKeyDownMask | AppKit.NSKeyUpMask #------------------------------------------------------------------------------ class _PyGui_NSApplication(NSApplication): pygui_app = None def sendEvent_(self, ns_event): # Perform special processing of key events. # Perform menu setup when menu bar is clicked. # Remember the most recent mouse-moved event to use as the # location of event types which do not have a location themselves. if Globals.pending_exception: raise_pending_exception() ns_type = ns_event.type() self.pygui_app._ns_menus_updated = False if (1 << ns_type) & _ns_key_event_mask: self.pygui_app.ns_process_key_event(ns_event) else: if ns_type == NSMouseMoved: Globals.ns_last_mouse_moved_event = ns_event ns_window = ns_event.window() if ns_window: ns_view = ns_window.contentView().hitTest_(ns_event.locationInWindow()) if ns_view: ns_view.mouseMoved_(ns_event) else: NSApplication.sendEvent_(self, ns_event) def menuNeedsUpdate_(self, ns_menu): self.pygui_app.ns_menu_needs_update(ns_menu) def menuSelection_(self, ns_menu_item): try: command = ns_menu_item.representedObject() index = ns_menu_item.tag() if index >= 0: dispatch_to_app(self, command, index) else: dispatch_to_app(self, command) except: self.pygui_app.report_error() def validateMenuItem_(self, item): return False def undo_(self, sender): dispatch_to_app(self, 'undo_cmd') def redo_(self, sender): dispatch_to_app(self, 'redo_cmd') def cut_(self, sender): dispatch_to_app(self, 'cut_cmd') def copy_(self, sender): dispatch_to_app(self, 'copy_cmd') def paste_(self, sender): dispatch_to_app(self, 'paste_cmd') def clear_(self, sender): dispatch_to_app(self, 'clear_cmd') def selectAll_(self, sender): dispatch_to_app(self, 'select_all_cmd') def application_openFile_(self, ns_app, path): app = self.pygui_app if app._ns_using_clargs: return True # Bizarrely, argv[0] gets passed to application_openFile_ under # some circumstances. We don't want to try to open it! if path == sys.argv[0]: return True app._ns_files_opened = True try: app.open_path(path) return True except Exception, e: app.report_error() return False def applicationDidFinishLaunching_(self, notification): app = self.pygui_app if app._ns_using_clargs: return try: if not app._ns_files_opened: app.open_app() except Exception, e: app.report_error() return False export(Application) #------------------------------------------------------------------------------ def raise_pending_exception(): exc_type, exc_value, exc_tb = Globals.pending_exception Globals.pending_exception = None raise exc_type, exc_value, exc_tb def create_ns_application(): ns_app = _PyGui_NSApplication.sharedApplication() ns_app.setDelegate_(ns_app) Globals.ns_application = ns_app def dispatch_to_app(ns_app, *args): app = ns_app.pygui_app if app: app.dispatch(*args) Globals.ns_screen_height = NSScreen.mainScreen().frame().size.height create_ns_application() #------------------------------------------------------------------------------ # Disable this for now, since MachSignals.signal segfaults. :-( # #def _install_sigint_handler(): # print "_install_sigint_handler" ### # from Foundation import NSRunLoop # run_loop = NSRunLoop.currentRunLoop() # if not run_loop: # print "...No current run loop" ### # sys.exit(1) ### # MachSignals.signal(signal.SIGINT, _sigint_handler) # #from PyObjCTools.AppHelper import installMachInterrupt # #installMachInterrupt() # print "...done" ### # #def _sigint_handler(signum): # print "_sigint_handler" ### # raise KeyboardInterrupt #def _install_sigint_handler(): # import signal # signal.signal(signal.SIGINT, _raise_keyboard_interrupt) # #def _raise_keyboard_interrupt(signum, frame): # raise KeyboardInterrupt #_install_sigint_handler()