# GNU Enterprise Forms - UI drivers - Base class for widgets implementation
#
# Copyright 2001-2009 Free Software Foundation
#
# This file is part of GNU Enterprise
#
# GNU Enterprise is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 3, or (at your option) any later version.
#
# GNU Enterprise is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with program; see the file COPYING. If not,
# write to the Free Software Foundation, Inc., 59 Temple Place
# - Suite 330, Boston, MA 02111-1307, USA.
#
# $Id: _base.py 9956 2009-10-11 18:54:57Z reinhard $

"""
Base class for UIwidgets used by specific UI drivers to subclass their widgets
from.
"""

from gnue.common import events
from gnue.common.definitions import GParser
from gnue.common.definitions.GObjects import GObj
from gnue.forms.GFObjects import GFTabStop, GFBox, GFScrollBar, GFLabel

__all__ = ['UIWidget', 'InvalidBoundingBoxError']

# =============================================================================
# Exceptions
# =============================================================================

class InvalidBoundingBoxError(GParser.MarkupError):
    """ Element A overlaps Element B """
    def __init__(self, current, item):
        cur_type = current._type[2:]
        cmp_type = item._type[2:]
        msg = u_("Widget %(cur_type)s '%(cur_name)s' overlaps %(cmp_type)s "
                 "'%(cmp_name)s'") \
              % {'cur_type': cur_type, 'cur_name': current.name,
                 'cmp_type': cmp_type, 'cmp_name': item.name}
        GParser.MarkupError.__init__(self, msg, current._url,
                current._lineNumber)

# =============================================================================

class InvalidSpanError(GParser.MarkupError):
    """ An element has an invalid width or height """
    def __init__(self, item):
        msg = u_("Widget %(type)s '%(name)s' has an invalid width or height") \
                % {'type': item._type[2:], 'name': item.name}
        GParser.MarkupError.__init__(self, msg, item._url, item._lineNumber)

# =============================================================================
# Base class for ui widgets
# =============================================================================

