357 lines
12 KiB
Python
357 lines
12 KiB
Python
#------------------------------------------------------------------------------
|
|
#
|
|
# 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()
|