#!/usr/bin/env python
# -*- coding: utf-8 -*-
# generated by wxGlade 0.6 on Sun May 25 23:31:23 2008

# Copyright 2008 Martin Manns
# Distributed under the terms of the GNU General Public License
# generated by wxGlade 0.6 on Mon Mar 17 23:22:49 2008

# --------------------------------------------------------------------
# pyspread 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 of the License, or
# (at your option) any later version.
#
# pyspread 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 pyspread.  If not, see <http://www.gnu.org/licenses/>.
# --------------------------------------------------------------------

"""
_grid
========

Provides:
---------
  1. MainGridTable: Interaction class for communicating with pysgrid
  2. TextCellEditor: Custom cell editor
  3. TextRenderer: Standard renderer for text output
  4. GridIndexMixin: Grid mixin for indexing and __len__
  5. GridSelectionMixin: Grid mixin for handling selections
  6. GridClipboardMixin: Grid mixin for handling selections
  7. MainGrid: Main grid

"""

from copy import copy
from math import sin, cos, pi
from itertools import izip
import string
import types

try:
    from itertools import product
except ImportError:
    # Python <= 2.5 
    def product(*args, **kwds):
        # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
        # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111
        pools = map(tuple, args) * kwds.get('repeat', 1)
        result = [[]]
        for pool in pools:
            result = [x+[y] for x in result for y in pool]
        for prod in result:
            yield tuple(prod)

import wx
import wx.grid
import wx.combo
import numpy

from _pyspread.config import odftags, DEFAULT_FONT, faces, dpi, GRID_LINE_PEN
from _pyspread.config import column_width_tag, row_height_tag
from _pyspread.config import default_cell_attributes, selected_cell_brush
from _pyspread._datastructures import PyspreadGrid
from _pyspread._choicebars import ContextMenu
from _pyspread._interfaces import Clipboard, Digest, PysInterfaces, \
                                 get_pen_from_data, get_brush_from_data, \
                                 get_font_from_data
from _pyspread.irange import irange

class MainGridTable(wx.grid.PyGridTableBase):
    """Table base class that handles interaction between MainGrid and pysgrid"""
    
    def __init__(self, grid):
        self.grid = grid
        self.pysgrid = grid.pysgrid
        
        wx.grid.PyGridTableBase.__init__(self)
        
        # we need to store the row length and column length to
        # see if the table has changed size
        self._rows = self.GetNumberRows()
        self._cols = self.GetNumberCols()
    
    def GetNumberRows(self):
        """Return the number of rows in the grid"""
        
        return self.pysgrid.shape[0]
    
    def GetNumberCols(self):
        """Return the number of columns in the grid"""
        
        return self.pysgrid.shape[1]
    
    def GetRowLabelValue(self, row):
        """Returns row number"""
        
        return str(row)
    
    def GetColLabelValue(self, col):
        """Returns column number"""
        
        return str(col)
    
    def IsEmptyCell(self, row, col):
        """Return True if the cell is empty"""
        
        if self.pysgrid[row, col, self.grid.current_table] is None:
            return True
        else:
            return False
    
    def GetSource(self, row, col, table=None):
        """Return the source string of a cell"""
        
        if table is None:
            table = self.grid.current_table
            
        value = self.pysgrid.sgrid[row, col, table]
        
        if value is None:
            return u""
        else:
            return value

    def GetValue(self, row, col, table=None):
        """Return the result value of a cell"""
        
        if table is None:
            table = self.grid.current_table
        
        value = self.pysgrid[row, col, table]
        
        if value is None:
            return u""
        else:
            return value
    
    def SetValue(self, row, col, value, refresh=True):
        """Set the value of a cell"""
        
        self.pysgrid[row, col, self.grid.current_table] = value
        
    def UpdateValues(self):
        """Update all displayed values"""
        
        # This sends an event to the grid table to update all of the values
        msg = wx.grid.GridTableMessage(self, wx.grid.GRIDTABLE_REQUEST_VIEW_GET_VALUES)
        self.grid.ProcessTableMessage(msg)

    def ResetView(self):
        """
        (Grid) -> Reset the grid view.   Call this to
        update the grid if rows and columns have been added or deleted
        """
        
        grid = self.grid
        
        grid.BeginBatch()

        for current, new, delmsg, addmsg in [
            (self._rows, self.GetNumberRows(), 
             wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED, 
             wx.grid.GRIDTABLE_NOTIFY_ROWS_APPENDED),
            (self._cols, self.GetNumberCols(), 
             wx.grid.GRIDTABLE_NOTIFY_COLS_DELETED, 
             wx.grid.GRIDTABLE_NOTIFY_COLS_APPENDED),
        ]:

            if new < current:
                msg = wx.grid.GridTableMessage(self,delmsg,new,current-new)
                grid.ProcessTableMessage(msg)
            elif new > current:
                msg = wx.grid.GridTableMessage(self,addmsg,new-current)
                grid.ProcessTableMessage(msg)
                self.UpdateValues()

        grid.EndBatch()

        self._rows = self.GetNumberRows()
        self._cols = self.GetNumberCols()

        # update the scrollbars and the displayed part of the grid
        grid.AdjustScrollbars()
        grid.ForceRefresh()

# end of class MainGridTable

