# Copyright (c) 2001, 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 Thuban for details.

"""
Selection handling
"""

__version__ = "$Revision: 2709 $"
# $Source$
# $Id: selection.py 2709 2006-10-03 09:34:51Z dpinte $

from Thuban.Lib.connector import Publisher

from messages import LAYER_SELECTED, SHAPES_SELECTED


class Selection(Publisher):

    """Manage the selection

    The selection consists of a list of shape ids and the layer that
    contains those shapes.

    Messages issued by the selection:

    LAYER_SELECTED -- A different layer has been selected or there was a
                      layer selected and the selection has been cleared.

                      Arguments: The newly selected layer or None if no
                      layer is selected.

    SHAPES_SELECTED -- The set of selected shapes has changed.

                       Arguments: The newly selected layer or None if no
                       layer is selected and a list with ids of the
                       selected shapes. The list is empty if no shapes
                       are selected.
    """

    def __init__(self):
        """Initialize the selection

        Initially nothing is selected so the selected layer is None and
        the list of selected shapes is empty.
        """
        self.layer = None
        self.shapes = []

    def issue_messages(self, issue_layer, issue_shape):
        """Internal: Issue SHAPES_SELECTED and LAYER_SELECTED messages

        If the issue_layer argument is true, issue a LAYER_SELECTED
        message. If issue_shape is true issue a SHAPES_SELECTED message.
        """
        if issue_layer:
            self.issue(LAYER_SELECTED, self.layer)
        if issue_shape:
            self.issue(SHAPES_SELECTED, self.layer, self.SelectedShapes())

    def ClearSelection(self):
        """Unselect the currently selected shapes if any.

        If there was a layer selected before the call, send a
        LAYER_SELECTED message. If there were selected shapes before the
        call, send a SHAPES_SELECTED message as well.
        """
        issue_layer = self.layer is not None
        issue_shape = len(self.shapes) > 0

        self.layer = None
        self.shapes = []
        self.issue_messages(issue_layer, issue_shape)

    def SelectedLayer(self):
        """Return the selected layer. If no layer is selected return None"""
        return self.layer

    def HasSelectedLayer(self):
        """Return true iff a layer is currently selected"""
        return self.layer is not None

    def SelectedShapes(self):
        """Return the ids of the selected shapes as a list.

        The list is sorted in ascending order. If no shapes are selected
        the list is empty.
        """
        return self.shapes

    def HasSelectedShapes(self):
        """Return true iff at least one shape is selected."""
        return len(self.shapes) > 0

    def SelectLayer(self, layer):
        """Select the given layer.

        If the layer is the currently selected layer, do nothing.
        Ortherwise, issue a LAYER_SELECTED message and if there were
        shapes selected preciously issue a SHAPES_SELECTED message as
        well.
        """
        if self.layer is not layer:
            self.layer = layer
            issue_shape = len(self.shapes) > 0
            self.shapes = []
            self.issue_messages(1, issue_shape)

    def SelectShapes(self, layer, shapes, add = 0):
        """Select the shapes given in the list shapes in the given layer.

        If the optional keyword parameter add is true, add the selected
        shapes to the currently selected shapes if layer is the already
        selected layer if any. If add is false (the default) make the
        shapes in shapes the selected shapes.

        If a different layer is selected afterwards issue a
        LAYER_SELECTED message. If the set of selected shapes has
        changed afterwards issue a SHAPES_SELECTED message.
        """
        # Turn the shapes explicitly into a list. This main purpose
        # is that we get a mutable sequence that we can modify in place.
        shapes = list(shapes)
        shapes.sort()

        if self.layer is not layer:
            # If the layer is differen we can ignore the add parameter
            # and we have to issue both messages.
            self.layer = layer
            self.shapes = shapes
            issue_layer = issue_shape = 1
        else:
            # If the layer is differen we have to merge the ids if add
            # is true, and we won't have to issue the LAYER_SELECTED
            # message.
            issue_layer = 0
            if add:
                # Merge the ids by creating a dict with the ids in
                # self.shapes and in shapes as the keys. The use the sorted
                # keys of the dict as the new shapes list.
                set = {}
                for i in self.shapes:
                    set[i] = 1
                for i in shapes:
                    set[i] = 1
                shapes = set.keys()
                shapes.sort()
            if self.shapes != shapes:
                self.shapes = shapes
                issue_shape = 1
            else:
                issue_shape = 0

        self.issue_messages(issue_layer, issue_shape)

    def AddShapes(self, layer, shapes):
        """Add the shapes on layer to the selection.

        If the layer is the already selected layer, add the shape ids in
        shapes to the already selected shapes.

        If the layer is not the already selected layer, make the layer
        and shapes the selected layer and shapes.
        """
        self.SelectShapes(layer, shapes)
