# 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.

__version__ = "$Revision: 1.29 $"

from types import StringType, UnicodeType

from wxPython.wx import *

from Thuban import _
from Thuban.UI.common import Color2wxColour

from Thuban.Model.color import Color

from Thuban.Model.messages import CHANGED
from Thuban.Model.layer import Layer
from Thuban.Model.map import Map

from dialogs import NonModalNonParentDialog
from messages import SESSION_REPLACED, LAYER_SELECTED

BMP_SIZE = 15

class SessionTreeCtrl(wxTreeCtrl):

    """Widget to display a tree view of the session.

    The tree view is created recursively from the session object. The
    tree view calls the session's TreeInfo method which should return a
    pair (<title>, <item>) where <title> ist the title of the session
    item in the tree view and <items> is a list of objects to use as the
    children of the session in the tree view.

    The items list can contain three types of items:

       1. a string. The string is used as the title for a leaf item in
          the tree view.

       2. an object with a TreeInfo method. This method is called and
          should return a pair just like the session's TreeInfo method.

       3. a pair (<title>, <item>) which is treated like the return
          value of TreeInfo.
    """

    def __init__(self, parent, ID, mainwindow, app):

        # Use the WANTS_CHARS style so the panel doesn't eat the Return key.
        wxTreeCtrl.__init__(self, parent, ID)

        self.mainwindow = mainwindow
        self.app = app
        # boolean to indicate that we manipulate the selection ourselves
        # so that we can ignore the selection events generated
        self.changing_selection = 0

        # Dictionary mapping layer id's to tree items
        self.layer_to_item = {}

        self.app.Subscribe(SESSION_REPLACED, self.session_changed)
        self.mainwindow.Subscribe(LAYER_SELECTED, self.layer_selected)

        # the session currently displayed in the tree
        self.session = None


        # pretend the session has changed to build the initial tree
        self.session_changed()

        EVT_TREE_SEL_CHANGED(self, self.GetId(), self.OnSelChanged)

    def unsubscribe_all(self):
        if self.session is not None:
            self.session.Unsubscribe(CHANGED, self.update_tree)
            self.session = None
        self.app.Unsubscribe(SESSION_REPLACED, self.session_changed)
        self.mainwindow.Unsubscribe(LAYER_SELECTED, self.layer_selected)

    def update_tree(self, *args):
        """Clear and rebuild the tree"""
        self.DeleteAllItems()
        self.layer_to_item.clear()
        self.image_list = wxImageList(BMP_SIZE, BMP_SIZE, False, 0)

        bmp = wxEmptyBitmap(BMP_SIZE, BMP_SIZE)
        dc = wxMemoryDC()
        dc.SelectObject(bmp)
        dc.SetBrush(wxBLACK_BRUSH)
        dc.Clear()
        dc.SelectObject(wxNullBitmap)

        self.emptyImageIndex = \
            self.image_list.AddWithColourMask(bmp, wxColour(0, 0, 0))

        self.AssignImageList(self.image_list)

        session = self.app.session
        info = session.TreeInfo()
        root = self.AddRoot(info[0], -1, -1, None)
        self.SetItemImage(root, self.emptyImageIndex)
        self.add_items(root, info[1])
        self.Expand(root)
        # select the selected layer
        selected_layer = self.mainwindow.current_layer()
        if selected_layer is not None:
            # One would expect that the selected_layer's id is in
            # layer_to_item at this point as we've just rebuilt that
            # mapping completely. However, when a new session is loaded
            # for instance, it can happen that the tree view is updated
            # before the canvas's selection in which case selected_layer
            # may be a layer of the old session.
            item = self.layer_to_item.get(id(selected_layer))
            if item is not None:
                self.SelectItem(item)

    def add_items(self, parent, items):

        if items is None: return

        for item in items:
            if hasattr(item, "TreeInfo"):
                # Supports the TreeInfo protocol
                info = item.TreeInfo()
                treeitem = self.AppendItem(parent, info[0], -1, -1, None)
                self.SetItemImage(treeitem, self.emptyImageIndex)
                self.SetPyData(treeitem, item)
                self.add_items(treeitem, info[1])
                self.Expand(treeitem)
                if isinstance(item, Layer):
                    self.layer_to_item[id(item)] = treeitem
            elif isinstance(item, StringType) or \
                 isinstance(item, UnicodeType):
                # it's a string
                treeitem = self.AppendItem(parent, item, -1, -1, None)
                self.SetItemImage(treeitem, self.emptyImageIndex)
            else:
                # assume its a sequence (title, items)
                if isinstance(item[1], Color):

                    treeitem = self.AppendItem(parent, "(%s)" % item[0])

                    bmp = wxEmptyBitmap(BMP_SIZE, BMP_SIZE)
                    brush = wxBrush(Color2wxColour(item[1]), wxSOLID)
                    dc = wxMemoryDC()
                    dc.SelectObject(bmp)
                    dc.SetBrush(brush)
                    dc.Clear()
                    dc.DrawRoundedRectangle(0, 0, 
                                            bmp.GetWidth(), bmp.GetHeight(),
                                            4)
                    dc.SelectObject(wxNullBitmap)

                    i = self.image_list.Add(bmp)
                    self.SetItemImage(treeitem, i)
                else:
                    treeitem = self.AppendItem(parent, item[0], -1, -1, None)
                    self.SetItemImage(treeitem, self.emptyImageIndex)
                    self.add_items(treeitem, item[1])
                self.Expand(treeitem)

    def session_changed(self, *args):
        new_session = self.app.session
        # if the session has changed subscribe/unsubscribe
        if self.session is not new_session:
            if self.session is not None:
                self.session.Unsubscribe(CHANGED, self.update_tree)
            if new_session is not None:
                new_session.Subscribe(CHANGED, self.update_tree)
            self.session = new_session
        self.update_tree()

    def normalize_selection(self):
        """Select the layer or map containing currently selected item"""
        item = self.GetSelection()
        while item.IsOk():
            object = self.GetPyData(item)
            if isinstance(object, Layer) or isinstance(object, Map):
                break
            item = self.GetItemParent(item)
        else:
            # No layer or map was found in the chain of parents, so
            # there's nothing we can do.
            return

        self.changing_selection = 1
        try:
            self.SelectItem(item)
        finally:
            self.changing_selection = 0

    def SelectedLayer(self):
        """Return the layer object currently selected in the tree.
        Return None if no layer is selected"""
        layer = self.GetPyData(self.GetSelection())
        if isinstance(layer, Layer):
            return layer
        return None

    def OnSelChanged(self, event):
        if self.changing_selection:
            # we're changing the selection ourselves (probably through
            # self.normalize_selection(). ignore the event.
            return
        self.normalize_selection()
        # SelectedLayer returns None if no layer is selected. Since
        # passing None to SelectLayer deselects the layer we can simply
        # pass the result of SelectedLayer on in all cases
        self.mainwindow.SelectLayer(self.SelectedLayer())

    def layer_selected(self, layer):
        item = self.layer_to_item.get(id(layer))
        if item is not None and item != self.GetSelection():
            self.SelectItem(item)


class SessionTreeView(NonModalNonParentDialog):

    """Non modal dialog showing the session as a tree"""

    def __init__(self, parent, app, name):
        NonModalNonParentDialog.__init__(self, parent, name, _("Session"))
        self.tree = SessionTreeCtrl(self, -1, parent, app)

    def OnClose(self, event):
        NonModalNonParentDialog.OnClose(self, event)

        # if there were a way to get notified when the tree control
        # itself is destroyed we could use that to unsubscribe instead
        # of doing it here. (EVT_WINDOW_DESTROY doesn't seem to sent at
        # all)
        self.tree.unsubscribe_all()