class TextCellEditor(wx.grid.PyGridCellEditor):
    """Custom cell editor
    
    All the methods that can be overridden are present. The ones that 
    must be overridden are marked with "*Must Override*" in the docstring.
    
    """
    
    def __init__(self, parent):
        self.parent = parent
        wx.grid.PyGridCellEditor.__init__(self)

    def Create(self, parent, id, evtHandler):
        """
        Called to create the control, which must derive from wx.Control.
        *Must Override*
        """
        
        self._tc = EntryLine(parent, id, "", grid=self.parent)
        self._tc.SetInsertionPoint(0)
        self.SetControl(self._tc)
        
        if evtHandler:
            self._tc.PushEventHandler(evtHandler)

    def Show(self, show, attr):
        """
        Show or hide the edit control.  You can use the attr (if not None)
        to set colours or fonts for the control.
        """
        super(TextCellEditor, self).Show(show, attr)

    def BeginEdit(self, row, col, grid):
        """
        Fetch the value from the table and prepare the edit control
        to begin editing.  Set the focus to the edit control.
        *Must Override*
        """
        self.startValue = grid.GetTable().GetSource(row, col)
        try:
            self._tc.SetValue(self.startValue)
        except TypeError:
            pass
        self._tc.SetInsertionPointEnd()
        self._tc.SetFocus()

        # For this example, select the text
        self._tc.SetSelection(0, self._tc.GetLastPosition())

    def EndEdit(self, row, col, grid):
        """
        Complete the editing of the current cell. Returns True if the value
        has changed.  If necessary, the control may be destroyed.
        *Must Override*
        """
        changed = False
        
        val = self._tc.GetValue()
        
        if val != self.startValue:
            changed = True
            grid.GetTable().SetValue(row, col, val) # update the table
        self.startValue = ''
        
        self.parent.pysgrid.unredo.mark()
        
        return changed

    def Reset(self):
        """
        Reset the value in the control back to its starting value.
        *Must Override*
        """
        self._tc.SetValue(self.startValue)
        
        self._tc.SetValue(self.startValue)
        self._tc.SetInsertionPointEnd()

    def StartingKey(self, evt):
        """
        If the editor is enabled by pressing keys on the grid, this will be
        called to let the editor do something about that first key if desired.
        """
        key = evt.GetKeyCode()
        ch = None
        if key in [ wx.WXK_NUMPAD0, wx.WXK_NUMPAD1, wx.WXK_NUMPAD2, 
                    wx.WXK_NUMPAD3, wx.WXK_NUMPAD4, wx.WXK_NUMPAD5, 
                    wx.WXK_NUMPAD6, wx.WXK_NUMPAD7, wx.WXK_NUMPAD8, 
                    wx.WXK_NUMPAD9 ]:
            ch = ch = chr(ord('0') + key - wx.WXK_NUMPAD0)

        elif key < 256 and key >= 0 and chr(key) in string.printable:
            ch = chr(key)

        if ch is not None:
            # For this example, replace the text.  Normally we would append it.
            #self._tc.AppendText(ch)
            self._tc.SetValue(ch)
            self._tc.SetInsertionPointEnd()
        else:
            evt.Skip()

    def StartingClick(self):
        """
        If the editor is enabled by clicking on the cell, this method will be
        called to allow the editor to simulate the click on the control if
        needed.
        """
        pass

    def Clone(self):
        """
        Create a new object which is the copy of this one
        *Must Override*
        """
        return TextCellEditor(parent=self)

# end of class TextCellEditor

