#
#		Python GUI - Application class - PyObjC
#

import os, sys, traceback
import objc
#from ExceptionHandling import \
#	NSExceptionHandler, NSLogUncaughtExceptionMask, NSLogAndHandleEveryExceptionMask
from Foundation import NSObject, NSBundle, NSDefaultRunLoopMode
import AppKit
from AppKit import NSApplication, NSResponder, NSScreen, NSMenu, NSMenuItem, \
    NSKeyDown, NSKeyUp, NSMouseMoved, NSLeftMouseDown, NSSystemDefined, \
    NSCommandKeyMask, NSPasteboard, NSStringPboardType, NSModalPanelRunLoopMode, \
    NSAnyEventMask
import GApplications
from GApplications import application, Application as GApplication
from Events import Event
from Exceptions import Cancel, Quit
from Menus import _ns_standard_actions

#------------------------------------------------------------------------------

ns_application = None
ns_screen_height = None
ns_last_mouse_moved_event = None

#------------------------------------------------------------------------------

class Application(GApplication):
    #  _ns_app          _PyGui_NSApplication
    #  _ns_pasteboard   NSPasteboard
    #  _ns_key_window   Window
    
    _ns_menubar_update_pending = False

    def __init__(self, **kwds):
        #print "Application.__init__: argv =", sys.argv ###
        create_ns_application()
        self._ns_app = ns_application
        self._ns_app.pygui_app = self
        self._ns_init_standard_menu_items()
        self._ns_pasteboard = NSPasteboard.generalPasteboard()
        self._ns_key_window = None
        GApplication.__init__(self, **kwds)
        ns_application.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)
        ns_app.setMainMenu_(ns_menubar)
        # Apple's docs fail to explain that the menu you pass to setAppleMenu_
        # must *also* be a member of the menu bar.
        ns_app_menu = menu_list[0]._ns_menu
        self._ns_app.setAppleMenu_(ns_app_menu)

    def handle_events(self):
        #print "Application.handle_events: entering NS run loop" ###
        #try:
            self._ns_app.run()
        #finally:
            #print "Application.handle_events: exiting NS run loop" ###

    def handle_next_event(self, modal_window = None):
        #print "Application.handle_next_event" ###
        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, None, 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 _find_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):
        raise NotImplementedError("TODO: Application.get_clipboard")
    
    def set_clipboard(self, data):
        raise NotImplementedError("TODO: Application.set_clipboard")
    
    def _quit(self):
        #print "Application._quit" ###
        self._ns_app.stop_(self)
    
    def _ns_init_standard_menu_items(self):
        self._ns_standard_menu_items = {}
        for (cmd_name, ns_selector) in _ns_standard_actions.iteritems():
            self._ns_standard_menu_items[cmd_name] = NSMenuItem.alloc().\
                    initWithTitle_action_keyEquivalent_("", ns_selector, "")
    
    def _dispatch_menu_setup(self, m):
        for (cmd_name, ns_menu_item) in self._ns_standard_menu_items.iteritems():
            ns_selector = ns_menu_item.action()
            target = self._ns_app.targetForAction_(ns_selector)
            if target and target.respondsToSelector_('validateMenuItem:'):
                valid = target.validateMenuItem_(ns_menu_item)
                m[cmd_name].enabled = valid
        GApplication._dispatch_menu_setup(self, m)
    
    def setup_menus(self, m):
        if not self._ns_app.modalWindow():
            GApplication.setup_menus(self, m)
    
    def process_args(self, args):
        #  Don't need to do anything, since args seem to get passed to
        #  us via application_openFile_ automatically when run with pythonw.
        #  Note: When using py2app, argv_emulation should be disabled.
        pass
        #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
        #	GApplication.process_args(self, args)

    def run(self):
        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)
        os._exit(0)

#------------------------------------------------------------------------------

_ns_key_event_mask = AppKit.NSKeyDownMask | AppKit.NSKeyUpMask

