#!/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/>.
# --------------------------------------------------------------------

"""
_widgets
========

Provides:
---------
  1. IndexGrid: Grid that supports indexing and __len__
  2. MainGrid: Main grid
  2. CollapsiblePane: Collapsible pane with basic toggle mechanism
  3. MacroEditPanel: Collapsible label, parameter entry area and editor
  4. SortedListCtrl: ListCtrl with items sorted in first column

"""

import bz2
import keyword
import cPickle as pickle
from itertools import izip
import types

import wx
import wx.grid
import wx.stc  as  stc
import wx.lib.mixins.listctrl  as  listmix
import numpy

from pyspread._datastructures import PyspreadGrid
from pyspread._choicebars import ContextMenu
from pyspread._interfaces import Clipboard, Digest
from pyspread.config import faces, text_styles, fold_symbol_style


class IndexGrid(wx.grid.Grid):
    """Extension class for wx.grid.Grid
    
    The class extends 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 __init__(self, *args, **kwds):
        super(IndexGrid, self).__init__(*args, **kwds)

    def __getitem__(self, key):
        if len(key) != 2:
            raise KeyError("key must be of length 2")
        
        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:
            raise KeyError("key must be of length 2")
        
        row, col = key[:2]
        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 IndexGrid


class MainGridBase(IndexGrid):
    """Base for the main grid of the spreadsheet application
    
    This class provides the methods and attributes that provide the basic
    functionality of a spreadsheet grid. The methods are called by the event
    methods of MainGrid. The actual data is stored in the self.pysgrid object
    that is set up when creating an instance of this class.
    The combo box that allows selectinvg a table is set up by this class.
    
    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
    
    Public methods:
    ---------------
    create_rowcol
    get_selected_rows_cols
    get_selection
    get_currentcell
    get_visiblecell_slice
    getselectiondata
    selection_replace
    update_grid
    selectnewcell
    loadfile
    cut
    copy
    paste
    undo
    redo
    delete
    change_dim
    insert_selected_rows
    insert_selected_cols
    insert_selected_tables
    delete_selected_rows
    delete_selected_cols
    delete_selected_tables
    
    """
    
    def __init__(self, *args, **kwds):
        
        # Sub widgets
        self.parent = args[0]
        try:
            self.mainwindow = kwds.pop('mainwindow')
        except KeyError:
            self.mainwindow = None
        
        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 = wx.TextCtrl(self.parent, -1, "")
        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.sgrid.shape[2])])
        
        self.copy_selection = [] # Cells from last copy operation 
        self.current_table = 0
        self.key = (0, 0, 0)
        self.selection_imminent = False
        self.prev_rowcol = [None, None] # Last cell for tooltip
        
        self.digest = Digest(acceptable_types=[types.UnicodeType])
        
        super(MainGridBase, self).__init__(*args, **kwds)
    
    def _fill_cells(self, gridslice=None):
        """Fills grid with results from grid data object"""
        
        for i in self.pysgrid.get_function_cell_indices(gridslice):
            if i[2] == self.current_table:
                res = self.pysgrid.get_item_from_maingrid(i)
                if res is None:
                    res = u''
                unicode_string = self.digest(res)
                self.SetCellValue(i[0], i[1], unicode_string)
        
        current_cell_index = (self.GridCursorRow, \
                              self.GridCursorCol, \
                              self.current_table)
        
        if current_cell_index == (-1, -1, self.current_table):
            current_cell_index = (0, 0, self.current_table)
        
        res = self.pysgrid.get_item_from_maingrid(current_cell_index)
        if res is None:
            res = u""
        unicode_string = self.digest(res)
        
        self.SetCellValue(current_cell_index[0], \
                          current_cell_index[1], \
                          unicode_string)

    def _set_cell_to_expression(self, key):
        """Update MainGrid cell from result to python expression"""
        try:
            self[key[:2]] = self.pysgrid.sgrid[key]
        except TypeError: 
            self[key[:2]] = ''
        
    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.sgrid.shape[:2])
        except:
            pass
        for i in xrange(self.pysgrid.sgrid.shape[0]):
            self.SetRowLabelValue(i, unicode(i))
        for i in xrange(self.pysgrid.sgrid.shape[1]):
            self.SetColLabelValue(i, unicode(i))

    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_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)
        #print topleft
        row, col = topleft_x + 1, topleft_y + 1 # Ensures visibility
        #print self.IsVisible(*topleft)
        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
        
        """
        
        if hasattr(source, 'get_item_from_maingrid'):
            getter = source.get_item_from_maingrid
        else:
            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:
                        data[row, col] = omittedfield_repr
                except TypeError:
                    if selection is None:
                        pass
                if hasattr(source, 'fgrid') and \
                   source.fgrid[row + rowslice.start, col + colslice.start, \
                                self.current_table] == 0 \
                   or data[row, col] is None:
                    #print data[row, col]
                    data[row, 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()
        #print sel_begin, sel_end
        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 update_grid(self, funceval=False, funceval_gridslice=None, \
                          gridslice="None"):
        """Updates the main grid.
        
        Updates the function grid slice funceval_gridslice if funceval==True.
        
        """
        
        if funceval:
            self.pysgrid.eval_function_grid(gridslice=funceval_gridslice)
        if gridslice == "None":
            gridslice = self.get_visiblecell_slice()
        self._fill_cells(gridslice=gridslice)

    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 loadfile(self, filename):
        """Load file named fileneme into spreadsheet"""
        
        try:
            infile = bz2.BZ2File(filename, "rb")
        except IOError:
            return 0
        sgrid = pickle.load(infile)
        infile.close()
        
        self.DeleteRows(0, self.pysgrid.fgrid.shape[0])
        self.DeleteCols(0, self.pysgrid.fgrid.shape[1])
        
        dim = sgrid.shape
        self.pysgrid = PyspreadGrid(dim)
        self.pysgrid.sgrid = sgrid
        
        self.AppendRows(dim[0])
        self.AppendCols(dim[1])
        self.cbox_Z.Clear()
        self.cbox_Z.AppendItems([str(i) for i in xrange(dim[2])])
        
        self.update_grid(funceval=True, \
                         funceval_gridslice=None, \
                         gridslice=None)
        
        self.create_rowcol()
        
        self.pysgrid.unredo.reset()

    def cut(self):
        """ Cuts TextCtrlSelection if present 
            else cuts Grid cells
            Source can be sgrid, fgrid 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:
            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 or fgrid
        
        """
        
        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.update_grid(funceval=True)
    
    def undo(self):
        """Undo the recent operations"""
        
        self.pysgrid.unredo.undo()
        self.ClearGrid()
        self.update_grid(funceval=True)
        self.create_rowcol()
    
    def redo(self):
        """Redo the recent operations"""
        
        self.pysgrid.unredo.redo()
        self.ClearGrid()
        self.update_grid(funceval=True)
        self.create_rowcol()
    
    def delete(self, selection=None):
        """Deletes selection"""
        
        if selection is None:
            selection = self.get_selection()
        for cell in selection:
            self.pysgrid.fgrid[cell[0], cell[1], self.current_table] = 0
            self.pysgrid[cell[0], cell[1], self.current_table] = ''
            self.SetCellValue(cell[0], cell[1], '')
        self.pysgrid.unredo.mark()
    
    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]
        
        print dimension, size, number
        # Append to MainGrid and pysgrid
        if dimension == 0:
            
            operations = \
              [(self.pysgrid.insert, [[size, None, None], number]), \
               (self.AppendRows, [number])]
            undo_operations = \
              [(self.pysgrid.remove, [[size, None, None], number]), \
               (self.DeleteRows, [size, number, False])]
            
        elif dimension == 1:
            ##self.pysgrid.insert([None, size, None], number)
            
            operations = \
              [(self.pysgrid.insert, [[None, size, None], number]), \
               (self.AppendCols, [number])]
            undo_operations = \
              [(self.pysgrid.remove, [[None, size, None], number]), \
               (self.DeleteCols, [size, number])]
            
        elif dimension == 2:
            ##self.pysgrid.insert([None, None, size], number)
            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]]
            print "R", range(minsize, maxsize + 1)
        #print delete, operations, undo_operations
        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()
        print self.pysgrid.shape
    
    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 into MainGrid
        operation = (self.InsertRows, [current_row, no_selected_rows, False])
        undo_operation = (self.DeleteRows, \
                          [current_row, no_selected_rows, False])
        self.pysgrid.unredo.mark()
        self.pysgrid.unredo.append(undo_operation, operation)
        
        # Insert rows into pysgrid
        self.InsertRows(pos=current_row, \
                        numRows=no_selected_rows, \
                        updateLabels=False)
        
        self.pysgrid.insert(insertionpoint=[current_row, None, None], \
                            notoinsert=no_selected_rows)
        self.create_rowcol()
        self.pysgrid.unredo.mark()
    
    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 into MainGrid and pysgrid
        operation = (self.InsertCols, [current_col, no_selected_cols, False])
        undo_operation = (self.DeleteCols, \
                          [current_col, no_selected_cols, False])
        self.pysgrid.unredo.mark()
        self.pysgrid.unredo.append(undo_operation, operation)
        self.InsertCols(pos=current_col, \
                        numCols=no_selected_cols, \
                        updateLabels=False)
        
        self.pysgrid.insert(insertionpoint=[None, current_col, None], \
                            notoinsert=no_selected_cols)
        self.create_rowcol()
        self.pysgrid.unredo.mark()
    
    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.sgrid.shape[2])])
        undo_operation = (self.cbox_Z.Delete, [self.pysgrid.sgrid.shape[2]-1])
        self.pysgrid.unredo.append(undo_operation, operation)
        self.cbox_Z.Append(unicode(self.pysgrid.sgrid.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.sgrid.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.sgrid.shape[0] - 1)
            # Delete rows from MainGrid and pysgrid
            operation = (self.DeleteRows, \
                         [current_row, no_selected_rows, False])
            undo_operation = (self.InsertRows, \
                              [current_row, no_selected_rows, False])
            self.pysgrid.unredo.append(undo_operation, operation)
            self.DeleteRows(current_row, no_selected_rows, updateLabels=False)
            self.pysgrid.remove(removalpoint=[current_row, None, None], \
                                notoremove=no_selected_rows)
            self.create_rowcol()
        self.pysgrid.unredo.mark()
    
    def delete_selected_cols(self):
        """Deletes the number of cols of the imminent selection at cursor."""
        
        if self.pysgrid.sgrid.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.sgrid.shape[1] - 1)
            # Delete columns from MainGrid and pysgrid
            operation = (self.DeleteCols, \
                         [current_col, no_selected_cols, False])
            undo_operation = (self.InsertCols, \
                              [current_col, no_selected_cols, False])
            self.pysgrid.unredo.append(undo_operation, operation)
            self.DeleteCols(current_col, no_selected_cols, updateLabels=False)
            self.pysgrid.remove(removalpoint=[None, current_col, None], \
                                              notoremove=no_selected_cols)
            self.create_rowcol()
        self.pysgrid.unredo.mark()
    
    def delete_selected_tables(self):
        """Deletes the current table."""
        
        if self.pysgrid.sgrid.shape[2] > 1:
            current_table = self.current_table
            no_selected_tables = 1
            operation = (self.cbox_Z.Delete, [self.pysgrid.sgrid.shape[2] - 1])
            undo_operation = (self.cbox_Z.Append, \
                              [unicode(self.pysgrid.sgrid.shape[2])])
            self.pysgrid.unredo.append(undo_operation, operation)
            self.cbox_Z.Delete(self.pysgrid.sgrid.shape[2] - 1)
            self.pysgrid.remove(removalpoint=[None, None, current_table], \
                                notoremove=no_selected_tables)
        self.pysgrid.unredo.mark()
    
    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.sgrid.shape[2]):
            # Update the whole grid including the empty cells
            self.current_table = newtable
            self.ClearGrid()
            self.update_grid()
            # Now update the EntryLine to the current string
            pos = tuple(list(self.get_currentcell())+[self.current_table])
            currstr = self.pysgrid.sgrid[pos]
            if self.selection_imminent:
                event.Skip()
                return 0
            self.selection_imminent = True
            try:
                self.entry_line.SetValue(currstr)
            except TypeError:
                self.entry_line.SetValue("")
            self.selection_imminent = False
        event.Skip()