class TextRenderer(wx.grid.PyGridCellRenderer):
    """This renderer draws borders and text at specified font, size, color"""

    def __init__(self, table):
        
        wx.grid.PyGridCellRenderer.__init__(self)
        self.table = table
        
        self.redraw_imminent = True
        
        evts = [wx.EVT_MOUSEWHEEL,
                wx.EVT_SCROLLWIN,
               ]
        
        for evt in evts:
            table.Bind(evt, self.OnRedraw_imminent)
        
        
    def OnRedraw_imminent(self, event):
        """A redraw is imminent -> set background and grid redraw flag"""
        
        self.redraw_imminent = True
        
        event.Skip()

    def Clip(self, dc, rect):
        """Setup the clipping rectangle"""
        
        dc.SetClippingRegion(rect.x, rect.y, rect.width, rect.height)
    
    def Unclip(self, dc):
        """Destroy the clippinmg rectangle"""
        
        dc.DestroyClippingRegion()
    
    def get_visible_rows(self, grid, row, col):
        """Returns a lists of the visible rows
        
        Parameters
        ----------
        grid: wx.Grid
        \tCurrent grid
        row: Integer
        \tLast visible row
        col: Integer
        \tLast visible col
        
        """
        
        visible_rows = []
        
        check_row = row + 0
        
        while check_row >= 0 and \
              grid.IsVisible(check_row, col, wholeCellVisible=False):
            visible_rows.append(check_row)
            check_row -= 1
        
        return visible_rows
        
    def get_visible_cols(self, grid, row, col):
        """Returns a lists of the visible rows
        
        Parameters
        ----------
        grid: wx.Grid
        \tCurrent grid
        row: Integer
        \tLast visible row
        col: Integer
        \tLast visible col
        
        """
        
        visible_cols = []
        
        check_col = col + 0
        
        while check_col >= 0 and \
              grid.IsVisible(row, check_col, wholeCellVisible=False):
            visible_cols.append(check_col)
            check_col -= 1
        
        return visible_cols
        
    def draw_visible_backgrounds(self, grid, attr, dc, rect, rows, cols):
        """Draws all backgrounds for all visible cells"""
        
        pysgrid = self.table.pysgrid
        tab = self.table.current_table
        
        vis_cells_rects = (grid.CellToRect(r, c) for r in rows for c in cols)
        vis_cells_tuples = [(r.x - 1, r.y - 1, r.width + 1, r.height + 1) \
                                for r in vis_cells_rects]
        
        default_bp = get_pen_from_data(default_cell_attributes["borderpen"]())
        
        # Now redraw all cells that have non-standard borders
        bordered_cells = []
        
        for row in rows:
            for col in cols:
                key = (row, col, tab)
                if get_pen_from_data(pysgrid.get_sgrid_attr(key, 
                    "borderpen")) != default_bp:
                    bordered_cells.append(key)
        
        bordered_cells_rects = (grid.CellToRect(row, col) 
                                for row, col in bordered_cells)
        vis_cells_tuples += [(row.x - 1, row.y - 1, 
                              row.width + 1, row.height + 1) \
                                for row in bordered_cells_rects]
        
        borderpens = []
        bgbrushes = []
        
        for row in rows:
            for col in cols:
            
                key = (row, col, tab)
                # Create pens
                borderpen = get_pen_from_data( \
                    pysgrid.get_sgrid_attr(key, "borderpen"))
                borderwidth = borderpen.GetWidth()
                bordercolor = borderpen.GetColour()
                borderstyle = borderpen.GetStyle()

                zoomed_borderwidth = max(1, int(round(borderwidth * grid.zoom)))
                zoomed_pen = wx.Pen(bordercolor, zoomed_borderwidth, 
                                    borderstyle)
                zoomed_pen.SetJoin(wx.JOIN_MITER)

                borderpens.append(zoomed_pen)
                colstr = str(borderpen.GetColour())

                # Create brushes
                bgbrush = get_brush_from_data( \
                    pysgrid.get_sgrid_attr(key, "bgbrush"))
                bgbrushes.append(bgbrush)
        
        # Draw the background
        dc.DrawRectangleList(vis_cells_tuples, borderpens, bgbrushes)

    def draw_selection_background(self, grid, dc, rect):
        """Draws the background for a selected cell"""
        
        self.Clip(dc, rect)
        
        dc.SetBrush(selected_cell_brush)
        dc.SetPen(wx.TRANSPARENT_PEN)
        draw_rect = wx.Rect(rect.x + 1, rect.y + 1, 
                            rect.width - 2, rect.height - 2)
        dc.DrawRectangleRect(draw_rect)
            
        self.Unclip(dc)

    def _draw_strikethrough_line(self, grid, attr, dc, rect, angle, 
                                 string_x, string_y, text_extent):
        """Draws the strikethrough line"""
        
        # wx.FONTFLAG_STRIKETHROUGH

        strikethroughwidth = max(1, int(round(1.5 * grid.zoom)))
        dc.SetPen(wx.Pen(wx.BLACK, strikethroughwidth, wx.SOLID))

        x1 = string_x
        y1 = string_y + text_extent[1] / 2
        x2 = string_x + text_extent[0] + 2
        y2 = string_y + text_extent[1] / 2

        if not -0.0001 < angle < 0.0001:

            rot_angle = angle / 180.0 * pi

            def rotation(x, y, angle, base_x=0.0, base_y=0.0):
                x -= base_x
                y -= base_y

                __x =  cos(rot_angle) * x + sin(rot_angle) * y
                __y = -sin(rot_angle) * x + cos(rot_angle) * y

                __x += base_x
                __y += base_y

                return __x, __y

            x1, y1 = rotation(x1, y1, rot_angle, 
                              base_x=string_x, base_y=string_y)
            x2, y2 = rotation(x2, y2, rot_angle,
                              base_x=string_x, base_y=string_y)

        dc.DrawLine(x1, y1, x2, y2)

    def Draw(self, grid, attr, dc, rect, row, col, isSelected):
        """Draws the cell border and content"""
        
        pysgrid = self.table.pysgrid
        key = (row, col, self.table.current_table)
        
        # Draw background if this is the last (at least partly) visible cell
                    
        is_bottomright_cell = \
            grid.IsVisible(row, col, wholeCellVisible=False) and \
            not grid.IsVisible(row + 1, col, wholeCellVisible=False) and \
            not grid.IsVisible(row, col + 1, wholeCellVisible=False)
        
        if self.redraw_imminent or is_bottomright_cell:
            
            # Set up cell attribute cache
            
            visible_rows = self.get_visible_rows(grid, row, col)
            visible_cols = self.get_visible_cols(grid, row, col)
            
            # Draw grid background rects
            self.draw_visible_backgrounds(grid, attr, dc, rect, 
                                          visible_rows, visible_cols)
            
            # Reset the background draw flag
            if is_bottomright_cell:
                self.redraw_imminent = False
        
        if isSelected:
            self.draw_selection_background(grid, dc, rect)
        
        textattributes = pysgrid.get_sgrid_attr(key, "textattributes")
        
        textfont = get_font_from_data( \
            pysgrid.get_sgrid_attr(key, "textfont"))
        
        # Check if the dc is drawn manually be a return func
        
        res = grid.pysgrid[row, col, grid.current_table]
        if type(res) is types.FunctionType and \
            True:
            # add func_dict attribute so that we are sure that it uses a dc
            res(grid, attr, dc, rect)
            
            # We do not want the string representation rendered so we return
            return None
        
        # Text label
        res_text = grid.GetCellValue(row, col)
        
        try:
            fontcolortag = odftags["fontcolor"]
            textcolor = textattributes[fontcolortag]
        except KeyError:
            textcolor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT)
        
        try:
            underline_mode = textattributes[odftags["underline"]]
        except KeyError:
            underline_mode = None
        
        dc.SetBackgroundMode(wx.TRANSPARENT)
        dc.SetTextForeground(textcolor)
        
        # Adjust font size to zoom
        
        font_size = textfont.GetPointSize()
        
        zoomed_fontsize = max(1, int(round(font_size * grid.zoom)))
        
        zoomed_font = wx.Font(zoomed_fontsize, textfont.GetFamily(),
            textfont.GetStyle(), textfont.GetWeight(), 
            underline_mode == "continuous", textfont.GetFaceName())
        dc.SetFont(zoomed_font)
        
        # Position string
        
        text_extent = dc.GetTextExtent(res_text)
        
        try: 
            text_align_tag = odftags["textalign"]
            horizontal_align = textattributes[text_align_tag]
        except KeyError: 
            pass
        
        try:
            vert_align_tag = odftags["verticalalign"]
            vertical_align = textattributes[vert_align_tag]
        except KeyError:
            vertical_align = "top"
        
        if vertical_align == "middle":
            string_y = rect.y + rect.height / 2 - text_extent[1] / 2 + 1
            
        elif vertical_align == "bottom":
            string_y = rect.y + rect.height - text_extent[1]
        
        else:
            string_y = rect.y + 2
        
        try:
            rot_angle_tag = odftags["rotationangle"]
            angle = float(textattributes[rot_angle_tag])
        except KeyError:
            angle = 0.0
        
        try:
            justification_tag = odftags["justification"]
            justification = textattributes[justification_tag]
        except KeyError:
            justification = "left"
        
        if justification == "left":
            string_x = rect.x + 2
        elif justification == "right":
            # First calculate x value for unrotated text
            string_x = rect.x + rect.width - 2
            
            # Now map onto rotated xy position
            rot_angle = angle / 180.0 * pi
            string_x = string_x - text_extent[0] * cos(rot_angle)
            string_y = string_y + text_extent[0] * sin(rot_angle)
        else:
            raise ValueError, "Cell justification must be left or right"
        
        dc.DrawRotatedText(res_text, string_x, string_y, angle)
        
        try:
            strikethrough_tag = odftags["strikethrough"]
            strikethrough = textattributes[strikethrough_tag]
        except KeyError:
            strikethrough = "transparent"
        
        if strikethrough in ["solid"]:
            self._draw_strikethrough_line(grid, attr, dc, rect, angle,
                                          string_x, string_y, text_extent)
        
# end of class TextRenderer
        

class GridIndexMixin(object):
    """Indexing, slicing and len extension class for wx.grid.Grid
    
    The class extends a wx.grid with indexing, slicing and len support.
    Cell content can be read and written by indexing.
    
    
    New methods
    -----------
    
    __getitem__
    \tProvides cell access via indexing and slicing
    __setitem__
    \tAllows setting the cell conent via indexing
    __len__
    \tReturns the number of grid rows
    
    """

    def __getitem__(self, key):
        row, col = key[:2]
        if row < 0:
            row += self.GetNumberRows()
        if row >= 0:
            return self.GetCellValue(row, col)


    def __setitem__(self, key, value):
        if len(key) == 2:
            key = list(key) + [self.current_table]
        elif len(key) != 3:
            raise KeyError("key must be of length 2 or 3")
        
        row, col, tab = key
        if row < 0:
            row += self.GetNumberRows()
        if row >= 0:
            return self.SetCellValue(row, col, value)

    def __getshape(self):
        return (self.GetNumberRows(), self.GetNumberCols())
    shape = property(__getshape)

    def __len__(self):
        return self.GetNumberRows()

# end of class GridIndexMixin