class UIWidget(GObj):
    """
    Base class for user interface widgets.

    @ivar _gfObject: The GFObject for this UIWidget
    @ivar widgets: Sequence of ui driver specific widgets created for this
        UIWidget.
    @ivar _container: If this widget can contain other widgets then _container
        is the specific container children should use (as parent)
    @ivar managed: This boolean flag determines wether a widget is used in a
        managed layout or in a positioned one
    @ivar _uiDriver: the GFUserInterface instance of the current ui driver used
        to render the form
    @ivar _uiForm: the UIForm widget this widget is a child of
    @ivar _form: the GFForm instance owning this widget

    @ivar bouding_boxes: is a list of tuples with the bounding boxes of all the
        widgets children. Such a tuple is made like (left, top, right, bottom,
        gfObject of the corresponding child).
    @ivar chr_x: zero-based x coordinate of the widget
    @ivar chr_y: zero-based y coordinate of the widget
    @ivar chr_w: width of the widgets in character-positions
    @ivar chr_h: height of the widgets in character-positions
    @ivar chr_pos: tuple of (chr_y, chr_x) defining the upper left corner
    @ivar chr_span: tuple of (chr_h, chr_w) defining the number of rows and
        columns the widget occupies
    """

    # -------------------------------------------------------------------------
    # Constructor
    # -------------------------------------------------------------------------

    def __init__(self, event):

        GObj.__init__(self, parent=event.parent)
        self._type = ("%s" % self.__class__).split('.')[-1][:-2]
        self._inits = [self.__primary_init]

        self._creationEvent = event
        self._gfObject = event.object
        self._container = None 
        self._uiForm = None
        self._uiDriver = None
        self._form = None

        self.managed = self._gfObject._form._layout.managed
        if self.managed:
            self.stretch = int(getattr(self._gfObject, 'Sizer__stretch', 1))
            self.min_width = int(getattr(self._gfObject, 'Sizer__min_width', 0))
            self.min_height = int(getattr(self._gfObject, 'Sizer__min_height',
                0))
            self.max_width = int(getattr(self._gfObject, 'Sizer__max_width', 0))
            self.max_height = int(getattr(self._gfObject, 'Sizer__max_height',
                0))
            self.def_width = int(getattr(self._gfObject, 'Sizer__def_width', 0))
            self.def_height = int(getattr(self._gfObject, 'Sizer__def_height',
                0))
            self.span = int(getattr(self._gfObject, 'Sizer__span', 1))

        else:
            self.bounding_boxes = []
            self.chr_w = None
            self.chr_h = None
            self.chr_x = None
            self.chr_y = None
            self.chr_pos = None
            self.chr_span = None


        self.in_grid = self.findParentOfType('UIGrid') is not None

        if self.in_grid:
            self.span = int(getattr(self._gfObject, 'Sizer__span', 1))

        self.widgets = []

        # TODO: The following assignments of itemWidth/Height as well as itemX
        # should be either removed completely or moved to the ui drivers
        # specific implementation.
        if hasattr(self._gfObject, 'Char__width'):
            self.itemWidth = self._gfObject.Char__width * int(event.textWidth)
        else:
            self.itemWidth = -1

        if hasattr(self._gfObject, 'Char__height'):
            self.itemHeight = self._gfObject.Char__height*int(event.textHeight)
        else:
            self.itemHeight = -1

        if hasattr(self._gfObject, 'Char__x'):
            self.itemX = self._gfObject.Char__x * int(event.widgetWidth)
        else:
            self.itemX = -1


    # -------------------------------------------------------------------------
    # Phase 1 initialization
    # -------------------------------------------------------------------------

    def __primary_init(self):

        self._uiDriver = self.findParentOfType('UIDriver')
        self._uiForm = self.findParentOfType('UIForm')

        try:
            owner = self.getParent()
            self._creationEvent.container = owner._container

        except AttributeError:
            if not hasattr(self._creationEvent, 'container'):
                self._creationEvent.container = None

        if not getattr(self._gfObject, 'hidden', False):
            for spacer in range(int(self._gfObject._rows)):
                widget = self.create_widget(self._creationEvent, spacer)
                self.widgets.append(widget)


    # -------------------------------------------------------------------------
    # Create an instance of the ui widget
    # -------------------------------------------------------------------------

    def create_widget(self, event, spacer):
        """
        """

        if not self.managed:
            if isinstance(self._gfObject, (GFBox, GFTabStop, GFLabel,
                GFScrollBar)):
                gap = self._gfObject._gap + 1

                self.chr_y = self._gfObject.Char__y + spacer * gap
                self.chr_x = self._gfObject.Char__x
                self.chr_w = self._gfObject.Char__width
                self.chr_h = getattr(self._gfObject, 'Char__height', 1)

                self.chr_pos  = (self.chr_y, self.chr_x)
                self.chr_span = (self.chr_h, self.chr_w)
                if 0 in self.chr_span:
                    raise InvalidSpanError(self._gfObject)

                if getattr(self._uiDriver, '__rearrange_boxes__', False):
                    self.__check_bounding_box()


        return self._create_widget_(event, spacer)


    # -------------------------------------------------------------------------
    # Verify if the widget has a proper bounding box
    # -------------------------------------------------------------------------

    def __check_bounding_box(self):

        # Check the bounding box of the parent
        (left, top, right, bottom) = self.get_bounding_box()

        owner = self.getParent()
        if isinstance(owner._gfObject, GFBox):
            (oright, obottom) = owner.get_bounding_box()[2:]

            if right >= oright or bottom >= obottom or left < 0 or top < 0:
                raise InvalidBoundingBoxError(self._gfObject, owner._gfObject)

        for (cleft, ctop, cright, cbot, citem) in owner.bounding_boxes:
            if (right < cleft) or (left > cright) or \
                    (bottom < ctop) or (top > cbot):
                continue
            raise InvalidBoundingBoxError(self._gfObject, citem)

        owner.bounding_boxes.append((left, top, right, bottom, self._gfObject))


    # -------------------------------------------------------------------------
    # Get the bounding box of an UIWidget
    # -------------------------------------------------------------------------

    def get_bounding_box(self):
        """
        Get the bounding box of the widget.  For managed layouts this is always
        None.  For positioned layouts it is a tuple of character-positions
        (left, top, right, bottom)

        @returns: bounding-box as (left, top, right, bottom)
        """

        if self.managed:
            return None
        else:
            return (self.chr_x, self.chr_y, self.chr_x + self.chr_w - 1,
                    self.chr_y + self.chr_h - 1)


    # -------------------------------------------------------------------------
    # Helper functions for descendants
    # -------------------------------------------------------------------------

    # -------------------------------------------------------------------------
    # Fire a request* event
    # -------------------------------------------------------------------------

    def _request(self, name, **params):
        """
        Fire a request<name> event passing the GFObject and the GFObject's form
        as additional event parameters.

        @param name: name of the event, i.e. a name of 'FOO' would fire a
            'requestFOO' event.
        @param params: dictionary with parameters passed with the event. this
            dictionary will always get the keys '_object' (with the GFObject of
            this widget) and '_form' (with the GFForm of this widget) set.
        """

        params['_object'] = self._gfObject
        params['_form'] = self._gfObject._form

        self._uiDriver.dispatchEvent(events.Event("request%s" % name, **params))


    # -------------------------------------------------------------------------
    # Virtual methods // Descendants should override these functions
    # -------------------------------------------------------------------------
    # TODO: add docstrings to these methods and pep8-ify them

    def _create_widget_(self, event, spacer):
        assert gDebug(1, "UI does not support %s" % self.__class__)