# end of class MainGridBase


class MainGrid(MainGridBase):
    """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:
    --------
    OnScroll
    OnCellEditorShown
    OnCellChanged
    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
        
        super(MainGrid, self).__init__(*args, **kwds)
        
        self.Bind(wx.grid.EVT_GRID_CELL_RIGHT_CLICK, self.OnContextMenu)
        self.Bind(wx.grid.EVT_GRID_CMD_CELL_CHANGE, self.OnCellChanged)
        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.EVT_SCROLLWIN, self.OnScroll)
        self.entry_line.Bind(wx.EVT_TEXT, self.OnText)
        self.entry_line.Bind(wx.EVT_CHAR, self.EvtChar)
        
    def OnScroll(self, event):
        """Scroll event method updates the grid"""
        
        self.update_grid()
        self.Update()
        event.Skip()

    def OnCellEditorShown(self, event):
        """CellEditor event method sets editor content to Python code"""
        
        self.key = (event.Row, event.Col, self.current_table)
        self._set_cell_to_expression(self.key)
        event.Skip()

    def OnCellChanged(self, event):
        """CellChanged event method calculates and updates the visible grid"""
        
        self.key = (event.Row, event.Col, self.current_table)
        currstr = self[self.key[:2]]
        try:
            self.CreateGrid(*self.pysgrid.sgrid.shape[:2])
        except AssertionError:
            pass
        self.pysgrid[self.key] = currstr
        funcevalslice = tuple([slice(self.key[i], self.key[i]+1) \
                                              for i in xrange(3)])
        self.update_grid(funceval=True, funceval_gridslice=funcevalslice)
        self.pysgrid.unredo.mark()
        event.Skip()

    def OnText(self, event):
        """Text event method evals the cell and updates the grid"""
        
        if self.selection_imminent:
            self.selection_imminent = False
        else:
            currstr = event.GetString()
            self[self.key[:2]] = event.GetString()
            if currstr == '':
                self.pysgrid.fgrid[self.key] = 0
            self.pysgrid[self.key] = currstr
        try:
            currcellslice = (slice(self.key[0], self.key[0]+1), \
                             slice(self.key[1], self.key[1]+1))
            self.update_grid(funceval=True, \
                             funceval_gridslice=currcellslice, \
                             gridslice=currcellslice)
        except IndexError:
            pass
        event.Skip()
        
    def EvtChar(self, event):
        """Key event method forces grid update on <Enter> key"""
        
        if event.GetKeyCode() == 13:
            self.update_grid()
            self.SetFocus()
        event.Skip()

    def OnCellSelected(self, event):
        """Cell selection event method puts Python code into entry line"""
        
        self.selection_imminent = True
        self.key = (event.Row, event.Col, self.current_table)
        try: 
            currstr = self.pysgrid.sgrid[self.key]
        except IndexError: 
            currstr = ""
        try: 
            self.entry_line.SetValue(currstr)
        except TypeError: 
            self.entry_line.SetValue("")
        self.update_grid()
        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.pysgrid.sgrid[row, col, self.current_table]
            if hinttext is None:
                hinttext = ''
            try:
                self.set_statustext(hinttext)
            except TypeError:
                pass
        event.Skip()

# end of class MainGrid

class CollapsiblePane(wx.CollapsiblePane):
    """Collapsible pane with basic toggle mechanism
    
    Parameters:
    -----------
    panename: string
    \tLabel for the collapsible pane
    
    """
    
    def __init__(self, *args, **kwds):
        self.label_show = "Show "
        self.label_hide = "Hide "
        panename = kwds.pop('panename')
        self.__dict__['label'] = panename
        wx.CollapsiblePane.__init__(self, *args, **kwds)
        self.SetLabel(self.label_show + panename)
        self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.restore_pane, self)
        
    def OnToggle(self, event):
        """"Toggle event handler"""
        self.Collapse(self.IsExpanded())
        self.restore_pane()
        
    def restore_pane(self, event=None):
        """Restores the layout of the content and changes teh labels"""
        self.GetParent().Layout()
        # and also change the labels
        if self.IsExpanded():
            self.SetLabel(self.label_hide + self.__dict__['label'])
        else:
            self.SetLabel(self.label_show + self.__dict__['label'])