class GridSelectionMixin(object):
    """Easy selection support extension class for wx.grid.Grid
    
    Public methods:
    ---------------
    get_selected_rows_cols
    get_selection
    get_selection_code
    get_currentcell
    get_visiblecell_slice
    getselectiondata
    selection_replace
    selectnewcell
    delete
    
    """
    
    def get_selected_rows_cols(self, selection = None):
        """ Get the slices of selected rows and cols, None if no selection """
        if selection is None:
            selection = self.get_selection()
        try:
            rowslice = slice(min(c[0] for c in selection), \
                             max(c[0] for c in selection) + 1)
        except IndexError:
            rowslice = None
        try: 
            colslice = slice(min(c[1] for c in selection), \
                             max(c[1] for c in selection) + 1)
        except IndexError: 
            colslice = None
        return rowslice, colslice
    
    def get_selection(self):
        """ Returns an index list of all cells that are selected in the grid.
        All selection types are considered equal. If no cells are selected,
        the current cell is returned."""
                
        # GetSelectedCells: individual cells selected by ctrl-clicking
        # GetSelectedRows: rows selected by clicking on the labels
        # GetSelectedCols: cols selected by clicking on the labels
        # GetSelectionBlockTopLeft
        # GetSelectionBlockBottomRight: For blocks of cells selected by dragging
        # across the grid cells.
              
        dimx, dimy = self.pysgrid.sgrid.shape[:2]
        
        selected_rows = self.GetSelectedRows()
        selected_cols = self.GetSelectedCols()
        
        selection = []
        selection += self.GetSelectedCells()
        selection += list((row, y) \
                          for row in selected_rows for y in xrange(dimy))
        selection += list((x, col) \
                          for col in selected_cols for x in xrange(dimx))
        
        selectionblock = izip(self.GetSelectionBlockTopLeft(), \
                              self.GetSelectionBlockBottomRight())
        for topleft, bottomright in selectionblock:
            selection += [(x, y) for x in xrange(topleft[0], bottomright[0]+1) \
                                 for y in xrange(topleft[1], bottomright[1]+1)]
        
        if selection == []:
            selection = [(self.get_currentcell())]
        selection = sorted(list(set(selection)))
        return selection
    
    def get_selection_code(self):
        """Returns code for accessing the current selection from a cell"""

        selection = self.get_selection()
        
        # If only one cell is selected return the representation
        
        if len(selection) == 1:
            return "S[%d, %d, %d]" % \
                (selection[0][0], selection[0][1], self.current_table)

        # Check if selection is rectangular
        min_x = min(x for x,y in selection)
        max_x = max(x for x,y in selection) + 1
        min_y = min(y for x,y in selection)
        max_y = max(y for x,y in selection) + 1

        rect = [(x, y) for x in xrange(min_x, max_x) \
                       for y in xrange(min_y, max_y)]

        if set(selection) == set(rect):
            # If a rectangular set is selected return slice
            
            code = "S[%d:%d, %d:%d, %d]" % (min_x, max_x, 
                    min_y, max_y, self.current_table)
        else:
            # If a non-rectangular set is selected return list comprehension
            code = "[S[x, y, " + repr(self.current_table) + \
                   "] for x, y in " + repr(selection) + "]"
        
        return code

    
    def get_currentcell(self):
        """Get cursor position"""
        
        row = self.GetGridCursorRow()
        col = self.GetGridCursorCol()
        return row, col
    
    def get_visiblecell_slice(self):
        """Returns a tuple of 3 slices that contanins the visible cells"""
        
        topleft_x = self.YToRow(self.GetViewStart()[1] * self.ScrollLineX)
        topleft_y = self.XToCol(self.GetViewStart()[0] * self.ScrollLineY)
        topleft = (topleft_x, topleft_y)
        row, col = topleft_x + 1, topleft_y + 1 # Ensures visibility
        
        while self.IsVisible(row, topleft[1], wholeCellVisible=False):
            row += 1
        while self.IsVisible(topleft[0], col, wholeCellVisible=False):
            col += 1
        lowerright = (row, col) # This cell is *not* visible
        return (slice(topleft[0], lowerright[0]), \
                slice(topleft[1], lowerright[1]), 
                slice(self.current_table, self.current_table+1))
    
    def getselectiondata(self, source, rowslice, colslice, \
                         selection=None, omittedfield_repr = '\b'):
        """
        Returns 2D source data array that matches the current selection
        
        Parameters:
        -----------
        source: Object
        \t Source of the data, sliceable in >= 2 dimensions
        
        rowslice: Slice
        \tRows to be retrieved
        
        colslice: Slice
        \tColumns to be retrieved
        
        selection: List
        \tRepresents selected cells in data
        
        omittedfield_repr: String
        \tRepresents empty cells and those cells that are printed but not
        \tselected if selection not None
        
        """
        
        getter = source.__getitem__
        
        try:
            data = numpy.array( \
                    getter((rowslice, colslice, self.current_table)).copy(), \
                    dtype="O")
        except AttributeError:
            data = numpy.array( \
                    getter((rowslice, colslice, self.current_table)), \
                    dtype="O")
        try:
            len(data)
        except TypeError:
            return self.digest(source[rowslice, colslice, self.current_table])
        
        for row in xrange(len(data)):
            try:
                datarange = xrange(len(data[row]))
            except TypeError:
                return data
            for col in datarange:
                try:
                    if (row + rowslice.start, col + colslice.start) \
                          not in selection:
                        #print repr(row, col)
                        data[row, col] = omittedfield_repr
                except TypeError:
                    if selection is None:
                        pass
                
                key = (row + rowslice.start, col + colslice.start, 
                      self.current_table)
                      
                ## THIS IS BAD, however print framework is redone anyways
                try:
                    is_none = data[row, col] is None
                except IndexError:
                    is_none = data[row] is None
                except IndexError:
                    is_none = data[col] is None
                                    
                if hasattr(source, 'sgrid') and source.sgrid[key] == 0  or \
                   is_none:
                    try:
                        data[row, col] = omittedfield_repr
                    except IndexError:
                        data[row] = omittedfield_repr
                    except IndexError:
                        data[col] = omittedfield_repr
        return data
    
    def selection_replace(self, editor, data):
        """ Replaces a selection in a TextCtrl with inserted data"""
        
        ##*** This should be moved into a custom TextCtrl class ***
        inspoint = int(editor.InsertionPoint)
        sel_begin, sel_end = editor.GetSelection()
        if sel_begin != sel_end and inspoint > sel_begin:
            inspoint = inspoint - \
                       min(abs(sel_end - sel_begin), abs(inspoint - sel_begin))
        oldval = editor.GetValue()[:sel_begin] + editor.GetValue()[sel_end:]
        newval = oldval[:inspoint] + data + oldval[inspoint:]
        editor.SetValue(newval)
        editor.SetInsertionPoint(inspoint + len(data))
        editor.SetSelection(inspoint, inspoint + len(data))

    def selectnewcell(self, key, event):
        """ Selects the cell with position key.
        
        Parameters:
        -----------
        key: 3-tuple
        \tPosition in grid(x,y,z)
        """
        
        if key[2] != self.current_table:
            self.cbox_z.SetSelection(key[2])
            event.GetString = lambda x = 0: self.digest(key[2])
            self.OnCombo(event)
            self.SetFocus()
        self.MakeCellVisible(*key[:2])
        self.SetGridCursor(*key[:2])
    
    def delete(self, selection=None):
        """Deletes selection"""
        
        if selection is None:
            selection = self.get_selection()
        for cell in selection:
            try:
                self.pysgrid[cell[0], cell[1], self.current_table] = u""
            except KeyError:
                #Cell is not there
                pass
        self.pysgrid.unredo.mark()
    
    def purge(self, selection=None):
        """Deletes selection including cell attributes"""
        
        if selection is None:
            selection = self.get_selection()
        for cell in selection:
            try:
                self.pysgrid.sgrid.pop((cell[0], cell[1], self.current_table))
            except KeyError:
                #Cell is not there
                pass
        self.pysgrid.unredo.mark()
        
