#
#		Python GUI - Application class - Generic
#

import sys
import os
from Properties import Properties, overridable_property
from MessageHandlers import MessageHandler
from Exceptions import Cancel, Quit, UnimplementedMethod
from Menus import Menu
from GMenus import MenuState

#MUNGE_KEY_EVENTS = 1

_application = None

class Application(Properties, MessageHandler):
    """The user should create exactly one Application object,
    or subclass thereof. It implements the main event loop
    and other application-wide behaviour."""
    
    _windows = None				# List of all existing Windows
    _documents = None			# List of all existing Documents
    _menus = []						# Menus to appear in all Windows
    _clipboard = None

    _std_menus = [
        Menu("File",
            ("New/N", 'new_cmd'),
            ("Open.../O", 'open_cmd'),
            "-",
            ("Close/W", 'close_cmd'),
            ("Save/S", 'save_cmd'),
            ("Save As...", 'save_as_cmd'),
            ("Revert", 'revert_cmd'),
            "-",
            ("Quit/Q", 'quit_cmd')
        ),
        Menu("Edit",
            ("Undo/Z", 'undo_cmd'),
            "-",
            ("Cut/X", 'cut_cmd'),
            ("Copy/C", 'copy_cmd'),
            ("Paste/V", 'paste_cmd'),
            ("Clear", 'clear_cmd')
        )
    ]

    def __init__(self):
        global _application
        self._windows = []
        self._documents = []
        self._update_list = []
        self._idle_tasks = []
        _application = self
        self.menus = self.std_menus

    #
    #		Constants
    #

    def get_std_menus(self):
        """Returns a list of Menus containing the standard
        framework-defined menu commands in their standard 
        positions."""
        return self._std_menus
    
    std_menus = property(get_std_menus)

    #
    #		Properties
    #

    def get_windows(self):
        return self._windows
    
    windows = overridable_property('windows', "A list of all existing Windows.")

    def get_documents(self):
        return self._documents
    
    documents = overridable_property('documents', "A list of all existing documents.")

    def get_menus(self):
        return self._menus

    def set_menus(self, menus):
        self._menus = menus
    
    menus = overridable_property('menus', """A list of Menus that are to be available from
        all Windows.""")

    #
    #		Event loop
    #

    def run(self):
        """The main event loop. Runs until a Quit exception
        is raised."""
        while 1:
            try:
                self.event_loop()
            except (KeyboardInterrupt, Quit):
                return
            except SystemExit:
                raise
            except:
                _report_exception()
    
    def event_loop(self, exit = None):
        """Loop reading and handling events until exit() returns
        true or an exception other than Cancel is raised."""
        while not (exit and exit()):
            try:
                if exit:
                    self.handle_next_event()
                else:
                    self.handle_events()
            except Cancel:
                pass

    def handle_events(self):
        """Handle events until an exception occurs. Waits for at least one event;
        may handle more, at the discretion of the implementation."""
        self.handle_next_event()
    
    def handle_next_event(self):
        """Wait for the next event to arrive and handle it. Transparently handles 
        any internal events such as window updates, etc., and executes any idle tasks 
        that become due while waiting for an event."""
        raise UnimplementedMethod(self, "handle_next_event")
    
    #
    #		Menu commands
    #

    def setup_menus(self, m):
        m.new_cmd.enabled = 1
        m.open_cmd.enabled = 1
        m.quit_cmd.enabled = 1

    def new_cmd(self):
        "Handle the New menu command."
        doc = self.make_new_document()
        doc.new_contents()
        self.make_window(doc)

    def open_cmd(self):
        "Handle the Open... menu command."
        from FileDialogs import request_old_file
        fileref = request_old_file()
        if fileref:
            doc = self.make_file_document(fileref)
            doc.set_file(fileref)
            doc.read()
            self.make_window(doc)

    def quit_cmd(self):
        """Handle the Quit menu command."""
        while self._documents:
            self._documents[0].close_cmd()
        raise Quit

    #
    #   Message dispatching
    #
    
    def dispatch(self, message, *args):
        target_window = self._find_target_window()
        if target_window:
            target_window.dispatch(message, *args)
        else:
            self.handle(message, *args)

    def _find_target_window(self):
        """Return the window to which messages should be
        dispatched, or None."""
        raise NotImplementedError
    
    #
    #		Abstract
    #

    def make_new_document(self):
        """Should create a new Document object of the appropriate 
        class in response to a New command."""
        raise UnimplementedMethod(self, 'make_new_document')

    def make_file_document(self, fileref):
        """Should create a new Document object of the appropriate 
        class for the given FileRef."""
        raise UnimplementedMethod(self, 'make_file_document')

    def make_window(self, document):
        """Should create a Window set up appropriately for viewing
        the given Document."""
        raise UnimplementedMethod(self, 'make_window')

    #
    #		Clipboard
    #

    def query_clipboard(self):
        "Tests whether the clipboard contains any data."
        return not not self._clipboard
    
    def get_clipboard(self):
        return self._clipboard

    def set_clipboard(self, x):
        self._clipboard = x
    
    #
    #   Window list management
    #
    
    def _add_window(self, window):
        if window not in self._windows:
            self._windows.append(window)
    
    def _remove_window(self, window):
        if window in self._windows:
            self._windows.remove(window)

    #
    #   Document list management
    #
    
    def _add_document(self, doc):
        if doc not in self._documents:
            self._documents.append(doc)
    
    def _remove_document(self, doc):
        if doc in self._documents:
            self._documents.remove(doc)

    #
    #   Other
    #
    
    def zero_windows_allowed(self):
        """Platforms should implement this to return false if there
        must be at least one window open at all times. Returning false 
        here forces the Quit command to be used instead of Close when 
        there is only one window open."""
        # TODO: Move this somewhere more global.
        raise UnimplementedMethod(self, 'zero_windows_allowed')
    
    def _perform_menu_setup(self, menus):
        """Given a list of Menu objects, perform menu setup processing
        and update associated platform menus ready for popping up or
        pulling down."""
        menu_state = MenuState(menus)
        self.dispatch('_setup_menus', menu_state)
        for menu in menus:
            menu._update_platform_menu()

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

def application():
    """Returns the global Application object. Creates a default one if needed."""
    global _application
    if not _application:
        from Applications import Application
        _application = Application()
    return _application

#------------------------------------------------------------------------------
#
#		$private
#
#------------------------------------------------------------------------------

def _report_exception():
    try:
        app_name = sys.argv[0]
        exc_type, exc_val, exc_tb = sys.exc_info()
        exc_desc = "%s: %s" % (exc_type.__name__, exc_val)
        from AlertFunctions import alert3
        message = "Sorry, %s encountered a problem and was unable to" \
            " complete the operation." % app_name
        result = alert3('stop', "%s\n\n%s" % (message, exc_desc),
            "Continue", "Abort", "Traceback",
            default = 1, cancel = None, width = 450, lines = 5)
        if result == 1:
            return
        elif result == -1:
            _display_traceback(exc_desc, exc_tb)
            return
    except:
        print "<<<<< Exception while reporting exception >>>>>"
        import traceback
        traceback.print_exc()
        print "<<<<< ----------------------------------- >>>>>"
    #  Get here if we want to bail out of the application
    raise exc_type, exc_val, exc_tb

def _display_traceback(exc_desc, exc_tb):
    #  Replace this sometime with a window showing the traceback.
    import traceback
    print
    traceback.print_tb(exc_tb)
    print exc_desc
    print