# end of class CollapsiblePane

class MacroEditPanel(wx.Panel):
    """Collapsible label, parameter entry area and editor"""
    def __init__(self, parent,  *args, **kwds):
        wx.Panel.__init__(self, parent, *args, **kwds)
        self.maxvar = 100 # The menu will fail for functions with more variables
        sizer = wx.FlexGridSizer(cols=1, rows=3, hgap=5, vgap=5)
        #print sizer.GetColWidths()
        sizer.AddGrowableCol(0)
        sizer.AddGrowableRow(2)
        self.SetSizer(sizer)
        # Add the three panes on the right hand side
        self.pane_docstring = CollapsiblePane(self, label='', \
                                panename = 'docstring', \
                                style=wx.CP_DEFAULT_STYLE|wx.CP_NO_TLW_RESIZE)
        self.make_docstring_content(self.pane_docstring.GetPane())
        sizer.Add(self.pane_docstring, 0, wx.RIGHT|wx.LEFT|wx.EXPAND, 25)
        self.pane_docstring.OnToggle(None)
        
        self.pane_macroform = CollapsiblePane(self, label='', \
                                panename = 'macro form', \
                                style=wx.CP_DEFAULT_STYLE|wx.CP_NO_TLW_RESIZE)
        self.make_macroform_content(self.pane_macroform.GetPane())
        sizer.Add(self.pane_macroform, 0, wx.RIGHT|wx.LEFT|wx.EXPAND, 25)
        self.pane_macroform.OnToggle(None)
        
        self.pane_code = CollapsiblePane(self, label='', \
                                panename = 'code', \
                                style=wx.CP_DEFAULT_STYLE|wx.CP_NO_TLW_RESIZE)
        self.make_code_content(self.pane_code.GetPane())
        sizer.Add(self.pane_code, 0, wx.RIGHT|wx.LEFT|wx.EXPAND, 25)
        
    def make_docstring_content(self, pane):
        """Puts docstring text control into panel"""
        
        self.docstringTextCtrl = wx.TextCtrl(pane, -1, '', \
                        style = wx.NO_BORDER|wx.TE_MULTILINE|wx.TE_READONLY)
        #self.docstringTextCtrl.SetEditable(False)
        sizer = wx.FlexGridSizer(cols=1, rows=1, hgap=5, vgap=5)
        sizer.AddGrowableCol(0)
        sizer.AddGrowableRow(0)
        sizer.Add(self.docstringTextCtrl, 0, wx.EXPAND|wx.FIXED_MINSIZE, 0) 
        pane.SetSizer(sizer)
    
    def update_macroform(self, pane, macro):
        """Updates the macro form, for parameter entry of current macro"""
        
        no_func_parameters = macro.func_code.co_argcount
        func_parameters = macro.func_code.co_varnames[:no_func_parameters]
        
        for i, variable in enumerate(func_parameters): 
            self.varlabels[i].SetLabel(variable)
            self.varlabels[i].Show(True)
            self.varentries[i].Show(True)
        
        for i in xrange(i+1, self.maxvar):
            self.varlabels[i].Hide()
            self.varentries[i].Hide()
        
        pane.OnToggle(None)
        pane.OnToggle(None)
        
    def make_macroform_content(self, pane):
        """Takes the current macro's parameters and creates the entry panel"""
        
        self.varlabels = []
        self.varentries = []
        self.macroformsizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=5)
        self.macroformsizer.AddGrowableRow(0)
        #self.macroformsizer.AddGrowableCol(0)
        self.macroformsizer.AddGrowableCol(1)
        for i in xrange(self.maxvar):
            self.varlabels.append(wx.StaticText(pane, -1, "", \
                                  style=wx.ALIGN_RIGHT))
            self.varentries.append(wx.TextCtrl(pane, -1, ""))
            self.varlabels[-1].Hide()
            self.varentries[-1].Hide()
            self.macroformsizer.Add(self.varlabels[i], 0, wx.EXPAND, 0)
            self.macroformsizer.Add(self.varentries[i], 1, wx.EXPAND, 0)
        self.ok_button = wx.Button(pane, wx.ID_OK)
        self.cancel_button = wx.Button(pane, wx.ID_CANCEL)
        self.macroformsizer.Add(self.cancel_button, 0, wx.EXPAND, 0)
        self.macroformsizer.Add(self.ok_button, 1, wx.EXPAND, 0)
        pane.SetSizer(self.macroformsizer)
        self.GetParent().Layout() 
    
    def make_code_content(self, pane):
        """Adds the code entry text control to the panel"""
        
        self.CodeTextCtrl = PythonSTC(pane, wx.NewId(), \
          pos=wx.DefaultPosition, \
          size=wx.DefaultSize, 
          style=wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB|wx.TE_MULTILINE|wx.EXPAND)
        
        self.CodeTextCtrl.SetToolTipString("Enter one python function here." + \
                        "\nThe first line has to begin with def")
        border = wx.FlexGridSizer(cols=0, hgap=5, vgap=5)
        border.AddGrowableCol(0)
        border.AddGrowableRow(0)
        border.Add(self.CodeTextCtrl, 0, wx.EXPAND|wx.FIXED_MINSIZE, 0)
        pane.SetSizer(border)