# end of class GridSelectionMixin

class GridClipboardMixin(object):
    """Easy clipboard support extension class for wx.grid.Grid
    
    Public methods:
    ---------------
    cut
    copy
    paste
    undo
    redo
    
    """
    
    def cut(self):
        """ Cuts TextCtrlSelection if present 
            else cuts Grid cells
            Source can be sgrid or the displayed wxGrid """
        self.copy(source=self.pysgrid.sgrid)
        focus = self.parent.FindFocus()
        if isinstance(focus, wx.TextCtrl):
            self.selection_replace(focus, "")
        else:
            self.delete()
    
    def _copy_textctrl(self, control):
        """Copies TextCtrlSelection"""
        
        selection = control.GetStringSelection()
        if selection != u"":
            self.copy_selection = []
            return selection
        else:
            return None
    
    def _copy_grid(self, source):
        """Copies Grid cells"""
        
        selection = self.get_selection()
        
        rowslice, colslice = self.get_selected_rows_cols(selection)
        data = self.getselectiondata(source, rowslice, colslice, selection)
        
        self.copy_selection = [ \
            (cell[0]-rowslice.start, cell[1]-colslice.start) \
            for cell in selection]
        
        try:
            iter(data)
            assert not isinstance(data, unicode) and \
                   not isinstance(data, basestring)
                   
        except (TypeError, AssertionError):
            return self.digest(data)
        
        try:
            data[0][0]
        except TypeError: # Only one row
            data = [data]
        
        clipboard_data = [[]]
        for datarow in data:
            if isinstance(datarow, unicode) or isinstance(datarow, basestring):
                clipboard_data[-1].append(self.digest(datarow))
            else:
                for ele in datarow:
                    clipboard_data[-1].append(self.digest(ele))
            clipboard_data.append([])
        
        return "\n".join("\t".join(line) for line in clipboard_data)

    
    def copy(self, source=None):
        """Copies TextCtrlSelection if present else copies Grid cells
        
        Parameters
        ----------
        source: sgrid
        
        """
        
        if source is None:
            source = self.pysgrid.sgrid
            
        focus = self.parent.FindFocus()
        
        if isinstance(focus, wx.TextCtrl):
            clipboard_data = self._copy_textctrl(focus)
        else:
            clipboard_data = self._copy_grid(source)
            
        if clipboard_data is not None:
            self.clipboard.set_clipboard(clipboard_data)
    
    def paste(self):
        """Pastes into TextCtrl if active else pastes to grid"""
        
        focus = self.parent.FindFocus()
        if isinstance(focus, wx.TextCtrl):
            data = self.clipboard.get_clipboard()
            self.selection_replace(focus, data)
        else: # We got a grid selection
            pastepos = (self.GetGridCursorRow(), \
                        self.GetGridCursorCol(), \
                        self.current_table)
            self.clipboard.grid_paste(self.pysgrid, key=pastepos)
        
        self.ForceRefresh()

# end of class GridClipboardMixin


