# Copyright (C) 2001, 2002, 2003 by Intevation GmbH
# Authors:
# Jan-Oliver Wagner <jan@intevation.de>
# Bernhard Herzog <bh@intevation.de>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with Thuban for details.

"""
Thuban's application object.
"""

__version__ = "$Revision: 1.34 $"

import sys, os
import os.path

import traceback

from wxPython.wx import *

from Thuban.Lib.connector import Publisher
from Thuban.Lib.fileutil import get_application_dir

from Thuban import _
from Thuban.Model.session import create_empty_session
from Thuban.Model.save import save_session
from Thuban.Model.load import load_session, LoadCancelled
from Thuban.Model.messages import MAPS_CHANGED
from Thuban.Model.layer import RasterLayer
import Thuban.Model.resource

import view
import tree
import mainwindow
import dbdialog
import exceptiondialog

from messages import SESSION_REPLACED


class ThubanApplication(wxApp, Publisher):

    """
    Thuban's application class.

    All wxWindows programs have to have an instance of an application
    class derived from wxApp. In Thuban the application class holds
    references to the main window and the session.
    """

    def OnInit(self):
        sys.excepthook = self.ShowExceptionDialog
        self.splash = self.splash_screen()
        if self.splash is not None:
            self.splash.Show()
        self.read_startup_files()
        self.top = self.CreateMainWindow()
        self.SetTopWindow(self.top)
        if self.splash is None:
            self.ShowMainWindow()
        self.session = None
        self.create_session()
        return True

    def OnExit(self):
        """Clean up code.

        Extend this in derived classes if needed.
        """
        self.session.Destroy()
        self.session = None
        Publisher.Destroy(self)

    def read_startup_files(self):
        """Read the startup files."""
        # for now the startup file is ~/.thuban/thubanstart.py
        dir = get_application_dir()
        if os.path.isdir(dir):
            sys.path.append(dir)
            try:
                import thubanstart
            except ImportError:
                tb = sys.exc_info()[2]
                try:
                    if tb.tb_next is not None:
                        # The ImportError exception was raised from
                        # inside the thubanstart module.
                        sys.stderr.write(_("Cannot import the thubanstart"
                                         " module\n"))
                        traceback.print_exc(None, sys.stderr)
                    else:
                        # There's no thubanstart module.
                        sys.stderr.write(_("No thubanstart module available\n"))
                finally:
                    # make sure we delete the traceback object,
                    # otherwise there's be circular references involving
                    # the current stack frame
                    del tb
            except:
                sys.stderr.write(_("Cannot import the thubanstart module\n"))
                traceback.print_exc(None, sys.stderr)
        else:
            # There's no .thuban directory
            sys.stderr.write(_("No ~/.thuban directory\n"))

    def splash_screen(self):
        """Create and return a splash screen.

        This method is called by OnInit to determine whether the
        application should have a splashscreen. If the application
        should display a splash screen override this method in a derived
        class and have it create and return the wxSplashScreen instance.
        The implementation of this method in the derived class should
        also arranged for ShowMainWindow to be called.

        The default implementation simply returns None so that no splash
        screen is shown and ShowMainWindow will be called automatically.
        """
        return None

    def ShowMainWindow(self):
        """Show the main window

        Normally this method is automatically called by OnInit to show
        the main window. However, if the splash_screen method has
        returned a splashscreen it is expected that the derived class
        also arranges for ShowMainWindow to be called at the appropriate
        time.
        """
        self.top.Show(True)

    def CreateMainWindow(self):
        """Create and return the main window for the application.

        Override this in subclasses to instantiate the Thuban mainwindow
        with different parameters or to use a different class for the
        main window.
        """
        msg = (_("This is the wxPython-based Graphical User Interface"
               " for exploring geographic data"))
        return mainwindow.MainWindow(NULL, -1, "Thuban", self, None,
                                     initial_message = msg,
                                     size = (600, 400))

    def Session(self):
        """Return the application's session object"""
        return self.session

    def SetSession(self, session):
        """Make session the new session.

        Issue SESSION_REPLACED after self.session has become the new
        session. After the session has been assigned call
        self.subscribe_session() with the new session and
        self.unsubscribe_session with the old one.
        """
        oldsession = self.session
        self.session = session
        self.subscribe_session(self.session)
        self.issue(SESSION_REPLACED)
        self.maps_changed()
        if oldsession is not None:
            self.unsubscribe_session(oldsession)
            oldsession.Destroy()

    def subscribe_session(self, session):
        """Subscribe to some of the sessions channels.

        Extend this method in derived classes if you need additional
        channels.
        """
        session.Subscribe(MAPS_CHANGED, self.maps_changed)

    def unsubscribe_session(self, session):
        """Unsubscribe from the sessions channels.

        Extend this method in derived classes if you subscribed to
        additional channels in subscribe_session().
        """
        session.Unsubscribe(MAPS_CHANGED, self.maps_changed)

    def create_session(self):
        """Create a default session.

        Override this method in derived classes to instantiate the
        session differently or to use a different session class. Don't
        subscribe to channels here yet. Do that in the
        subscribe_session() method.
        """
        self.SetSession(create_empty_session())

    def OpenSession(self, filename, db_connection_callback = None):
        """Open the session in the file named filename"""
        # Make sure we deal with an absolute pathname. Otherwise we can
        # get problems when saving because the saving code expects an
        # absolute directory name
        filename = os.path.abspath(filename)
        if db_connection_callback is None:
            db_connection_callback = self.run_db_param_dialog
        try:
            session = load_session(filename,
                               db_connection_callback=db_connection_callback)
        except LoadCancelled:
            return
        session.SetFilename(filename)
        session.UnsetModified()
        self.SetSession(session)

        for map in session.Maps():
            for layer in map.Layers():
                if isinstance(layer, RasterLayer) \
                    and not Thuban.Model.resource.has_gdal_support():
                    msg = _("The current session contains Image layers,\n"
                            "but the GDAL library is not available to "
                            "draw them.")
                    dlg = wx.wxMessageDialog(None,
                                             msg,
                                             _("Library not available"),
                                             wx.wxOK | wx.wxICON_INFORMATION)
                    print msg
                    dlg.ShowModal()
                    dlg.Destroy()
                    break

    def run_db_param_dialog(self, parameters, message):
        """Implementation of the db_connection_callback for loading sessions"""
        dlg = dbdialog.DBDialog(None, _("DB Connection Parameters"),
                                parameters, message)
        return dlg.RunDialog()


    def SaveSession(self):
        save_session(self.session, self.session.filename)

    def maps_changed(self, *args):
        """Subscribed to the session's MAPS_CHANGED messages.

        Set the toplevel window's map to the map in the session. This is
        done by calling the window's SetMap method with the map as
        argument. If the session doesn't have any maps None is used
        instead.

        Currently Thuban can only really handle at most one map in a
        sessions so the first map in the session's list of maps as
        returned by the Maps method is used.
        """
        if self.session.HasMaps():
            self.top.SetMap(self.session.Maps()[0])
        else:
            self.top.SetMap(None)

    in_exception_dialog = 0 # flag: are we already inside the exception dialog?

    def ShowExceptionDialog(self, exc_type, exc_value, exc_traceback):
        """Show a message box with information about an exception.
    
        The parameters are the usual values describing an exception in
        Python, the exception type, the value and the traceback.
    
        This method can be used as a value for the sys.excepthook.
        """

        if self.in_exception_dialog:
            return
        self.in_exception_dialog = 1
        while wxIsBusy():
            wxEndBusyCursor() # reset the mouse cursor

        try:
            lines = traceback.format_exception(exc_type, exc_value,
                                            exc_traceback)
            message = _("An unhandled exception occurred:\n%s\n"
                        "(please report to"
                        " http://thuban.intevation.org/bugtracker.html)"
                        "\n\n%s") % (exc_value, "".join(lines))
            print message

            # We don't use an explicit parent here because this method might
            # be called in circumstances where the main window doesn't exist
            # anymore.
            exceptiondialog.run_exception_dialog(None, message)

        finally:
            self.in_exception_dialog = 0
            # delete the last exception info that python keeps in
            # sys.last_* because especially last_traceback keeps
            # indirect references to all objects bound to local
            # variables and this might prevent some object from being
            # collected early enough.
            sys.last_type = sys.last_value = sys.last_traceback = None

