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

"""List control for projections"""

__version__ = "$Revision: 2878 $"
# $Source$
# $Id: projlist.py 2878 2009-06-02 11:06:15Z dpinte $

import wx

from Thuban import _

from Thuban.Lib.connector import Publisher

from Thuban.Model.messages import PROJECTION_ADDED, PROJECTION_REMOVED, \
     PROJECTION_REPLACED


PROJ_SELECTION_CHANGED = "PROJ_SELECTION_CHANGED"


class ProjectionList(wx.ListCtrl, Publisher):

    """A ListCtrl that shows a list of projections

    The list control is effectively a view on several ProjFile instances
    and a specific 'original' projection (e.g. the projection of the
    current map). The list control subscribes to the change messages of
    the ProjFile instances to update the list whenever they change.

    When the selection changes the instance sends a
    PROJ_SELECTION_CHANGED message.
    """

    def __init__(self, parent, proj_files, orig_proj, ID = -1):
        """Initialize the ProjectionList

        Parameters:

        parent -- The parent widget
        proj_files -- a sequence of ProjFile objects
        orig_proj -- The projection originally selected in the map/layer
        """
        wx.ListCtrl.__init__(self, parent, ID, style=wx.LC_REPORT|wx.LC_VIRTUAL)

        self.InsertColumn(0, _("Available Projections"))
        self.proj_files = proj_files
        self._subscribe_proj_files()
        self.orig_proj = orig_proj
        self.projections = []
        self.proj_map = {}
        self.needs_update = False
        self.update_projections()
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_LEFT_UP, self.mouse_left_up)
        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.item_selected, id=self.GetId())
        self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.item_deselected, id=self.GetId())
        self.Bind(wx.EVT_IDLE, self.OnIdle)

    def __del__(self):
        Publisher.__del__(self)

    def _subscribe_proj_files(self):
        """Subscribe to the messages of self.proj_files"""
        for pf in self.proj_files:
            pf.Subscribe(PROJECTION_ADDED, self.pf_projection_added)
            pf.Subscribe(PROJECTION_REMOVED, self.pf_projection_removed)
            pf.Subscribe(PROJECTION_REPLACED, self.pf_projection_replaced)

    def _unsubscribe_proj_files(self):
        """Unsubscribe from the messages subscribed to in _subscribe_proj_files
        """
        for pf in self.proj_files:
            pf.Unsubscribe(PROJECTION_ADDED, self.pf_projection_added)
            pf.Unsubscribe(PROJECTION_REMOVED, self.pf_projection_removed)
            pf.Unsubscribe(PROJECTION_REPLACED, self.pf_projection_replaced)

    def Destroy(self):
        self._unsubscribe_proj_files()
        # Call wxListCtrl's method last because afterwards self is not
        # an instance of ProjectionList anymore as wxPython replaces
        # self.__class__ with its dead object class
        Publisher.Destroy(self)
        wx.ListCtrl.Destroy(self)

    def update_on_idle(self):
        self.needs_update = True

    def OnIdle(self, evt):
        if self.needs_update:
            self.needs_update = False
            self.update_projections()
        evt.Skip()

    def update_projections(self):
        """Update the internal list of projection information"""

        # Remember which projections are selected so that we can select
        # them again after the update
        selection = {}
        for p in self.selected_projections():
            selection[id(p[0])] = 1
        old_length = len(self.projections)

        # Build the new projection list
        projections = [(_("<None>"), None, None)]
        for pf in self.proj_files:
            for p in pf.GetProjections():
                projections.append((p.Label(), p, pf))
        if self.orig_proj is not None:
            projections.append((_("%s (current)") % self.orig_proj.Label(),
                                self.orig_proj, None))
        self.projections = projections
        self.projections.sort()

        # Deselect all items with indices higher than the new length
        # before settign the new item count This is a work-around for a
        # bug in the listctrl which doesn't updat the selection count
        # correctly in a call to SetItemCount if items have been
        # selected with indices higher than the new count.
        if len(self.projections) < old_length:
            for i in xrange(len(self.projections), old_length):
                self.SetItemState(i, 0, wx.LIST_STATE_SELECTED)

        self.SetItemCount(len(self.projections))

        # Reselect the projections that had been selected before.
        get = selection.get
        count = 0
        for i in xrange(len(self.projections)):
            p = self.projections[i][1]
            if get(id(p)):
                state = wx.LIST_STATE_SELECTED
                count += 1
            else:
                state = 0
            self.SetItemState(i, state, wx.LIST_STATE_SELECTED)

        # If the selection changed send a PROJ_SELECTION_CHANGED message
        if count != len(selection):
            self._issue_proj_selection_changed()

    def pf_projection_added(self, proj):
        """Subscribed to the projfile's PROJECTION_ADDED messages

        Request an update the projection list in idle time.
        """
        self.update_on_idle()

    def pf_projection_removed(self, proj):
        """Subscribed to the projfile's PROJECTION_REMOVED messages

        Request an update the projection list in idle time.
        """
        self.update_on_idle()

    def pf_projection_replaced(self, old, new):
        """Subscribed to the projfile's PROJECTION_REPLACED messages

        Request an update the projection list in idle time.
        """
        self.update_on_idle()

    def OnSize(self, evt):
        self.SetColumnWidth(0, evt.GetSize().width)
        evt.Skip()

    def SelectProjection(self, proj):
        """Select the projection and deselect all others."""
        # Set both the wxLIST_STATE_SELECTED and wxLIST_STATE_FOCUSED
        # flags on the newly selected item. If only
        # wxLIST_STATE_SELECTED is set, some other item is focused and
        # the first time the focus is moved with the keyboard the
        # selection moves in unexpected ways.
        state = wx.LIST_STATE_SELECTED|wx.LIST_STATE_FOCUSED
        for i in range(len(self.projections)):
            p = self.projections[i][1]
            self.SetItemState(i, p is proj and state or 0, state)

    def ClearSelection(self):
        """Deselect all projections."""
        for i in range(len(self.projections)):
            self.SetItemState(i, 0, wx.LIST_STATE_SELECTED)

    def SetProjFiles(self, proj_files):
        """Set the projfile objects whose projections are shown in the list"""
        self._unsubscribe_proj_files()
        self.proj_files = proj_files
        self._subscribe_proj_files()
        self.update_projections()

    def OnGetItemText(self, item, col):
        """Callback for the virtual ListCtrl mode"""
        return self.projections[item][0]

    def OnGetItemAttr(self, item):
        """Callback for the virtual ListCtrl mode"""
        return None

    def OnGetItemImage(self, item):
        """Callback for the virtual ListCtrl mode"""
        return -1

    def selected_projections(self):
        """Return a list with all selected projection infos

        The return value is a list of (proj, proj_file) pairs. proj_file
        is the projection file object the projection was read from, if
        any. Both proj and proj_file may be None, but if proj_file is
        not None proj will also not be None.
        """
        return [self.projections[i][1:]
                    for i in range(len(self.projections))
                        if self.GetItemState(i, wx.LIST_STATE_SELECTED)]

    def _issue_proj_selection_changed(self):
        """Internal: Issue a PROJ_SELECTION_CHANGED message"""
        self.issue(PROJ_SELECTION_CHANGED, self.selected_projections())

    def item_selected(self, evt):
        """Handler for EVT_LIST_ITEM_SELECTED"""
        self._issue_proj_selection_changed()

    def item_deselected(self, evt):
        #"""Handler for EVT_LIST_ITEM_DESELECTED"""
        self._issue_proj_selection_changed()

    def mouse_left_up(self, evt):
        """Handle EVT_LEFT_UP events. Issue a selection message.

        It's not clear whether the selection really has changed but the
        selection events send by the list ctrl don't cover selecting
        ranges of items :(.
        """
        self._issue_proj_selection_changed()