class GridManipulationMixin(object):
    """Manipulates rows, columns and tables. Mixin for wx.grid.Grid
    
    change_dim
    insert_selected_rows
    insert_selected_cols
    insert_selected_tables
    delete_selected_rows
    delete_selected_cols
    delete_selected_tables
    
    """
    
    def change_dim(self, dimension, number):
        """
        Appends/deletes a number of rows/cols/tables to a dimension of the grid
        
        Parameters
        ----------
        
        dimension: int
        \tThe dimension at which rows/cols/tables are appended
        number: int
        \tThe number of rows that shall be appended (if > 0) or deleted (if < 0)
        
        """
        
        assert dimension in xrange(3)
        
        if number == 0:
            # Nothing to do
            return None
        
        elif number < 0:
            number = abs(number)
            delete = True
            
            # Size of target grid dimension
            size = self.pysgrid.sgrid.shape[dimension] - number
            
        else:
            delete = False
            
            # Size of source grid dimension
            size = self.pysgrid.sgrid.shape[dimension]
        
        # Append
        if dimension == 0:
            operations = \
              [(self.pysgrid.insert, [[size, None, None], number])]
            undo_operations = \
              [(self.pysgrid.remove, [[size, None, None], number])]
            
        elif dimension == 1:
            operations = \
              [(self.pysgrid.insert, [[None, size, None], number])]
            undo_operations = \
              [(self.pysgrid.remove, [[None, size, None], number])]
            
        elif dimension == 2:
            minsize = min(size, size + number)
            maxsize = max(size, size + number)
            operations = \
              [(self.pysgrid.insert, [[None, None, size], number])] + \
              [(self.cbox_z.Append, [unicode(i)]) \
                    for i in xrange(minsize, maxsize)]
            undo_operations = \
              [(self.pysgrid.remove, [[None, None, size], number])] + \
              [(self.cbox_z.Delete, [i-1]) \
                    for i in range(minsize, maxsize + 1)[::-1]]
        
        if delete:
            operations, undo_operations = undo_operations, operations
        
        for undo_operation, operation in zip(undo_operations, operations):
            operation[0](*operation[1]) # Do the operation
            self.pysgrid.unredo.append(undo_operation, operation)
        
        self.create_rowcol()
        self.table.ResetView()
    
    def insert_selected_rows(self):
        """Inserts the number of rows of the imminent selection at cursor."""
        
        # Current insertion position and selected rows
        selectedrows = set(c[0] for c in self.get_selection())
        current_row = min(selectedrows)
        no_selected_rows = max(1, len(selectedrows))
        
        # Insert rows
        self.pysgrid.insert(insertionpoint=[current_row, None, None], \
                            notoinsert=no_selected_rows)
        self.create_rowcol()
        self.pysgrid.unredo.mark()
        self.table.ResetView()
    
    def insert_selected_cols(self):
        """Inserts the number of cols of the imminent selection at cursor."""
        
        # Current insertion position and selected columns
        selectedcols = set(c[1] for c in self.get_selection())
        current_col = min(selectedcols)
        no_selected_cols = max(1, len(selectedcols))
        
        # Insert columns
        self.pysgrid.insert(insertionpoint=[None, current_col, None], \
                            notoinsert=no_selected_cols)
        self.create_rowcol()
        self.pysgrid.unredo.mark()
        self.table.ResetView()
    
    def insert_selected_tables(self):
        """Inserts one table after the current one."""
        
        current_table = self.current_table
        no_selected_tables = 1
        operation = (self.cbox_z.Append, [unicode(self.pysgrid.shape[2])])
        undo_operation = (self.cbox_z.Delete, [self.pysgrid.shape[2]-1])
        self.pysgrid.unredo.append(undo_operation, operation)
        self.cbox_z.Append(unicode(self.pysgrid.shape[2]))
        self.pysgrid.insert(insertionpoint=[None, None, current_table], \
                            notoinsert=no_selected_tables)
        self.pysgrid.unredo.mark()

    def delete_selected_rows(self):
        """Deletes the number of rows of the imminent selection at cursor."""
        
        if self.pysgrid.shape[0] > 0:
            # Current deletion position and selected rows
            selectedrows = set(c[0] for c in self.get_selection())
            current_row = min(selectedrows)
            no_selected_rows = min(max(1, len(selectedrows)), \
                               self.pysgrid.shape[0] - 1)
            # Delete rows
            self.pysgrid.remove(removalpoint=[current_row, None, None], \
                                notoremove=no_selected_rows)
            self.create_rowcol()
            self.table.ResetView()
        self.pysgrid.unredo.mark()
    
    def delete_selected_cols(self):
        """Deletes the number of cols of the imminent selection at cursor."""
        
        if self.pysgrid.shape[1] > 0:
            # Current insertion position and selected columns
            selectedcols = set(c[1] for c in self.get_selection())
            current_col = min(selectedcols)
            no_selected_cols = min(max(1, len(selectedcols)), \
                               self.pysgrid.shape[1] - 1)
            # Delete columns
            self.pysgrid.remove(removalpoint=[None, current_col, None], \
                                              notoremove=no_selected_cols)
            self.create_rowcol()
            self.table.ResetView()
        self.pysgrid.unredo.mark()
    
    def delete_selected_tables(self):
        """Deletes the current table."""
        
        if self.pysgrid.shape[2] > 1:
            current_table = self.current_table
            no_selected_tables = 1
            operation = (self.cbox_z.Delete, [self.pysgrid.shape[2] - 1])
            undo_operation = (self.cbox_z.Append, \
                              [unicode(self.pysgrid.shape[2])])
            self.pysgrid.unredo.append(undo_operation, operation)
            self.cbox_z.Delete(self.pysgrid.shape[2] - 1)
            self.pysgrid.remove(removalpoint=[None, None, current_table], \
                                notoremove=no_selected_tables)
        self.pysgrid.unredo.mark()
    
# end of class GridManipulationMixin

