#
#		Python GUI - Application class - Carbon
#

import time
from Carbon.Evt import WaitNextEvent
from Carbon.Events import everyEvent, mouseDown, mouseUp, keyDown, keyUp, autoKey, \
    updateEvt, activateEvt, osEvt, cmdKey, mouseMovedMessage
from Carbon.Qd import NewRgn, RectRgn
from Carbon.Menu import ClearMenuBar, DrawMenuBar, MenuSelect, MenuEvent, HiliteMenu
from Carbon.Scrap import GetCurrentScrap
from Carbon.Win import FrontWindow, FindWindow, WhichWindow
from Carbon.Windows import inMenuBar, inContent, inDrag, inGrow, inGoAway, inZoomIn, inZoomOut
from MacOS import Error as MacOS_Error
from GApplications import Application as GApplication
from GApplications import application
from Events import Event

_mac_window_to_window = {}
_mac_current_menus = []
_mac_menu_id_to_menu = {}

class Application(GApplication):

    _idle_tasks = None		  # In order of scheduled time
    
    def __init__(self, **kwds):
        GApplication.__init__(self, **kwds)
        self.reset_blink()

    def set_menus(self, menu_list):
        GApplication.set_menus(self, menu_list)
        self._mac_update_menubar()
    
    def _window_menus_changed(self, window):
        if window is self._mac_front_window():
            self._mac_update_menubar()
    
    def _mac_update_menubar(self):
        all_menus = self._menus
        window = self._mac_front_window()
        if window:
            all_menus = all_menus + window._menus
        if all_menus <> _mac_current_menus:
            _mac_current_menus[:] = all_menus
            _mac_menu_id_to_menu.clear()
            ClearMenuBar()
            for menu in all_menus:
                menu._mac_menu.InsertMenu(0)
                _mac_menu_id_to_menu[menu._id] = menu
            DrawMenuBar()

    def _mac_next_event(self, mouse_rgn = None):
        while 1:
            timeout = self._idle_task_timeout()
            #print "Application._mac_next_event: timeout = %.3f" % timeout ###
            if timeout is not None:
                ticks = int(60 * timeout)
            else:
                ticks = 0x7fffffff
            flag, mac_event_record = WaitNextEvent(everyEvent, ticks, mouse_rgn)
            if flag:
                return mac_event_record
            else:
                self._execute_idle_task()

    def handle_next_event(self, modal_window = None):
        mac_event_record = self._mac_next_event()
        self._mac_handle_event(mac_event_record, modal_window)
    
    def _next_mouse_event(self):
        mouse_rgn = NewRgn()
        x, y = self._mac_last_mouse_pos
        RectRgn(mouse_rgn, (x, y, x+1, y+1))
        event = None
        while not event:
            mac_event_record = self._mac_next_event(mouse_rgn)
            mac_what = mac_event_record[0]
            if mac_what == mouseUp:
                event = Event(mac_event_record, 'mouse_up')
            elif mac_what == osEvt and ((mac_event_record[1] >> 24) & 0xff == mouseMovedMessage):
                event = Event(mac_event_record, 'mouse_drag')
            elif mac_what == updateEvt or mac_what == activateEvt:
                self._mac_handle_event(mac_event_record, None)
        self._mac_last_mouse_pos = event.global_position
        return event
    
    def _mac_handle_event(self, mac_event_record, modal_window):
        mac_what, mac_message, mac_when, mac_where, mac_modifiers = mac_event_record
        if mac_what == mouseDown:
            mac_part, mac_window = FindWindow(mac_where)
            if mac_part == inMenuBar:
                self._mac_do_menu_setup()
                self._mac_do_menu_item(MenuSelect(mac_where))
            else:
                window = _mac_window_to_window.get(mac_window, None)
                if window:
                    if not modal_window or window is modal_window:
                        if mac_part == inContent:
                            if mac_window == FrontWindow():
                                self._mac_last_mouse_pos = mac_where
                                window._dispatch_mouse_event(Event(mac_event_record))
                            else:
                                window._mac_select()
                        elif mac_part == inDrag:
                            window._mac_drag(mac_where)
                        elif mac_part == inGrow:
                            window._mac_grow(mac_where)
                        elif mac_part == inGoAway:
                            window._mac_go_away(mac_where)
                        elif mac_part == inZoomIn or mac_part == inZoomOut:
                            window._mac_zoom(mac_where, mac_part)
        elif mac_what == keyDown:
            self._mac_do_menu_setup()
            if not self._mac_do_menu_item(MenuEvent(mac_event_record)):
                if not mac_modifiers & cmdKey:
                    self.dispatch('key_down', Event(mac_event_record))
                else:
                    self.dispatch('command_key', Event(mac_event_record))
        elif mac_what == keyUp:
            self.dispatch('key_up', Event(mac_event_record))
        elif mac_what == autoKey:
            self.dispatch('key_down', Event(mac_event_record))
        elif mac_what == updateEvt or mac_what == activateEvt:
            mac_window = WhichWindow(mac_message)
            window = _mac_window_to_window.get(mac_window, None)
            if window:
                if mac_what == updateEvt:
                    window._mac_update()
                else:
                    window.broadcast('activated', mac_modifiers & 1)
                    self._mac_update_menubar()

    def _mac_front_window(self):
        mac_win = FrontWindow()
        return _mac_window_to_window.get(mac_win, None)
    
    def _mac_do_menu_setup(self):
        self._perform_menu_setup(_mac_current_menus)

    def _mac_do_menu_item(self, menu_item):
        #print "Application._mac_do_menu_item: 0x%04X" % menu_item ###
        try:
            menu_id = menu_item >> 16
            item = menu_item & 0xffff
            if menu_id > 0:
                menu = _mac_menu_id_to_menu.get(menu_id, None)
                if menu:
                    command, args = menu._command_and_args_for_item(item)
                    if command:
                        self.dispatch(command, *args)
            elif menu_id < 0 and item == 9: # Quit item of MacOSX application menu
                self.dispatch('quit_cmd')
        finally:
            HiliteMenu(0)
        return menu_id <> 0
    
    _find_target_window = _mac_front_window
    
    def zero_windows_allowed(self):
        return 1

    def query_clipboard(self):
        try:
            return GetCurrentScrap().GetScrapFlavorSize('TEXT') > 0
        except MacOS_Error:
            return 0

    def get_clipboard(self):
        try:
            return GetCurrentScrap().GetScrapFlavorData('TEXT')
        except MacOS_Error:
            return ""
    
    def set_clipboard(self, data):
        GetCurrentScrap().PutScrapFlavor('TEXT', 0, data)

    #
    #		Idle tasks
    #

    def add_idle_task(self, client, interval, message, args = ()):
        task = IdleTask(client, interval, message, args)
        self._schedule_idle_task(task, self._current_time())

    def remove_idle_task(self, client, message):
        tasks = self._idle_tasks
        for i in xrange(len(tasks)):
            task = tasks[i]
            if task.client is client and task.message == message:
                del tasks[i]
                return

    def blink_task(self):
        # print "Blink task" ###
        self.dispatch('blink')

    def reset_blink(self):
        self.remove_idle_task(self, 'blink_task')
        self.add_idle_task(self, 0.5, 'blink_task')

    def _schedule_idle_task(self, task, from_time):
        time = from_time + task.interval
        # print "Application._schedule_idle_task: time =", time ###
        task.time = time
        tasks = self._idle_tasks
        i = 0
        n = len(tasks)
        while i < n and tasks[i].time <= time:
            i = i + 1
        tasks.insert(i, task)

    def _idle_task_timeout(self):
        # Return the time until the next idle task is due,
        # or None if there are no idle tasks.
        tasks = self._idle_tasks
        if tasks:
            return max(tasks[0].time - self._current_time(), 0.0)
        else:
            return None
    
    def _execute_idle_task(self):
        # If the idle task at the head of the queue is due,
        # execute and reschedule it.
        tasks = self._idle_tasks
        if tasks:
            task = tasks[0]
            time_now = self._current_time()
            #print "Application._execute_idle_task: time now = %3f, task time = %3f" % (
            #	time_now, task.time) ###
            if task.time - time_now <= 0.0:
                task.execute()
                del tasks[0]
                #print "...rescheduling after %.3f = %3f" % (
                #	task.interval, time_now + task.interval) ###
                self._schedule_idle_task(task, time_now)

    def _current_time(self):
        return time.time()
    
#------------------------------------------------------------------------------

class IdleTask:
    client = None
    interval = None
    message = None
    args = None
    time = None

    def __init__(self, client, interval, message, args):
        self.client = client
        self.interval = interval
        self.message = message
        self.args = args

    def execute(self):
        method = getattr(self.client, self.message)
        apply(method, self.args)