#------------------------------------------------------------------------------

class _PyGui_NSApplication(NSApplication):

    pygui_app = None
    files_opened = False

    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 pending_exception:
            raise_pending_exception()
        ns_type = ns_event.type()
        #print "sendEvent_:", ns_event ###
        if (1 << ns_type) & _ns_key_event_mask:
            self.process_key_event(ns_event)
        else:
            if ns_type == NSMouseMoved:
                global ns_last_mouse_moved_event
                ns_last_mouse_moved_event = ns_event
            elif ns_type == NSSystemDefined and ns_event.subtype() == 7 \
                    and not ns_event.window():
                # Mouse down in the menu bar. We detect it by means of this strange
                # event because mouse-down events in the menu bar don't make it to
                # sendEvent: until it's too late.
                self.perform_menu_setup()
            NSApplication.sendEvent_(self, ns_event)
    
    def 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:
            self.perform_menu_setup()
            NSApplication.sendEvent_(self, ns_event)
        else:
            ns_window = ns_event.window()
            if ns_window:
                ns_window.sendEvent_(ns_event)
            else:
                self.pass_event_to_application(ns_event)
    
    def perform_menu_setup(self):
        app = self.pygui_app
        if app:
            app._perform_menu_setup()
    
    def pass_event_to_application(self, ns_event):
        app = self.pygui_app
        if app:
            event = Event(ns_event)
            app.handle(event.kind, event)

#	def sendAction_to_from_(self, action, target, sender):
#		print "_PyGui_NSApplication.sendAction_to_from_",  action, target, sender ###
#		return NSApplication.sendAction_to_from_(self, action, target, sender)

    def menuSelection_(self, ns_menu_item):
        command = ns_menu_item.representedObject()
        index = ns_menu_item.tag()
        if index >= 0:
            self.dispatch_to_app(command, index)
        else:
            self.dispatch_to_app(command)
    
    def dispatch_to_app(self, *args):
        app = self.pygui_app
        if app:
            app.dispatch(*args)
    
    def validateMenuItem_(self, item):
        return False
    
    def undo_(self, sender):
        self.dispatch_to_app('undo_cmd')

    def redo_(self, sender):
        self.dispatch_to_app('redo_cmd')

    def cut_(self, sender):
        self.dispatch_to_app('cut_cmd')

    def copy_(self, sender):
        self.dispatch_to_app('copy_cmd')

    def paste_(self, sender):
        self.dispatch_to_app('paste_cmd')

    def clear_(self, sender):
        self.dispatch_to_app('clear_cmd')

    def select_all_(self, sender):
        self.dispatch_to_app('select_all_cmd')

    def 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 ns_info:
                if ns_info['CFBundleName'] == "Python":
                    ns_info['CFBundleName'] = GApplications.application_name
                return
        #raise RuntimeError("No bundle information found. "
        #	"Perhaps the application was not run with pythonw?")
    
    def application_openFile_(self, ns_app, path):
        #print "PyGUI_NSApplication.application_openFile_:", path ###
        # 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
        self.files_opened = True
        try:
            self.pygui_app.open_path(path)
            return True
        except Exception, e:
            self.pygui_app.report_error()
            return False
    
    def applicationDidFinishLaunching_(self, notification):
        #print "PyGUI_NSApplication.applicationDidFinishLaunching_"
        if not self.files_opened:
            self.pygui_app.open_app()

#------------------------------------------------------------------------------

pending_exception = None

def raise_pending_exception():
    global pending_exception
    exc_type, exc_value, exc_tb = pending_exception
    pending_exception = None
    raise exc_type, exc_value, exc_tb

def create_ns_application():
    global ns_application
    ns_application = _PyGui_NSApplication.sharedApplication()
    ns_application.setDelegate_(ns_application)

ns_screen_height = NSScreen.mainScreen().frame().size.height

#------------------------------------------------------------------------------

# 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()