# end of class MacroEditPanel


class SortedListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
    """ListCtrl with items sorted in first column"""
    
    def __init__(self, parent, wxid, pos=wx.DefaultPosition,
                 size=wx.DefaultSize, style=0):
        wx.ListCtrl.__init__(self, parent, wxid, pos, size, style)
        listmix.ListCtrlAutoWidthMixin.__init__(self)

# end of class SortedListCtrl

class PythonSTC(stc.StyledTextCtrl):
    """Editor that highlights Python source code.
    
    Stolen from the wxPython demo.py
    """
    fold_symbols = 2
    
    def __init__(self, *args, **kwargs):
        stc.StyledTextCtrl.__init__(self, *args, **kwargs)

        self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
        self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)

        self.SetLexer(stc.STC_LEX_PYTHON)
        self.SetKeyWords(0, " ".join(keyword.kwlist))

        self.SetProperty("fold", "1")
        self.SetProperty("tab.timmy.whinge.level", "1")
        self.SetMargins(0, 0)

        self.SetViewWhiteSpace(False)
        self.SetUseAntiAliasing(True)
        
        self.SetEdgeMode(stc.STC_EDGE_BACKGROUND)
        self.SetEdgeColumn(78)

        # Setup a margin to hold fold markers
        self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
        self.SetMarginMask(2, stc.STC_MASK_FOLDERS)
        self.SetMarginSensitive(2, True)
        self.SetMarginWidth(2, 12)
        
        # Import symbol style from config file
        for marker in fold_symbol_style:
            self.MarkerDefine(*marker)
        
        self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
        self.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
        
        # Global default styles for all languages
        self.StyleSetSpec(stc.STC_STYLE_DEFAULT, \
                          "face:%(helv)s,size:%(size)d" % faces)
        self.StyleClearAll()  # Reset all to be like the default
        
        # Import text style specs from config file
        for spec in text_styles:
            self.StyleSetSpec(*spec)
        
        self.SetCaretForeground("BLUE")
        
        self.SetMarginType(1, stc.STC_MARGIN_NUMBER)
        self.SetMarginWidth(1, 30)


    def OnUpdateUI(self, evt):
        """Syntax highlighting while editing"""
        
        # check for matching braces
        brace_at_caret = -1
        brace_opposite = -1
        char_before = None
        caret_pos = self.GetCurrentPos()

        if caret_pos > 0:
            char_before = self.GetCharAt(caret_pos - 1)
            style_before = self.GetStyleAt(caret_pos - 1)

        # check before
        if char_before and chr(char_before) in "[]{}()" and \
           style_before == stc.STC_P_OPERATOR:
            brace_at_caret = caret_pos - 1

        # check after
        if brace_at_caret < 0:
            char_after = self.GetCharAt(caret_pos)
            style_after = self.GetStyleAt(caret_pos)

            if char_after and chr(char_after) in "[]{}()" and \
               style_after == stc.STC_P_OPERATOR:
                brace_at_caret = caret_pos

        if brace_at_caret >= 0:
            brace_opposite = self.BraceMatch(brace_at_caret)

        if brace_at_caret != -1  and brace_opposite == -1:
            self.BraceBadLight(brace_at_caret)
        else:
            self.BraceHighlight(brace_at_caret, brace_opposite)

    def OnMarginClick(self, evt):
        # fold and unfold as needed
        if evt.GetMargin() == 2:
            if evt.GetShift() and evt.GetControl():
                self.fold_all()
            else:
                line_clicked = self.LineFromPosition(evt.GetPosition())

                if self.GetFoldLevel(line_clicked) & \
                   stc.STC_FOLDLEVELHEADERFLAG:
                    if evt.GetShift():
                        self.SetFoldExpanded(line_clicked, True)
                        self.expand(line_clicked, True, True, 1)
                    elif evt.GetControl():
                        if self.GetFoldExpanded(line_clicked):
                            self.SetFoldExpanded(line_clicked, False)
                            self.expand(line_clicked, False, True, 0)
                        else:
                            self.SetFoldExpanded(line_clicked, True)
                            self.expand(line_clicked, True, True, 100)
                    else:
                        self.ToggleFold(line_clicked)
    
    def fold_all(self):
        """Folds/unfolds all levels in the editor"""
        
        line_count = self.GetLineCount()
        expanding = True
        
        # find out if we are folding or unfolding
        for line_num in range(line_count):
            if self.GetFoldLevel(line_num) & stc.STC_FOLDLEVELHEADERFLAG:
                expanding = not self.GetFoldExpanded(line_num)
                break
        
        line_num = 0
        
        while line_num < line_count:
            level = self.GetFoldLevel(line_num)
            if level & stc.STC_FOLDLEVELHEADERFLAG and \
               (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE:
                
                if expanding:
                    self.SetFoldExpanded(line_num, True)
                    line_num = self.expand(line_num, True)
                    line_num = line_num - 1
                else:
                    last_child = self.GetLastChild(line_num, -1)
                    self.SetFoldExpanded(line_num, False)
                    
                    if last_child > line_num:
                        self.HideLines(line_num+1, last_child)
            
            line_num = line_num + 1
    
    def expand(self, line, do_expand, force=False, vislevels=0, level=-1):
        """Multi-purpose expand method from original STC class"""
        
        lastchild = self.GetLastChild(line, level)
        line += 1
        
        while line <= lastchild:
            if force:
                if vislevels > 0:
                    self.ShowLines(line, line)
                else:
                    self.HideLines(line, line)
            elif do_expand:
                self.ShowLines(line, line)
            
            if level == -1:
                level = self.GetFoldLevel(line)
            
            if level & stc.STC_FOLDLEVELHEADERFLAG:
                if force:
                    self.SetFoldExpanded(line, vislevels - 1)
                    line = self.expand(line, do_expand, force, vislevels-1)
                
                else:
                    expandsub = do_expand and self.GetFoldExpanded(line)
                    line = self.expand(line, expandsub, force, vislevels-1)
            else:
                line += 1
        
        return line
        
# end of class PythonSTC
