#!/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 wx
import wx.grid
import wx.stc  as  stc
import wx.lib.mixins.listctrl  as  listmix
import numpy

from _datastructures import PyspreadGrid
from _choicebars import ContextMenu
from _interfaces import Clipboard
from config import faces, icon_size, DEBUG


class IndexGrid(wx.grid.Grid):
    """Grid that supports indexing and __len__"""
    def __getitem__(self, key):
        if type(key) is tuple:
            row, col = key[:2]
            if row < 0:
                row += self.GetNumberRows()
            if row >= 0:
                return self.GetCellValue(row, col)
        raise KeyError("key must be a tuple of length 2")

    def __setitem__(self, key, value):
        if type(key) is tuple:
            row, col = key[:2]
            if row < 0:
                row += self.GetNumberRows()
            if row >= 0:
                return self.SetCellValue(row, col, value)
        raise KeyError("key must be a tuple of length 2")

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

# end of class IndexGrid


class MainGrid(IndexGrid):
    """Main grid"""
    def __init__(self, parent, id=wx.NewId(), pos=(0, 0), size=(1, 1), style=0, \
                 mainwindow=None, datagrid=None, dim=(1000, 100, 3)):
        # Sub widgets
        self.parent = parent
        self.mainwindow = mainwindow
        self.entry_line = wx.TextCtrl(parent, -1, "")        
        self.pysgrid = PyspreadGrid(dim)
        self.__add_combo(parent)
        self.contextmenu = ContextMenu(parent=parent)
        self.clipboard = Clipboard()
        
        self.copy_selection = [] # Cells from last copy operation 
        self.current_table = 0
        self.key = (0, 0, 0)
        self.selection_imminent = False
        
        wx.grid.Grid.__init__(self, parent, id, pos, size, style)
        
        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.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 __add_combo(self, parent):
        """Choicebox for the Z choice of the worksheet"""
        cbID = wx.NewId()
        self.cbox_Z = wx.ComboBox(parent.main_window_toolbar, cbID, "0", \
                      choices=[str(dim) for dim in xrange(self.pysgrid.sgrid.shape[2])],
                      size=(icon_size[0]*2, icon_size[1]-4), style=wx.CB_DROPDOWN)
        parent.main_window_toolbar.AddControl(self.cbox_Z)
            
        parent.Bind(wx.EVT_TEXT, self.OnCombo, id=cbID)
        parent.Bind(wx.EVT_COMBOBOX, self.OnCombo, id=cbID)
        parent.main_window_toolbar.Realize()
        
    def create_rowcol(self):
        """Creates a new, empty 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, str(i))
        for i in xrange(self.pysgrid.sgrid.shape[1]):
            self.SetColLabelValue(i, str(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 tl, br in selectionblock:
            selection += [(x, y) for x in xrange(tl[0], br[0]+1) 
                                 for y in xrange(tl[1], br[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):
        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 __fill_cells(self, gridslice=None):
        """Fills grid"""
        for i in self.pysgrid.get_function_cell_indices(gridslice):
            if i[2] == self.current_table:
                self.SetCellValue(i[0], i[1], str(self.pysgrid[i]))
        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)
        self.SetCellValue(current_cell_index[0], \
                          current_cell_index[1], \
                          str(self.pysgrid[current_cell_index]))

    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: str(key[2])
            self.OnCombo(event)
            self.SetFocus()
        self.MakeCellVisible(*key[:2])
        self.SetGridCursor(*key[:2])

    def OnScroll(self, event):
        self.update_grid()
        self.Update()
        event.Skip()

    def OnCellEditorShown(self, event):
        if DEBUG: print "Event handler `OnCellEditorShown'"
        self.key = (event.Row, event.Col, self.current_table)
        self._set_cell_to_expression(self.key)
        event.Skip()

    def OnCellChanged(self, event):
        if DEBUG: print "Event handler `OnCellChanged'"
        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): # wxGlade: MainWindow.<event_handler>
        if DEBUG: print "Event handler `OnText'"
        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):
        if event.GetKeyCode() == 13:
            self.update_grid()
            self.SetFocus()
        event.Skip()

    def OnCellSelected(self, event):
        if DEBUG: print "Event handler `OnCellSelected'"
        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):
        if DEBUG: print "Event handler `OnContextMenu'"
        self.PopupMenu(self.contextmenu)
        #contextmenu.Destroy()
        event.Skip()
    
    def OnCombo(self, event):
        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
            self.pos = tuple(list(self.get_currentcell())+[self.current_table])
            currstr = self.pysgrid.sgrid[self.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()

    def loadfile(self, filename):
        """Load file named fileneme into spreadsheet"""
        try:
            infile = bz2.BZ2File(filename, "rb")
        except IOError:
            return 0
        self.pysgrid.sgrid = pickle.load(infile)
        infile.close()
        self.DeleteRows(0, self.pysgrid.fgrid.shape[0])
        self.DeleteCols(0, self.pysgrid.fgrid.shape[1])
        self.AppendRows(self.pysgrid.sgrid.shape[0])
        self.AppendCols(self.pysgrid.sgrid.shape[1])
        self.cbox_Z.Clear()
        self.cbox_Z.AppendItems([str(i) for i in xrange(self.pysgrid.sgrid.shape[2])])
        self.pysgrid.fgrid = numpy.zeros(self.pysgrid.sgrid.shape, dtype="O")
        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 getselectiondata(self, source, rowslice, colslice, selection=None):
        """2D source data array that matches the current selection"""
        try:
            data = source[rowslice, colslice, self.current_table].copy()
        except AttributeError:
            data = source[rowslice, colslice, self.current_table]
        try:
            len(data)
        except TypeError:
            return data
        for row in xrange(len(data)):
            for col in xrange(len(data[row])):
                if selection is not None and \
                   (row + rowslice.start, col + colslice.start) not in selection:
                    data[row, col] = '\b'  # \b stands for omitted field
        return data

    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 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):
            sel = focus.GetStringSelection()
            if sel != "":
                clipboard_data = sel
                self.copy_selection = []
            else: return 0
        else:            
            selection = self.get_selection()
            #print selection
            rowslice, colslice = self.get_selected_rows_cols(selection)
            data = self.getselectiondata(source, rowslice, colslice, selection)
            #print data
            try:
                clipboard_data = "\n".join("\t".join(str(d) for d in datarow) for datarow in data)
            except TypeError:
                clipboard_data = str(data)
            self.copy_selection = [(cell[0]-rowslice.start, cell[1]-colslice.start) for cell in selection]
        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, pos=pastepos)
        self.update_grid(funceval=True)
    
    def undo(self):
        self.pysgrid.unredo.undo()
        self.ClearGrid()
        self.update_grid(funceval=True)
        self.create_rowcol()
        
    def redo(self):
        self.pysgrid.unredo.redo()
        self.ClearGrid()
        self.update_grid(funceval=True)
        self.create_rowcol()

    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 delete(self, selection=None):
        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 insert_rows(self):
        # 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 and pysgrid
        operation = (self.InsertRows, [current_row, no_selected_rows, False])
        undo_operation = (self.DeleteRows, [current_row, no_selected_rows, False])
        self.pysgrid.unredo.append(undo_operation, operation)
        self.InsertRows(current_row, 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_cols(self):
        # 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.append(undo_operation, operation)
        self.InsertCols(current_col, 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_tables(self):
        current_table = self.current_table
        no_selected_tables = 1
        operation = (self.cbox_Z.Append, [str(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(str(self.pysgrid.sgrid.shape[2]))
        self.pysgrid.insert(insertionpoint=[None, None, current_table], notoinsert=no_selected_tables)
        self.pysgrid.unredo.mark()

    def delete_rows(self):
        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_cols(self):
        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_tables(self):
        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, [str(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()

# 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, -1)
        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):
        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):
        i = 0
        for i, variable in enumerate(macro.func_code.co_varnames): 
            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):
        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):
        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, ID, pos=wx.DefaultPosition,
                 size=wx.DefaultSize, style=0):
        wx.ListCtrl.__init__(self, parent, ID, 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, parent, ID,
                 pos=wx.DefaultPosition, size=wx.DefaultSize,
                 style=0):
        stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style)

        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.SetBufferedDraw(False)
        #self.SetViewEOL(True)
        #self.SetEOLMode(stc.STC_EOL_CRLF)
        self.SetUseAntiAliasing(True)
        
        self.SetEdgeMode(stc.STC_EDGE_BACKGROUND)
        self.SetEdgeColumn(78)

        # Setup a margin to hold fold markers
        #self.SetFoldFlags(16)  ###  WHAT IS THIS VALUE?  WHAT ARE THE OTHER FLAGS?  DOES IT MATTER?
        self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
        self.SetMarginMask(2, stc.STC_MASK_FOLDERS)
        self.SetMarginSensitive(2, True)
        self.SetMarginWidth(2, 12)

        if self.fold_symbols == 0:
            # Arrow pointing right for contracted folders, arrow pointing down for expanded
            self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN,    stc.STC_MARK_ARROWDOWN, "black", "black")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDER,        stc.STC_MARK_ARROW, "black", "black")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB,     stc.STC_MARK_EMPTY, "black", "black")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL,    stc.STC_MARK_EMPTY, "black", "black")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND,     stc.STC_MARK_EMPTY,     "white", "black")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY,     "white", "black")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY,     "white", "black")
            
        elif self.fold_symbols == 1:
            # Plus for contracted folders, minus for expanded
            self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN,    stc.STC_MARK_MINUS, "white", "black")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDER,        stc.STC_MARK_PLUS,  "white", "black")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB,     stc.STC_MARK_EMPTY, "white", "black")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL,    stc.STC_MARK_EMPTY, "white", "black")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND,     stc.STC_MARK_EMPTY, "white", "black")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_EMPTY, "white", "black")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_EMPTY, "white", "black")

        elif self.fold_symbols == 2:
            # Like a flattened tree control using circular headers and curved joins
            self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN,    stc.STC_MARK_CIRCLEMINUS,          "white", "#404040")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDER,        stc.STC_MARK_CIRCLEPLUS,           "white", "#404040")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB,     stc.STC_MARK_VLINE,                "white", "#404040")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL,    stc.STC_MARK_LCORNERCURVE,         "white", "#404040")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND,     stc.STC_MARK_CIRCLEPLUSCONNECTED,  "white", "#404040")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_CIRCLEMINUSCONNECTED, "white", "#404040")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNERCURVE,         "white", "#404040")

        elif self.fold_symbols == 3:
            # Like a flattened tree control using square headers
            self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN,    stc.STC_MARK_BOXMINUS,          "white", "#808080")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDER,        stc.STC_MARK_BOXPLUS,           "white", "#808080")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB,     stc.STC_MARK_VLINE,             "white", "#808080")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL,    stc.STC_MARK_LCORNER,           "white", "#808080")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND,     stc.STC_MARK_BOXPLUSCONNECTED,  "white", "#808080")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080")
            self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER,           "white", "#808080")


        self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
        self.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)

        # Make some styles,  The lexer defines what each style is used for, we
        # just have to define what each style looks like.  This set is adapted from
        # Scintilla sample property files.

        # 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

        # Global default styles for all languages
        self.StyleSetSpec(stc.STC_STYLE_DEFAULT,     "face:%(helv)s,size:%(size)d" % faces)
        self.StyleSetSpec(stc.STC_STYLE_LINENUMBER,  "back:#C0C0C0,face:%(helv)s,size:%(size2)d" % faces)
        self.StyleSetSpec(stc.STC_STYLE_CONTROLCHAR, "face:%(other)s" % faces)
        self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,  "fore:#FFFFFF,back:#0000FF,bold")
        self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,    "fore:#000000,back:#FF0000,bold")

        # Python styles
        # Default 
        self.StyleSetSpec(stc.STC_P_DEFAULT, "fore:#000000,face:%(helv)s,size:%(size)d" % faces)
        # Comments
        self.StyleSetSpec(stc.STC_P_COMMENTLINE, "fore:#007F00,face:%(other)s,size:%(size)d" % faces)
        # Number
        self.StyleSetSpec(stc.STC_P_NUMBER, "fore:#007F7F,size:%(size)d" % faces)
        # String
        self.StyleSetSpec(stc.STC_P_STRING, "fore:#7F007F,face:%(helv)s,size:%(size)d" % faces)
        # Single quoted string
        self.StyleSetSpec(stc.STC_P_CHARACTER, "fore:#7F007F,face:%(helv)s,size:%(size)d" % faces)
        # Keyword
        self.StyleSetSpec(stc.STC_P_WORD, "fore:#00007F,bold,size:%(size)d" % faces)
        # Triple quotes
        self.StyleSetSpec(stc.STC_P_TRIPLE, "fore:#7F0000,size:%(size)d" % faces)
        # Triple double quotes
        self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, "fore:#7F0000,size:%(size)d" % faces)
        # Class name definition
        self.StyleSetSpec(stc.STC_P_CLASSNAME, "fore:#0000FF,bold,underline,size:%(size)d" % faces)
        # Function or method name definition
        self.StyleSetSpec(stc.STC_P_DEFNAME, "fore:#007F7F,bold,size:%(size)d" % faces)
        # Operators
        self.StyleSetSpec(stc.STC_P_OPERATOR, "bold,size:%(size)d" % faces)
        # Identifiers
        self.StyleSetSpec(stc.STC_P_IDENTIFIER, "fore:#000000,face:%(helv)s,size:%(size)d" % faces)
        # Comment-blocks
        self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, "fore:#7F7F7F,size:%(size)d" % faces)
        # End of line where string is not closed
        self.StyleSetSpec(stc.STC_P_STRINGEOL, "fore:#000000,face:%(mono)s,back:#E0C0E0,eol,size:%(size)d" % faces)

        self.SetCaretForeground("BLUE")
        
        self.SetMarginType(1, stc.STC_MARGIN_NUMBER)
        self.SetMarginWidth(1, 30)

        # register some images for use in the AutoComplete box.
        self.RegisterImage(2, 
            wx.ArtProvider.GetBitmap(wx.ART_NEW, size=(16,16)))
        self.RegisterImage(3, 
            wx.ArtProvider.GetBitmap(wx.ART_COPY, size=(16,16)))


    def OnKeyPressed(self, event):
        if self.CallTipActive():
            self.CallTipCancel()
        key = event.GetKeyCode()

        if key == 32 and event.ControlDown():
            pos = self.GetCurrentPos()

            # Tips
            if event.ShiftDown():
                self.CallTipSetBackground("yellow")
                self.CallTipShow(pos, 'lots of of text: blah, blah, blah\n\n'
                                 'show some suff, maybe parameters..\n\n'
                                 'fubar(param1, param2)')
            # Code completion
            else:
                #lst = []
                #for x in range(50000):
                #    lst.append('%05d' % x)
                #st = " ".join(lst)
                #print len(st)
                #self.AutoCompShow(0, st)

                kw = keyword.kwlist[:]
#                kw.append("zzzzzz?2")
#                kw.append("aaaaa?2")
#                kw.append("__init__?3")
#                kw.append("zzaaaaa?2")
#                kw.append("zzbaaaa?2")
#                kw.append("this_is_a_longer_value")
#                #kw.append("this_is_a_much_much_much_much_much_much_much_longer_value")

                kw.sort()  # Python sorts are case sensitive
                self.AutoCompSetIgnoreCase(False)  # so this needs to match

                # Images are specified with a appended "?type"
                for i in range(len(kw)):
                    if kw[i] in keyword.kwlist:
                        kw[i] = kw[i] + "?1"

                self.AutoCompShow(0, " ".join(kw))
        else:
            event.Skip()


    def OnUpdateUI(self, evt):
        # check for matching braces
        braceAtCaret = -1
        braceOpposite = -1
        charBefore = None
        caretPos = self.GetCurrentPos()

        if caretPos > 0:
            charBefore = self.GetCharAt(caretPos - 1)
            styleBefore = self.GetStyleAt(caretPos - 1)

        # check before
        if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
            braceAtCaret = caretPos - 1

        # check after
        if braceAtCaret < 0:
            charAfter = self.GetCharAt(caretPos)
            styleAfter = self.GetStyleAt(caretPos)

            if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
                braceAtCaret = caretPos

        if braceAtCaret >= 0:
            braceOpposite = self.BraceMatch(braceAtCaret)

        if braceAtCaret != -1  and braceOpposite == -1:
            self.BraceBadLight(braceAtCaret)
        else:
            self.BraceHighlight(braceAtCaret, braceOpposite)
            #pt = self.PointFromPosition(braceOpposite)
            #self.Refresh(True, wxRect(pt.x, pt.y, 5,5))
            #print pt
            #self.Refresh(False)


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

                if self.GetFoldLevel(lineClicked) & stc.STC_FOLDLEVELHEADERFLAG:
                    if evt.GetShift():
                        self.SetFoldExpanded(lineClicked, True)
                        self.Expand(lineClicked, True, True, 1)
                    elif evt.GetControl():
                        if self.GetFoldExpanded(lineClicked):
                            self.SetFoldExpanded(lineClicked, False)
                            self.Expand(lineClicked, False, True, 0)
                        else:
                            self.SetFoldExpanded(lineClicked, True)
                            self.Expand(lineClicked, True, True, 100)
                    else:
                        self.ToggleFold(lineClicked)


    def FoldAll(self):
        lineCount = self.GetLineCount()
        expanding = True

        # find out if we are folding or unfolding
        for lineNum in range(lineCount):
            if self.GetFoldLevel(lineNum) & stc.STC_FOLDLEVELHEADERFLAG:
                expanding = not self.GetFoldExpanded(lineNum)
                break

        lineNum = 0

        while lineNum < lineCount:
            level = self.GetFoldLevel(lineNum)
            if level & stc.STC_FOLDLEVELHEADERFLAG and \
               (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE:

                if expanding:
                    self.SetFoldExpanded(lineNum, True)
                    lineNum = self.Expand(lineNum, True)
                    lineNum = lineNum - 1
                else:
                    lastChild = self.GetLastChild(lineNum, -1)
                    self.SetFoldExpanded(lineNum, False)

                    if lastChild > lineNum:
                        self.HideLines(lineNum+1, lastChild)

            lineNum = lineNum + 1



    def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
        lastChild = self.GetLastChild(line, level)
        line = line + 1

        while line <= lastChild:
            if force:
                if visLevels > 0:
                    self.ShowLines(line, line)
                else:
                    self.HideLines(line, line)
            else:
                if doExpand:
                    self.ShowLines(line, line)

            if level == -1:
                level = self.GetFoldLevel(line)

            if level & stc.STC_FOLDLEVELHEADERFLAG:
                if force:
                    if visLevels > 1:
                        self.SetFoldExpanded(line, True)
                    else:
                        self.SetFoldExpanded(line, False)

                    line = self.Expand(line, doExpand, force, visLevels-1)

                else:
                    if doExpand and self.GetFoldExpanded(line):
                        line = self.Expand(line, True, force, visLevels-1)
                    else:
                        line = self.Expand(line, False, force, visLevels-1)
            else:
                line = line + 1

        return line
        
# end of class PythonSTC