class MainGrid(wx.grid.Grid, 
               GridIndexMixin, GridSelectionMixin,
               GridClipboardMixin, GridManipulationMixin):
    """Main grid for spreadsheet application
    
    This is the wxPython GUI object. The class provides the front-end event
    methods of the grid
    
    Parameters:
    -----------
    parent: wx.Window
    \tParent window that has a ToolBar called main_window_toolbar
    
    dim: 3-tuple, defaults to (1000, 100, 3)
    \tX, Y, Z Dimension of the grid
    
    cbox_z: wxComboBox, defaults to None, mandatory
    \tCombobox for selecting tables
    
    Methods:
    --------
    create_rowcol
    loadfile
    undo
    redo
    OnScroll
    OnCellEditorShown
    OnCellEditorHidden
    OnText
    EvtChar
    OnCellSelected
    OnContextMenu
    OnMouseMotion
    OnCombo
    
    """
    
    def __init__(self, *args, **kwds):
        try:
            self.set_statustext = kwds.pop('set_statustext')
        except KeyError:
            self.set_statustext = lambda x: None
        
        self.parent = args[0]
        self.current_table = 0
        
        dim = kwds.pop("dim")
        
        self.cbox_z = kwds.pop("cbox_z")
        if self.cbox_z is None:
            raise ValueError, "A Combobox for selecting tables is required"
        
        self.entry_line = EntryLine(self.parent, -1, "", grid=self)
        self.pysgrid = PyspreadGrid(dim)
        self.contextmenu = ContextMenu(parent=self.parent)
        self.clipboard = Clipboard()
        
        self.cbox_z.AppendItems([unicode(dim) \
                for dim in xrange(self.pysgrid.shape[2])])
        
        self.copy_selection = [] # Cells from last copy operation 
        self.current_table = 0
        self.key = (0, 0, 0)
        self.prev_rowcol = [None, None] # Last cell for tooltip
        
        self.digest = Digest(acceptable_types=[types.UnicodeType])
        
        # Zoom currently affects font size and border line width
        
        self.zoom = 1.0
        
        super(MainGrid, self).__init__(*args, **kwds)
        
        self.text_renderer = TextRenderer(self)
        self.SetDefaultRenderer(self.text_renderer)
        
        self.table = MainGridTable(self)
        self.SetTable(self.table, True)
        
        self.EnableGridLines(False)
        
        self.col_label_size = self.GetColLabelSize()
        self.row_label_size = self.GetRowLabelSize()
        
        self.std_row_size = self.GetRowSize(0)
        self.std_col_size = self.GetColSize(0)
        
        self.SetDefaultCellFont(DEFAULT_FONT)
        
        # Event bindings
        
        self.Bind(wx.EVT_TEXT, self.OnText)
        
        self.Bind(wx.grid.EVT_GRID_CELL_RIGHT_CLICK, self.OnContextMenu)
        self.Bind(wx.grid.EVT_GRID_CMD_EDITOR_HIDDEN, self.OnCellEditorHidden)
        self.Bind(wx.grid.EVT_GRID_CMD_EDITOR_SHOWN, self.OnCellEditorShown)
        self.Bind(wx.grid.EVT_GRID_CMD_SELECT_CELL, self.OnCellSelected)
        self.GetGridWindow().Bind(wx.EVT_MOTION, self.OnMouseMotion)
        
        self.Bind(wx.grid.EVT_GRID_ROW_SIZE, self.OnRowSize)
        self.Bind(wx.grid.EVT_GRID_COL_SIZE, self.OnColSize)
        
        self.Bind(wx.EVT_SCROLLWIN, self.OnScroll)
        self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
        
        # When selection procwess ends, 
        # the shiftkey or the left mouse button are released
        
        self.GetGridWindow().Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
        self.GetGridWindow().Bind(wx.EVT_KEY_UP, self.OnKeyUp)

        
    def create_rowcol(self):
        """Creates a new grid"""
        
        operation = (self.create_rowcol, [])
        undo_operation = (self.create_rowcol, [])
        self.pysgrid.unredo.append(undo_operation, operation)
        
        try:
            self.CreateGrid(*self.pysgrid.shape[:2])
        except:
            pass

    def _update_row_sizes(self, interface):
        """Sets the row sizes according to interface data"""
        
        for key in interface.row_styles:
            rowno, tabno = key
            
            size = float(interface.row_styles[key][row_height_tag][:-2])
            dpi_adjusted_size = size * dpi[0]
            
            if tabno == 0:
                self.SetRowSize(rowno, dpi_adjusted_size * self.zoom)
            
            key = (0, colno, tabno)
            tag = odftags["rowheight"]
            
            self.pysgrid.create_sgrid_attribute(key, tag)
            setattr(self.pysgrid.sgrid[key], tag, dpi_adjusted_size)
        
    def _update_col_sizes(self, interface):
        """Sets the row sizes according to interface data"""
        
        for k in interface.column_styles:
            colno, tabno = k
            
            size = float(interface.column_styles[k][column_width_tag][:-2])
            dpi_adjusted_size = size * dpi[0]
            
            if tabno == 0:
                self.SetColSize(colno, dpi_adjusted_size * self.zoom)
                
            key = (0, colno, tabno)
            tag = odftags["colwidth"]
            
            self.pysgrid.create_sgrid_attribute(key, tag)
            setattr(self.pysgrid.sgrid[key], tag, dpi_adjusted_size)
        
    def loadfile(self, filename, interface = None):
        """Load file named filename into spreadsheet"""
        
        if interface is None:
            interface = PysInterfaces()
            
        try:
            interface.open(filename)
        except IOError:
            return 0
        
        # Get cell values
        sgrid = interface.get_values()
        self.pysgrid.sgrid = sgrid
        dim = sgrid.shape
        
        self._update_row_sizes(interface)
        self._update_col_sizes(interface)
        
        interface.close()
        

        # Now update the EntryLine to the current string
        pos = tuple(list(self.get_currentcell())+[self.current_table])
        currstr = self.table.GetSource(*pos)
        try:
            self.entry_line.SetValue(currstr)
        except TypeError:
            self.entry_line.SetValue("")
        
        # Set up Combo box for table choice
        
        self.cbox_z.Clear()
        self.cbox_z.AppendItems([str(i) for i in xrange(dim[2])])
        
        self.create_rowcol()
        self.pysgrid.unredo.reset()
        self.table.ResetView()
        
        # Set up grid
        self.current_table = 0
        self.zoom = 1.0
        
        self.ClearGrid()
        
        self.zoom_rows()
        self.zoom_cols()
        self.zoom_labels()
        
        self.Update()
        
        # Evaluate everything in order to catch globals
        for key in self.pysgrid.sgrid:
            self.pysgrid[key]
        self.pysgrid._resultcache = {}
        self.Update()

    def savefile(self, filename, interface = None):
        """Same file named filename into spreadsheet"""
        
        if interface is None:
            interface = PysInterfaces()
        
        interface.save(self.pysgrid.sgrid, filename)


    def undo(self):
        """Undo the recent operations"""
        
        self.pysgrid.unredo.undo()
        self.ClearGrid()
        self.Update()
        self.create_rowcol()
    
    def redo(self):
        """Redo the recent operations"""
        
        self.pysgrid.unredo.redo()
        self.ClearGrid()
        self.Update()
        self.create_rowcol()
    
    def OnText(self, event):
        """Text entry event method in grid"""
        
        code = event.GetString()
        
        if code != "":
            try: 
                self.entry_line.SetValue(code)
            except TypeError: 
                self.entry_line.SetValue("")
        
        event.Skip()
    
    def OnScroll(self, event):
        """Scroll event method updates the grid"""
        
        self.Update()
        event.Skip()
    
    def OnCellEditorShown(self, event):
        """CellEditor event method sets editor content to Python code"""
        
        row, col = event.Row, event.Col
        self.key = (row, col, self.current_table)
        
        event.Skip()
    
    def OnCellEditorHidden(self, event):
        """When a cell editor is hidden, the grid is refreshed"""
        
        self.ForceRefresh()
        self.Update()
        event.Skip()
    
    def OnCellSelected(self, event):
        """Cell selection event method 
        
        1) Sets cell editor
        2) Puts code into entry line
        3) Updates Attribute toolbar
        
        """
        
        row, col, tab = event.Row, event.Col, self.current_table
        self.key = row, col, tab
        
        self.SetCellEditor(row, col, TextCellEditor(parent=self)) 
        
        try: 
            currstr = self.table.GetSource(*self.key)
        except IndexError: 
            currstr = ""
        
        try: 
            self.entry_line.SetValue(currstr)
        except TypeError: 
            self.entry_line.SetValue("")
        
        self.ForceRefresh()
        
        # Attributes Toolbar update
        pysgrid = self.pysgrid
        
        nativefontinfo = wx.NativeFontInfo()
        nativefontinfo.FromString(pysgrid.get_sgrid_attr(self.key, "textfont"))
        
        textfont = wx.Font(10, wx.NORMAL, wx.NORMAL, wx.NORMAL, False, 'Arial')
        textfont.SetNativeFontInfo(nativefontinfo)
        
        textattributes = pysgrid.get_sgrid_attr(self.key, "textattributes")
        
        bgbrush_data = pysgrid.get_sgrid_attr(self.key, "bgbrush")
        
        borderpen_data = pysgrid.get_sgrid_attr(self.key, "borderpen")
        
        self.parent.attributes_toolbar.update(\
            textfont=textfont, textattributes=textattributes,
            bgbrush_data=bgbrush_data, borderpen_data=borderpen_data)
        
        event.Skip()

    def OnContextMenu(self, event):
        """Context Menu event method calls context menu"""
        
        self.PopupMenu(self.contextmenu)
        #contextmenu.Destroy()
        event.Skip()
    
    def OnMouseMotion(self, event):
        """Mouse motionselection event method puts Python code into statusbar"""
        
        pos_x, pos_y = self.CalcUnscrolledPosition(event.GetPosition())
        row = self.YToRow(pos_y)
        col = self.XToCol(pos_x)
        
        if (row, col) != self.prev_rowcol and row >= 0 and col >= 0:
            self.prev_rowcol[:] = [row, col]
            
            hinttext = self.table.GetSource(row, col)
            
            if hinttext is None:
                hinttext = ''
            try:
                self.set_statustext(hinttext)
            except TypeError:
                pass
        event.Skip()
        
    def OnCombo(self, event):
        """Combo box event method that updates the current table"""
        
        try:
            newtable = int(event.GetString())
        except ValueError:
            newtable = 0
        
        if newtable in xrange(self.pysgrid.shape[2]):
            # Update the whole grid including the empty cells
            self.current_table = newtable
            
            self.ClearGrid()
            self.Update()
            
            self.zoom_rows()
            self.zoom_cols()
            self.zoom_labels()
            
            # Now update the EntryLine to the current string
            pos = tuple(list(self.get_currentcell())+[self.current_table])
            
            currstr = self.table.GetSource(*pos)
            
            try:
                self.entry_line.SetValue(currstr)
            except TypeError:
                self.entry_line.SetValue("")
    
    def OnRowSize(self, event):
        """Stores the size of a currently changed row in 1st col"""
        
        rowno = event.GetRowOrCol()
        tabno = self.current_table
        
        tag = odftags["rowheight"]
        
        rowsize = self.GetRowSize(rowno) / self.zoom
        
        try:
            self.pysgrid.sgrid.rows[rowno][tag] = rowsize
        except KeyError:
            self.pysgrid.sgrid.rows[rowno] = {}
            self.pysgrid.sgrid.rows[rowno][tag] = rowsize
        except AttributeError:
            self.pysgrid.sgrid.rows = {}
            self.pysgrid.sgrid.rows[rowno] = {}
            self.pysgrid.sgrid.rows[rowno][tag] = rowsize
        
        event.Skip()
        
    def OnColSize(self, event):
        """Stores the size of a currently changed column in 1st row attrib"""
        
        colno = event.GetRowOrCol()
        tabno = self.current_table
        
        tag = odftags["colwidth"]
        
        colsize = self.GetColSize(colno) / self.zoom
        
        try:
            self.pysgrid.sgrid.cols[colno][tag] = colsize
        except KeyError:
            self.pysgrid.sgrid.cols[colno] = {}
            self.pysgrid.sgrid.cols[colno][tag] = colsize
        except AttributeError:
            self.pysgrid.sgrid.cols = {}
            self.pysgrid.sgrid.cols[colno] = {}
            self.pysgrid.sgrid.cols[colno][tag] = colsize

        event.Skip()
    
    def OnLeftUp(self, event):
        """Left mouse button up event handler"""
        
        # If there is a selection, activate redraw of grid
        self.text_renderer.redraw_imminent = True
        self.ForceRefresh()
        
        event.Skip()
    
    def OnKeyUp(self, event):
        """Key released event handler"""
        
        # If shift is released, a new selection is present 
        # => redraw
        
        if event.GetKeyCode() == 306:
            self.text_renderer.redraw_imminent = True
            self.ForceRefresh()
        event.Skip()
    
    def zoom_rows(self):
        """Zooms grid rows"""
        
        tabno = self.current_table
        tag = odftags["rowheight"]
        
        sgrid = self.pysgrid.sgrid
        
        pos = self.CalcUnscrolledPosition(self.GetScrollX(0), 
                                          self.GetScrollY(0))
        
        self.SetDefaultRowSize(self.std_row_size * self.zoom, 
                               resizeExistingRows=True)
        self.SetRowLabelSize(self.row_label_size * self.zoom)
        
        if not hasattr(sgrid, "rows"):
            return
        
        for rowno in sgrid.rows:
            if tag in sgrid.rows[rowno]:
                zoomed_row_size = sgrid.rows[rowno][tag] * self.zoom
                self.SetRowSize(rowno, zoomed_row_size)
        
        self.MakeCellVisible(*pos)
        
    def zoom_cols(self):
        """Zooms grid columns"""
        
        tabno = self.current_table
        tag = odftags["colwidth"]
        
        sgrid = self.pysgrid.sgrid
        
        pos = self.CalcUnscrolledPosition(self.GetScrollX(0), 
                                          self.GetScrollY(0))
        
        self.SetDefaultColSize(self.std_col_size * self.zoom, 
                               resizeExistingCols=True)
        self.SetColLabelSize(self.col_label_size * self.zoom)
        
        if not hasattr(sgrid, "cols"):
            return
        
        for colno in sgrid.cols:
            if tag in sgrid.cols[colno]:
                zoomed_col_size = sgrid.cols[colno][tag] * self.zoom
                self.SetColSize(colno, zoomed_col_size)
        
        self.MakeCellVisible(*pos)

    def zoom_labels(self):
        """Zooms grid labels"""
        
        labelfont = self.GetLabelFont()
        labelfont.SetPointSize(max(1, 
                            int(round(faces['size'] * self.zoom))))
        self.SetLabelFont(labelfont)
        
    def OnMouseWheel(self, event):
        """Event handler for mouse wheel actions
        
        Invokes zoom when mouse when Ctrl is also pressed
        
        """
        
        if event.ControlDown():
            zoomstep = 0.05 * event.LinesPerAction
            
            if event.WheelRotation > 0:
                self.zoom += zoomstep
            else:
                if self.zoom > 0.6:
                    self.zoom -= zoomstep
            

            self.zoom_rows()
            self.zoom_cols()
            self.zoom_labels()
            
            self.ForceRefresh()
        else:
            event.Skip()

# end of class MainGrid


class EntryLine(wx.TextCtrl):
    """"The line for entering cell code"""
    
    def __init__(self, *args, **kwargs):
        
        self.parent = args[0]
        
        try:
            self.grid = kwargs.pop("grid")
        except KeyError:
            self.grid = self.parent.MainGrid
        
        super(EntryLine, self).__init__(*args, **kwargs)
        
        self.Bind(wx.EVT_TEXT, self.OnText)
        self.Bind(wx.EVT_CHAR, self.EvtChar)

    def OnText(self, event):
        """Text event method evals the cell and updates the grid"""
        
        key = self.grid.key
        
        self.grid.pysgrid[key] = event.GetString()
        
        event.Skip()
        
    def EvtChar(self, event):
        """Key event method
        
         * Forces grid update on <Enter> key
         * Handles insertion of cell access code
        
        """
        
        if event.GetKeyCode() == 13:
            self.grid.ForceRefresh()
            self.grid.SetFocus()
            
        elif event.GetKeyCode() == wx.WXK_INSERT and \
             event.ControlDown():
            self.WriteText(self.grid.get_selection_code())
        
        event.Skip()
