#
#		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
			self._ns_app.using_clargs = True
			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
	using_clargs = 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):
		if self.using_clargs:
			True
		#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):
		if self.using_clargs:
			return
		#print "PyGUI_NSApplication.applicationDidFinishLaunching_"
		try:
			if not self.files_opened:
				self.pygui_app.open_app()
		except Exception, e:
			self.pygui_app.report_error()
			return False

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

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