PageRenderTime 58ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/wxspreadsheet.py

https://bitbucket.org/ccoughlin/wxspreadsheet
Python | 198 lines | 184 code | 2 blank | 12 comment | 9 complexity | 16ed050a6918abd8ff7565e79f2441be MD5 | raw file
  1. #!/usr/bin/env python
  2. """
  3. wxspreadsheet.py: extension of wxPython's CSheet (grid)
  4. with basic spreadsheet functionality
  5. """
  6. import wx.lib.sheet
  7. import wx.grid
  8. import csv
  9. class ContextMenu(wx.Menu):
  10. '''Basic right-click popup menu for CSheet controls. Currently
  11. implements copy-paste selected cell(s), insert row / column, delete
  12. row / column.'''
  13. def __init__(self, parent):
  14. wx.Menu.__init__(self)
  15. self.parent = parent
  16. insertrow = wx.MenuItem(self, wx.NewId(), 'Insert Row(s)')
  17. self.AppendItem(insertrow)
  18. self.Bind(wx.EVT_MENU, self.OnInsertRow, id=insertrow.GetId())
  19. deleterow = wx.MenuItem(self, wx.NewId(), 'Delete Row(s)')
  20. self.AppendItem(deleterow)
  21. self.Bind(wx.EVT_MENU, self.OnDeleteRow, id=deleterow.GetId())
  22. insertcol = wx.MenuItem(self, wx.NewId(), 'Insert Column(s)')
  23. self.AppendItem(insertcol)
  24. self.Bind(wx.EVT_MENU, self.OnInsertCol, id=insertcol.GetId())
  25. deletecol = wx.MenuItem(self, wx.NewId(), 'Delete Column(s)')
  26. self.AppendItem(deletecol)
  27. self.Bind(wx.EVT_MENU, self.OnDeleteCol, id=deletecol.GetId())
  28. self.AppendSeparator()
  29. copy = wx.MenuItem(self, wx.NewId(), 'Copy')
  30. self.AppendItem(copy)
  31. self.Bind(wx.EVT_MENU, self.OnCopy, id=copy.GetId())
  32. paste = wx.MenuItem(self, wx.NewId(), 'Paste')
  33. self.AppendItem(paste)
  34. self.Bind(wx.EVT_MENU, self.OnPaste, id=paste.GetId())
  35. clear = wx.MenuItem(self, wx.NewId(), 'Clear Selected Cells')
  36. self.AppendItem(clear)
  37. self.Bind(wx.EVT_MENU, self.OnClear, id=clear.GetId())
  38. def OnInsertRow(self, event):
  39. '''Basic "Insert Row(s) Here" function'''
  40. self.parent.SelectRow(self._getRow())
  41. self.parent.InsertRows(self._getRow(), self._getSelectionRowSize())
  42. def OnDeleteRow(self, event):
  43. '''Basic "Delete Row(s)" function'''
  44. self.parent.DeleteRows(self._getRow(), self._getSelectionRowSize())
  45. def OnInsertCol(self, event):
  46. '''Basic "Insert Column(s) Here" function'''
  47. self.parent.InsertCols(self._getCol(), self._getSelectionColSize())
  48. def OnDeleteCol(self, event):
  49. '''Basic "Delete Column(s)" function'''
  50. self.parent.DeleteCols(self._getCol(), self._getSelectionColSize())
  51. def OnClear(self, event):
  52. '''Erases the contents of the currently selected cell(s).'''
  53. self.parent.Clear()
  54. def OnCopy(self, event):
  55. '''Copies the contents of the currently selected cell(s)
  56. to the clipboard.'''
  57. self.parent.Copy()
  58. def OnPaste(self, event):
  59. '''Pastes the clipboard's contents to the currently
  60. selected cell(s).'''
  61. self.parent.Paste()
  62. def _getRow(self):
  63. '''Returns the first (top) row in the selected row(s) if any,
  64. otherwise returns the row of the current cursor position.'''
  65. selected_row = self.parent.GetSelectedRows()
  66. if selected_row != []:
  67. return selected_row[0]
  68. else:
  69. return self.parent.GetGridCursorRow()
  70. def _getCol(self):
  71. '''Returns the first (left) row in the selected column(s) if any,
  72. otherwise returns the column of the current cursor position.'''
  73. selected_col = self.parent.GetSelectedCols()
  74. if selected_col != []:
  75. return selected_col[0]
  76. else:
  77. return self.parent.GetGridCursorCol()
  78. def _getSelectionRowSize(self):
  79. '''Returns the number of selected rows, number of rows in the
  80. current selection, or 1 in order of preference.'''
  81. numrows = 1
  82. if self.parent.GetSelectionBlockTopLeft() != []:
  83. numrows = self.parent.GetSelectionBlockBottomRight()[0][0] -\
  84. self.parent.GetSelectionBlockTopLeft()[0][0]+1
  85. else:
  86. numrows = len(self.parent.GetSelectedRows())
  87. return numrows
  88. def _getSelectionColSize(self):
  89. '''Returns the number of selected columns, number of columns in the
  90. current selection, or 1 in order of preference.'''
  91. numcols = 1
  92. if self.parent.GetSelectionBlockTopLeft() != []:
  93. numcols = self.parent.GetSelectionBlockBottomRight()[0][1] -\
  94. self.parent.GetSelectionBlockTopLeft()[0][1]+1
  95. else:
  96. numcols = len(self.parent.GetSelectedCols())
  97. return numcols
  98. class SpreadsheetTextCellEditor(wx.TextCtrl):
  99. """ Custom text control for cell editing """
  100. def __init__(self, parent, id, grid):
  101. wx.TextCtrl.__init__(self, parent, id, "",
  102. style=wx.NO_BORDER | wx.TE_PROCESS_ENTER)
  103. self._grid = grid # Save grid reference
  104. self.Bind(wx.EVT_CHAR, self.OnChar)
  105. def OnChar(self, evt): # Hook OnChar for custom behavior
  106. """Customizes char events """
  107. key = evt.GetKeyCode()
  108. if key == wx.WXK_DOWN or key == wx.WXK_RETURN:
  109. self._grid.DisableCellEditControl() # Commit the edit
  110. self._grid.MoveCursorDown(False) # Change the current cell
  111. elif key == wx.WXK_UP:
  112. self._grid.DisableCellEditControl() # Commit the edit
  113. self._grid.MoveCursorUp(False) # Change the current cell
  114. elif key == wx.WXK_LEFT:
  115. self._grid.DisableCellEditControl() # Commit the edit
  116. self._grid.MoveCursorLeft(False) # Change the current cell
  117. elif key == wx.WXK_RIGHT:
  118. self._grid.DisableCellEditControl() # Commit the edit
  119. self._grid.MoveCursorRight(False) # Change the current cell
  120. evt.Skip() # Continue event
  121. class SpreadsheetCellEditor(wx.lib.sheet.CCellEditor):
  122. """ Custom cell editor """
  123. def __init__(self, grid):
  124. super(SpreadsheetCellEditor, self).__init__(grid)
  125. def Create(self, parent, id, evtHandler):
  126. """ Create the actual edit control. Must derive from wxControl.
  127. Must Override
  128. """
  129. self._tc = SpreadsheetTextCellEditor(parent, id, self._grid)
  130. self._tc.SetInsertionPoint(0)
  131. self.SetControl(self._tc)
  132. if evtHandler:
  133. self._tc.PushEventHandler(evtHandler)
  134. class Spreadsheet(wx.lib.sheet.CSheet):
  135. '''Child class of CSheet (child of wxGrid) that implements a basic
  136. right-click popup menu.'''
  137. def __init__(self, parent):
  138. super(Spreadsheet, self).__init__(parent)
  139. def OnRightClick(self, event):
  140. '''Defines the right click popup menu for the spreadsheet'''
  141. '''Move the cursor to the cell clicked'''
  142. self.SetGridCursor(event.GetRow(), event.GetCol())
  143. self.PopupMenu(ContextMenu(self), event.GetPosition())
  144. def ReadCSV(self, csvfile, _delimiter=',' , _quotechar='#'):
  145. '''Reads a CSV file into the current spreadsheet, replacing
  146. existing contents (if any).'''
  147. self.ClearGrid()
  148. with open(csvfile, 'rU') as inputfile:
  149. csv_reader = csv.reader(inputfile,
  150. delimiter = _delimiter, quotechar = _quotechar)
  151. try:
  152. self.SetNumberRows(0)
  153. self.SetNumberCols(0)
  154. rownum = 0
  155. for row in csv_reader:
  156. self.AppendRows(1)
  157. numcols = len(row)
  158. if self.GetNumberCols() < numcols:
  159. self.SetNumberCols(numcols)
  160. colnum = 0
  161. for cell in row:
  162. self.SetCellValue(rownum, colnum, str(cell))
  163. colnum = colnum + 1
  164. rownum = rownum + 1
  165. except csv.Error as err:
  166. print("Skipping line {0}: {1}".format(csv_reader.line_num, err))
  167. def WriteCSV(self, csvfile, _delimiter=',', _quotechar='#', _quoting = csv.QUOTE_MINIMAL):
  168. '''Writes the current contents of the spreadsheet to a CSV file'''
  169. csv_writer = csv.writer(open(csvfile, 'wb'), delimiter=_delimiter, quotechar=_quotechar, quoting=_quoting)
  170. for rownum in range(self.GetNumberRows()):
  171. rowdata = []
  172. for colnum in range(self.GetNumberCols()):
  173. rowdata.append(self.GetCellValue(rownum, colnum))
  174. csv_writer.writerow(rowdata)