# GNU Enterprise Forms - wx 2.6 UI Driver - Box widget
#
# 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: grid.py 10036 2009-11-03 13:45:11Z reinhard $
"""
Implementation of a <grid>.  A grid is a grid control made of multiple
<gridlines>.
"""

import wx

from gnue.forms.uidrivers.wx26.widgets import _base, gridline

__all__ = ["UIGrid"]


# =============================================================================
# Interface implementation for a box widget
# =============================================================================

class UIGrid (_base.UIHelper):
    """
    The visual container for the grid control.
    """

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

    def __init__(self, event):

        _base.UIHelper.__init__(self, event)
        self.__max = 0
        self.__visible = 0


    # -------------------------------------------------------------------------
    # Create a wx box widget
    # -------------------------------------------------------------------------

    def _create_widget_ (self, event, spacer):
        """
        Create the wx.Panel for the grid control and add it to the owner.
        The spacer is ignored for <grid> tags.

        @param event: the creation-event instance carrying information like
            container (parent-widget)
        @param spacer: not used for grid tags
        """

        owner  = self.getParent()
        parent = event.container

        # We need a little hack here: When the user enlarges the form, we want
        # the grid to add more lines.  That's easy.  However, when the user
        # makes the form smaller again, we want those extra grid lines to
        # disappear again.  Unfortunately, the extra grid lines will define the
        # minimum size for the whole grid, so the sizers will never reduce the
        # size of the grid below what the current number of lines need, and we
        # will never even notice that we should reduce the number of lines.
        # Our hack: The grid, together with a small 3 pixel high panel is
        # placed on a vertical sizer.  The grid is not resizable, but the panel
        # is.  Now if the panel grows smaller than 3 pixel, we need to remove a
        # grid line.
        # The size of the panel is chosen with 3 pixel so it can double as the
        # lower border of the grid.

        # The base panel for the grid control
        self.widget = wx.Panel(parent, -1)
        self.widget.Bind(wx.EVT_SIZE, self.__on_size)
        self.widget.Bind(wx.EVT_MOUSEWHEEL, self.__on_mousewheel)

        # The outer sizer: a vertical sizer containing the grid and the small 3
        # pixel panel.
        outer = wx.BoxSizer(wx.VERTICAL)
        self.widget.SetSizer(outer)

        # The inner sizer: a horizontal sizer containing the grid to the left
        # and the scroll bar to the right.
        inner = wx.BoxSizer(wx.HORIZONTAL)
        outer.Add(inner, 0, wx.EXPAND)

        # The actual grid.
        self._container = wx.Panel(self.widget, -1)
        self._container.SetSizer(wx.GridBagSizer())
        inner.Add(self._container, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 3)

        # The scroll bar.
        self.scroll = wx.ScrollBar(self.widget, -1, style=wx.SB_VERTICAL)
        self.scroll.Bind(wx.EVT_SCROLL, self.__on_scroll)
        inner.Add(self.scroll, 0, wx.EXPAND | wx.TOP, 3)

        # The small 3 pixel panel that doubles as the lower border.  We have to
        # reset the min size to 0 later (in update_size_hints) *after* the form
        # has been initially layouted, so the panel starts with 3 pixel height
        # in the initial layout.
        self.__spacer = wx.Panel(self.widget, -1, size=(0, 3))
        outer.Add(self.__spacer, 1, wx.EXPAND)

        self.__max = self._gfObject.rows
        self.__visible = self.__max

        self.__build_header()

        owner.add_widgets(self, spacer)

        return None


    # -------------------------------------------------------------------------
    # Build the first row of the grid (the header)
    # -------------------------------------------------------------------------

    def __build_header(self):

        sizer = self._container.GetSizer()
        num_cols = 0
        linenum = 0
        for line in self._gfObject.findChildrenOfType('GFGridLine', True, True):
            index = 0
            for item in line._children:
                span = int(getattr(item, 'Sizer__span', 1))
                panel = wx.Panel(self._container, -1, style=wx.RAISED_BORDER)
                box = wx.BoxSizer(wx.HORIZONTAL)
                panel.SetSizer(box)
                static = wx.StaticText(panel, -1, getattr(item, 'label') or u"")
                box.Add(static, 1, wx.EXPAND)
                sizer.Add(panel, (linenum, index), (1, span), wx.EXPAND)
                index += span
            num_cols = max(num_cols, index)
            linenum += 1

        # For now, make all columns growable:
        for col in range(0, num_cols):
            sizer.AddGrowableCol(col)


    # -------------------------------------------------------------------------
    # Get the row-number of a concrete gridline in the GridBagSizer
    # -------------------------------------------------------------------------

    def _get_row(self, line, record):
        """
        Get the row number of a concrete gridline in the GridBagSizer
        @param line: the UIGridLine instance we're interested in
        @param record: the spacer (rel. record-number) of the line in question
        @returns: the row within the GridBagSizer
        """

        return len(self._children) * (record + 1) + self._children.index(line)


    # -------------------------------------------------------------------------
    # Event-Handler
    # -------------------------------------------------------------------------

    def __on_size(self, event):

        if not self._uiForm.sizing_enabled:
            event.Skip()
            return

        saved = self._uiForm.sizing_enabled
        self._uiForm.sizing_enabled = False

        try:
            free_height = self.__spacer.GetSize()[1] - 3

            rec_height = 0
            for item in self._children:
                rec_height += max([panel.GetSize()[1] \
                    for panel in item._columns[0]])

            if free_height >= rec_height:
                self.__add_new_records(free_height / rec_height)
                self._gfObject._event_rows_changed(self.__visible)

            elif free_height < 0:
                self.__hide_records(1)
                self._gfObject._event_rows_changed(self.__visible)

        finally:
            self._uiForm.sizing_enabled = saved

        event.Skip()

    # -------------------------------------------------------------------------

    def __on_mousewheel(self, event):

        if event.GetWheelRotation() < 0:
            delta = 1
        else:
            delta = -1
        self._gfObject._block._event_scroll_delta(delta)

    # -------------------------------------------------------------------------

    def __on_scroll(self, event):
        self._gfObject._block._event_scroll_to_record(event.GetPosition())
        event.Skip()

    # -------------------------------------------------------------------------
    # Add new records to the grid (after resizing it)
    # -------------------------------------------------------------------------

    def __add_new_records(self, num_recs):

        for index in range(num_recs):
            record = self.__visible + index

            if record >= self.__max:
                self.walk(self.__child_add_walker, record)
                self.__max += 1
            else:
                self.__change_visibility(record, True)

        self.__visible += num_recs

        self._uiForm.main_window.Layout()


    # -------------------------------------------------------------------------
    # Create all child-widgets
    # -------------------------------------------------------------------------

    def __child_add_walker(self, item, spacer):

        if item == self:
            return

        widget = item.create_widget(item._creationEvent, spacer)
        item.widgets.append(widget)


    # -------------------------------------------------------------------------
    # Show or hide grid lines
    # -------------------------------------------------------------------------

    def __change_visibility(self, record, state):

        grid = self._container.GetSizer()

        for item in self._children:
            row = self._get_row(item, record)
            for col in range(len(item._children)):
                item = grid.FindItemAtPosition((row, col))
                if item:
                    item.Show(state)


    # -------------------------------------------------------------------------
    # Hide a given number of records
    # -------------------------------------------------------------------------

    def __hide_records(self, num_recs):

        for index in range(num_recs):
            if self.__visible == 1:
                break
            self.__change_visibility(self.__visible-1, False)
            self.__visible -= 1

        self._uiForm.main_window.Layout()


    # -------------------------------------------------------------------------
    # Change our spacer to be allowed to be reduced to 0
    # -------------------------------------------------------------------------

    def update_size_hints(self):

        self.__spacer.SetMinSize((0, 0))


    # -------------------------------------------------------------------------
    # Adjust scrollbar if the current record has changed
    # -------------------------------------------------------------------------

    def _ui_adjust_scrollbar_(self, position, size, count):
        """
        Adjust the thumb-position and the number of rows of the scrollbar
        """
        self.scroll.SetScrollbar(position, size, count, size - 1, True)


    # -------------------------------------------------------------------------
    # Indicate whether this box is vertically growable
    # -------------------------------------------------------------------------

    def is_growable(self):

        return True


# =============================================================================
# Configuration data
# =============================================================================

configuration = {
  'baseClass': UIGrid,
  'provides' : 'GFGrid',
  'container': 1
}
