Package tkintertable :: Module Tables
[hide private]
[frames] | no frames]

Source Code for Module tkintertable.Tables

   1  #!/usr/bin/env python 
   2  """ 
   3      Implements the core TableCanvas class. 
   4      Created Oct 2008 
   5      Copyright (C) Damien Farrell 
   6   
   7      This program is free software; you can redistribute it and/or 
   8      modify it under the terms of the GNU General Public License 
   9      as published by the Free Software Foundation; either version 2 
  10      of the License, or (at your option) any later version. 
  11   
  12      This program is distributed in the hope that it will be useful, 
  13      but WITHOUT ANY WARRANTY; without even the implied warranty of 
  14      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  15      GNU General Public License for more details. 
  16   
  17      You should have received a copy of the GNU General Public License 
  18      along with this program; if not, write to the Free Software 
  19      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
  20  """ 
  21   
  22  from Tkinter import * 
  23  from TableModels import TableModel 
  24  from TableFormula import Formula 
  25  from Prefs import Preferences 
  26  import tkFileDialog, tkMessageBox, tkSimpleDialog 
  27  import tkFont 
  28  import math, time 
  29  import os, types 
  30  import string, copy 
  31  import platform 
32 33 -class TableCanvas(Canvas):
34 """A tkinter class for providing table functionality""" 35
36 - def __init__(self, parent=None, model=None, width=None, height=None, 37 rows=10, cols=5, **kwargs):
38 Canvas.__init__(self, parent, bg='white', 39 width=width, height=height, 40 relief=GROOVE, 41 scrollregion=(0,0,300,200)) 42 self.parentframe = parent 43 #get platform into a variable 44 self.ostyp = self.checkOSType() 45 self.platform = platform.system() 46 self.width = width 47 self.height = height 48 self.set_defaults() 49 50 self.currentpage = None 51 self.navFrame = None 52 self.currentrow = 0 53 self.currentcol = 0 54 self.reverseorder = 0 55 self.startrow = self.endrow = None 56 self.startcol = self.endcol = None 57 self.allrows = False #for selected all rows without setting multiplerowlist 58 self.multiplerowlist=[] 59 self.multiplecollist=[] 60 self.col_positions=[] #record current column grid positions 61 self.mode = 'normal' 62 self.editable = True 63 self.filtered = False 64 65 self.loadPrefs() 66 #set any options passed in kwargs to overwrite defaults and prefs 67 for key in kwargs: 68 self.__dict__[key] = kwargs[key] 69 70 if model == None: 71 self.model = TableModel(rows=rows,columns=cols) 72 else: 73 self.model = model 74 75 self.rows = self.model.getRowCount() 76 self.cols = self.model.getColumnCount() 77 self.tablewidth = (self.cellwidth)*self.cols 78 self.do_bindings() 79 #initial sort order 80 self.model.setSortOrder() 81 82 #column specific actions, define for every column type in the model 83 #when you add a column type you should edit this dict 84 self.columnactions = {'text' : {"Edit": 'drawCellEntry' }, 85 'number' : {"Edit": 'drawCellEntry' }} 86 self.setFontSize() 87 return
88
89 - def set_defaults(self):
90 """Set default settings""" 91 self.cellwidth=150 92 self.maxcellwidth=200 93 self.rowheight=20 94 self.horizlines=1 95 self.vertlines=1 96 self.alternaterows=0 97 self.autoresizecols = 0 98 self.inset=2 99 self.x_start=0 100 self.y_start=1 101 self.linewidth=1.0 102 self.rowheaderwidth=40 103 self.showkeynamesinheader=False 104 self.thefont = ('Arial',12) 105 self.cellbackgr = '#F7F7FA' 106 self.entrybackgr = 'white' 107 self.grid_color = '#ABB1AD' 108 self.selectedcolor = 'yellow' 109 self.rowselectedcolor = '#CCCCFF' 110 self.multipleselectioncolor = '#ECD672' 111 return
112
113 - def setFontSize(self):
114 """Set font size to match font, we need to get rid of fontsize as 115 a separate variable?""" 116 if hasattr(self, 'thefont') and type(self.thefont) is types.TupleType: 117 self.fontsize = self.thefont[1] 118 return
119
120 - def mouse_wheel(self, event):
121 """Handle mouse wheel scroll for windows""" 122 123 if event.num == 5 or event.delta == -120: 124 event.widget.yview_scroll(1, UNITS) 125 self.tablerowheader.yview_scroll(1, UNITS) 126 if event.num == 4 or event.delta == 120: 127 if self.canvasy(0) < 0: 128 return 129 event.widget.yview_scroll(-1, UNITS) 130 self.tablerowheader.yview_scroll(-1, UNITS) 131 self.redrawVisible() 132 return
133
134 - def do_bindings(self):
135 """Bind keys and mouse clicks, this can be overriden""" 136 self.bind("<Button-1>",self.handle_left_click) 137 self.bind("<Double-Button-1>",self.handle_double_click) 138 self.bind("<Control-Button-1>", self.handle_left_ctrl_click) 139 self.bind("<Shift-Button-1>", self.handle_left_shift_click) 140 141 self.bind("<ButtonRelease-1>", self.handle_left_release) 142 if self.ostyp=='mac': 143 #For mac we bind Shift, left-click to right click 144 self.bind("<Button-2>", self.handle_right_click) 145 self.bind('<Shift-Button-1>',self.handle_right_click) 146 else: 147 self.bind("<Button-3>", self.handle_right_click) 148 149 self.bind('<B1-Motion>', self.handle_mouse_drag) 150 self.bind('<Motion>', self.handle_motion) 151 152 self.bind_all("<Control-x>", self.deleteRow) 153 self.bind_all("<Control-n>", self.addRow) 154 self.bind_all("<Delete>", self.clearData) 155 self.bind_all("<Control-v>", self.paste) 156 157 #if not hasattr(self,'parentapp'): 158 # self.parentapp = self.parentframe 159 160 self.parentframe.master.bind_all("<Right>", self.handle_arrow_keys) 161 self.parentframe.master.bind_all("<Left>", self.handle_arrow_keys) 162 self.parentframe.master.bind_all("<Up>", self.handle_arrow_keys) 163 self.parentframe.master.bind_all("<Down>", self.handle_arrow_keys) 164 self.parentframe.master.bind_all("<KP_8>", self.handle_arrow_keys) 165 self.parentframe.master.bind_all("<Return>", self.handle_arrow_keys) 166 self.parentframe.master.bind_all("<Tab>", self.handle_arrow_keys) 167 #if 'windows' in self.platform: 168 self.bind("<MouseWheel>", self.mouse_wheel) 169 self.bind('<Button-4>', self.mouse_wheel) 170 self.bind('<Button-5>', self.mouse_wheel) 171 self.focus_set() 172 return
173
174 - def getModel(self):
175 """Get the current table model""" 176 return self.model
177
178 - def setModel(self, model):
179 """Set a new model - requires redraw to reflect changes""" 180 self.model = model 181 return
182
183 - def createfromDict(self, data):
184 """Attempt to create a new model/table from a dict""" 185 try: 186 namefield=self.namefield 187 except: 188 namefield=data.keys()[0] 189 self.model = TableModel() 190 self.model.importDict(data, namefield=namefield) 191 self.model.setSortOrder(0,reverse=self.reverseorder) 192 return
193
194 - def createTableFrame(self, callback=None):
195 """Adds column header and scrollbars and combines them with 196 the current table adding all to the master frame provided in constructor. 197 Table is then redrawn.""" 198 199 #Add the table and header to the frame 200 self.tablerowheader = RowHeader(self.parentframe, self, width=self.rowheaderwidth) 201 self.tablecolheader = ColumnHeader(self.parentframe, self) 202 self.Yscrollbar = AutoScrollbar(self.parentframe,orient=VERTICAL,command=self.set_yviews) 203 self.Yscrollbar.grid(row=1,column=2,rowspan=1,sticky='news',pady=0,ipady=0) 204 self.Xscrollbar = AutoScrollbar(self.parentframe,orient=HORIZONTAL,command=self.set_xviews) 205 self.Xscrollbar.grid(row=2,column=1,columnspan=1,sticky='news') 206 self['xscrollcommand'] = self.Xscrollbar.set 207 self['yscrollcommand'] = self.Yscrollbar.set 208 self.tablecolheader['xscrollcommand'] = self.Xscrollbar.set 209 self.tablerowheader['yscrollcommand'] = self.Yscrollbar.set 210 self.parentframe.rowconfigure(1,weight=1) 211 self.parentframe.columnconfigure(1,weight=1) 212 213 self.tablecolheader.grid(row=0,column=1,rowspan=1,sticky='news',pady=0,ipady=0) 214 self.tablerowheader.grid(row=1,column=0,rowspan=1,sticky='news',pady=0,ipady=0) 215 self.grid(row=1,column=1,rowspan=1,sticky='news',pady=0,ipady=0) 216 217 self.adjustColumnWidths() 218 self.redrawTable(callback=callback) 219 self.parentframe.bind("<Configure>", self.redrawVisible) 220 self.tablecolheader.xview("moveto", 0) 221 self.xview("moveto", 0) 222 return
223
224 - def getVisibleRegion(self):
225 x1, y1 = self.canvasx(0), self.canvasy(0) 226 w, h = self.winfo_width(), self.winfo_height() 227 if w <= 1.0 or h <= 1.0: 228 w, h = self.master.winfo_width(), self.master.winfo_height() 229 x2, y2 = self.canvasx(w), self.canvasy(h) 230 return x1, y1, x2, y2
231
232 - def getRowPosition(self, y):
233 h = self.rowheight 234 y_start = self.y_start 235 row = (int(y)-y_start)/h 236 if row < 0: 237 return 0 238 if row > self.rows: 239 row = self.rows 240 return row
241
242 - def getColPosition(self, x):
243 x_start = self.x_start 244 w = self.cellwidth 245 i=0 246 col=0 247 for c in self.col_positions: 248 col = i 249 if c+w>=x: 250 break 251 i+=1 252 return col
253
254 - def getVisibleRows(self, y1, y2):
255 """Get the visible row range""" 256 start = self.getRowPosition(y1) 257 end = self.getRowPosition(y2)+1 258 if end > self.rows: 259 end = self.rows 260 return start, end
261
262 - def getVisibleCols(self, x1, x2):
263 """Get the visible column range""" 264 start = self.getColPosition(x1) 265 end = self.getColPosition(x2)+1 266 if end > self.cols: 267 end = self.cols 268 return start, end
269
270 - def redrawVisible(self, event=None, callback=None):
271 """Redraw the visible portion of the canvas""" 272 273 model = self.model 274 self.rows = self.model.getRowCount() 275 self.cols = self.model.getColumnCount() 276 if self.cols == 0 or self.rows == 0: 277 self.delete('entry') 278 self.delete('rowrect') 279 self.delete('currentrect') 280 return 281 self.tablewidth = (self.cellwidth) * self.cols 282 self.configure(bg=self.cellbackgr) 283 self.setColPositions() 284 285 #are we drawing a filtered subset of the recs? 286 if self.filtered == True and self.model.filteredrecs != None: 287 self.rows = len(self.model.filteredrecs) 288 self.delete('colrect') 289 290 self.rowrange = range(0,self.rows) 291 self.configure(scrollregion=(0,0, self.tablewidth+self.x_start, 292 self.rowheight*self.rows+10)) 293 294 x1, y1, x2, y2 = self.getVisibleRegion() 295 startvisiblerow, endvisiblerow = self.getVisibleRows(y1, y2) 296 self.visiblerows = range(startvisiblerow, endvisiblerow) 297 startvisiblecol, endvisiblecol = self.getVisibleCols(x1, x2) 298 self.visiblecols = range(startvisiblecol, endvisiblecol) 299 300 self.drawGrid(startvisiblerow, endvisiblerow) 301 align = self.align 302 self.delete('fillrect') 303 for row in self.visiblerows: 304 if callback != None: 305 callback() 306 for col in self.visiblecols: 307 colname = model.getColumnName(col) 308 bgcolor = model.getColorAt(row,col, 'bg') 309 fgcolor = model.getColorAt(row,col, 'fg') 310 text = model.getValueAt(row,col) 311 self.drawText(row, col, text, fgcolor, align) 312 if bgcolor != None: 313 self.drawRect(row,col, color=bgcolor) 314 315 #self.drawSelectedCol() 316 self.tablecolheader.redraw() 317 self.tablerowheader.redraw(align=self.align, showkeys=self.showkeynamesinheader) 318 #self.setSelectedRow(self.currentrow) 319 self.drawSelectedRow() 320 self.drawSelectedRect(self.currentrow, self.currentcol) 321 #print self.multiplerowlist 322 323 if len(self.multiplerowlist)>1: 324 self.tablerowheader.drawSelectedRows(self.multiplerowlist) 325 self.drawMultipleRows(self.multiplerowlist) 326 self.drawMultipleCells() 327 return
328
329 - def redrawTable(self, event=None, callback=None):
330 self.redrawVisible(event, callback) 331 return
332
333 - def redrawCell(self, row=None, col=None, recname=None, colname=None):
334 """Redraw a specific cell only""" 335 if row == None and recname != None: 336 row = self.model.getRecordIndex(recname) 337 if col == None and colname != None: 338 col = self.model.getColumnIndex(colname) 339 bgcolor = self.model.getColorAt(row,col, 'bg') 340 fgcolor = self.model.getColorAt(row,col, 'fg') 341 text = self.model.getValueAt(row,col) 342 self.drawText(row, col, text, fgcolor) 343 if bgcolor != None: 344 self.drawRect(row,col, color=bgcolor) 345 return
346
347 - def adjustColumnWidths(self):
348 """Optimally adjust col widths to accomodate the longest entry 349 in each column - usually only called on first redraw""" 350 #self.cols = self.model.getColumnCount() 351 try: 352 fontsize = self.thefont[1] 353 except: 354 fontsize = self.fontsize 355 scale = 8.5 * float(fontsize)/12 356 for col in range(self.cols): 357 colname = self.model.getColumnName(col) 358 if self.model.columnwidths.has_key(colname): 359 w = self.model.columnwidths[colname] 360 else: 361 w = self.cellwidth 362 maxlen = self.model.getlongestEntry(col) 363 size = maxlen * scale 364 if size < w: 365 continue 366 #print col, size, self.cellwidth 367 if size >= self.maxcellwidth: 368 size = self.maxcellwidth 369 self.model.columnwidths[colname] = size + float(fontsize)/12*6 370 return
371
372 - def autoResizeColumns(self):
373 """Automatically set nice column widths and draw""" 374 self.adjustColumnWidths() 375 self.redrawTable() 376 return
377
378 - def setColPositions(self):
379 """Determine current column grid positions""" 380 self.col_positions=[] 381 w=self.cellwidth 382 x_pos=self.x_start 383 self.col_positions.append(x_pos) 384 for col in range(self.cols): 385 colname=self.model.getColumnName(col) 386 if self.model.columnwidths.has_key(colname): 387 x_pos=x_pos+self.model.columnwidths[colname] 388 else: 389 x_pos=x_pos+w 390 self.col_positions.append(x_pos) 391 self.tablewidth = self.col_positions[len(self.col_positions)-1] 392 return
393
394 - def sortTable(self, columnIndex=0, columnName=None, reverse=0):
395 """Set up sort order dict based on currently selected field""" 396 #if columnName != None: 397 # columnIndex = self.model.getColumnIndex(columnName) 398 self.model.setSortOrder(columnIndex, columnName, reverse) 399 self.redrawTable() 400 return
401
402 - def set_xviews(self,*args):
403 """Set the xview of table and col header""" 404 apply(self.xview,args) 405 apply(self.tablecolheader.xview,args) 406 self.redrawVisible() 407 return
408
409 - def set_yviews(self,*args):
410 """Set the xview of table and row header""" 411 apply(self.yview,args) 412 apply(self.tablerowheader.yview,args) 413 self.redrawVisible() 414 return
415
416 - def addRow(self, key=None, **kwargs):
417 """Add new row""" 418 key = self.model.addRow(key, **kwargs) 419 self.redrawTable() 420 self.setSelectedRow(self.model.getRecordIndex(key)) 421 return
422
423 - def addRows(self, num=None):
424 """Add new rows""" 425 if num == None: 426 num = tkSimpleDialog.askinteger("Now many rows?", 427 "Number of rows:",initialvalue=1, 428 parent=self.parentframe) 429 if not num: 430 return 431 keys = self.model.autoAddRows(num) 432 self.redrawTable() 433 self.setSelectedRow(self.model.getRecordIndex(keys[0])) 434 return
435
436 - def addColumn(self, newname=None):
437 """Add a new column""" 438 if newname == None: 439 from Dialogs import MultipleValDialog 440 coltypes = self.getModel().getDefaultTypes() 441 d = MultipleValDialog(title='New Column', 442 initialvalues=(coltypes, ''), 443 labels=('Column Type','Name'), 444 types=('list','string'), 445 parent=self.parentframe) 446 if d.result == None: 447 return 448 else: 449 coltype = d.results[0] 450 newname = d.results[1] 451 452 if newname != None: 453 if newname in self.getModel().columnNames: 454 tkMessageBox.showwarning("Name exists", 455 "Name already exists!", 456 parent=self.parentframe) 457 else: 458 self.model.addColumn(newname) 459 self.parentframe.configure(width=self.width) 460 self.redrawTable() 461 return
462
463 - def deleteRow(self):
464 """Delete a row""" 465 if len(self.multiplerowlist)>1: 466 n = tkMessageBox.askyesno("Delete", 467 "Delete Selected Records?", 468 parent=self.parentframe) 469 if n == True: 470 rows = self.multiplerowlist 471 self.model.deleteRows(rows) 472 self.setSelectedRow(0) 473 self.clearSelected() 474 self.redrawTable() 475 else: 476 n = tkMessageBox.askyesno("Delete", 477 "Delete This Record?", 478 parent=self.parentframe) 479 if n: 480 row = self.getSelectedRow() 481 self.model.deleteRow(row) 482 self.setSelectedRow(row-1) 483 self.clearSelected() 484 self.redrawTable() 485 return
486
487 - def deleteColumn(self):
488 """Delete currently selected column""" 489 n = tkMessageBox.askyesno("Delete", 490 "Delete This Column?", 491 parent=self.parentframe) 492 if n: 493 col = self.getSelectedColumn() 494 self.model.deleteColumn(col) 495 self.currentcol = self.currentcol - 1 496 self.redrawTable() 497 return
498
499 - def deleteCells(self, rows, cols):
500 """Clear the cell contents""" 501 n = tkMessageBox.askyesno("Clear Confirm", 502 "Clear this data?", 503 parent=self.parentframe) 504 if not n: 505 return 506 for col in cols: 507 for row in rows: 508 #absrow = self.get_AbsoluteRow(row) 509 self.model.deleteCellRecord(row, col) 510 self.redrawCell(row,col) 511 return
512
513 - def clearData(self, evt=None):
514 """Delete cells from gui event""" 515 rows = self.multiplerowlist 516 cols = self.multiplecollist 517 self.deleteCells(rows, cols) 518 return
519
520 - def autoAddColumns(self, numcols=None):
521 """Automatically add x number of cols""" 522 if numcols == None: 523 numcols = tkSimpleDialog.askinteger("Auto add rows.", 524 "How many empty columns?", 525 parent=self.parentframe) 526 self.model.auto_AddColumns(numcols) 527 self.parentframe.configure(width=self.width) 528 self.redrawTable() 529 return
530
531 - def getRecordInfo(self, row):
532 """Show the record for this row""" 533 model = self.model 534 #We need a custom dialog for allowing field entries here 535 #absrow = self.get_AbsoluteRow(row) 536 from Dialogs import RecordViewDialog 537 d = RecordViewDialog(title="Record Details", 538 parent=self.parentframe, table=self, row=row) 539 return
540
541 - def findValue(self, searchstring=None, findagain=None):
542 """Return the row/col for the input value""" 543 if searchstring == None: 544 searchstring = tkSimpleDialog.askstring("Search table.", 545 "Enter search value", 546 parent=self.parentframe) 547 found=0 548 if findagain == None or not hasattr(self,'foundlist'): 549 self.foundlist=[] 550 if self.model!=None: 551 for row in range(self.rows): 552 for col in range(self.cols): 553 text = str(self.model.getValueAt(row,col)) 554 if text=='' or text==None: 555 continue 556 cell=row,col 557 if findagain == 1 and cell in self.foundlist: 558 continue 559 if text.lower().find(searchstring.lower())!=-1: 560 print 'found in',row,col 561 found=1 562 #highlight cell 563 self.delete('searchrect') 564 self.drawRect(row, col, color='red', tag='searchrect', delete=0) 565 self.lift('searchrect') 566 self.lift('celltext'+str(col)+'_'+str(row)) 567 #add row/col to foundlist 568 self.foundlist.append(cell) 569 #need to scroll to centre the cell here.. 570 x,y = self.getCanvasPos(row, col) 571 self.xview('moveto', x) 572 self.yview('moveto', y) 573 self.tablecolheader.xview('moveto', x) 574 self.tablerowheader.yview('moveto', y) 575 return row, col 576 if found==0: 577 self.delete('searchrect') 578 print 'nothing found' 579 return None
580
581 - def showAll(self):
582 self.model.filteredrecs = None 583 self.filtered = False 584 self.redrawTable() 585 return
586
587 - def doFilter(self, event=None):
588 """Filter the table display by some column values. 589 We simply pass the model search function to the the filtering 590 class and that handles everything else. 591 See filtering frame class for how searching is done. 592 """ 593 if self.model==None: 594 return 595 names = self.filterframe.doFiltering(searchfunc=self.model.filterBy) 596 #create a list of filtered recs 597 self.model.filteredrecs = names 598 self.filtered = True 599 self.redrawTable() 600 return
601
602 - def createFilteringBar(self, parent=None, fields=None):
603 """Add a filter frame""" 604 if parent == None: 605 parent = Toplevel() 606 parent.title('Filter Records') 607 x,y,w,h = self.getGeometry(self.master) 608 parent.geometry('+%s+%s' %(x,y+h)) 609 if fields == None: 610 fields = self.model.columnNames 611 from Filtering import FilterFrame 612 self.filterframe = FilterFrame(parent, fields, 613 self.doFilter, self.closeFilterFrame) 614 self.filterframe.pack() 615 return parent
616
617 - def showFilteringBar(self):
618 if not hasattr(self, 'filterwin') or self.filterwin == None: 619 self.filterwin = self.createFilteringBar() 620 self.filterwin.protocol("WM_DELETE_WINDOW", self.closeFilterFrame) 621 else: 622 self.filterwin.lift() 623 return
624
625 - def closeFilterFrame(self):
626 """Callback for closing filter frame""" 627 self.filterwin.destroy() 628 self.filterwin = None 629 self.showAll() 630 return
631
632 - def resizeColumn(self, col, width):
633 """Resize a column by dragging""" 634 #print 'resizing column', col 635 #recalculate all col positions.. 636 colname=self.model.getColumnName(col) 637 self.model.columnwidths[colname]=width 638 self.setColPositions() 639 self.redrawTable() 640 self.drawSelectedCol(self.currentcol) 641 return
642
643 - def get_currentRecord(self):
644 """Get the currently selected record""" 645 rec = self.model.getRecordAtRow(self.currentrow) 646 return rec
647
648 - def get_currentColName(self):
649 """Get the currently selected record name""" 650 colname = self.mo(self.currentcol) 651 return colname
652
653 - def get_currentRecordName(self):
654 """Get the currently selected record name""" 655 recname = self.model.getRecName(self.currentrow) 656 return recname
657
658 - def get_selectedRecordNames(self):
659 """Get a list of the current multiple selection, if any""" 660 recnames=[] 661 for row in self.multiplerowlist: 662 recnames.append(self.model.getRecName(row)) 663 return recnames
664
665 - def get_currentRecCol(self):
666 """Get the clicked rec and col names as a tuple""" 667 recname = self.get_currentRecordName() 668 colname = self.get_currentColName() 669 return (recname, colname)
670
671 - def get_row_clicked(self, event):
672 """get row where event on canvas occurs""" 673 h=self.rowheight 674 #get coord on canvas, not window, need this if scrolling 675 y = int(self.canvasy(event.y)) 676 y_start=self.y_start 677 rowc = (int(y)-y_start)/h 678 #rowc = math.floor(rowc) 679 #print 'event.y',event.y, 'y',y 680 #print 'rowclicked', rowc 681 return rowc
682
683 - def get_col_clicked(self,event):
684 """get col where event on canvas occurs""" 685 w=self.cellwidth 686 x = int(self.canvasx(event.x)) 687 x_start=self.x_start 688 #print self.col_positions 689 for colpos in self.col_positions: 690 try: 691 nextpos=self.col_positions[self.col_positions.index(colpos)+1] 692 except: 693 nextpos=self.tablewidth 694 if x > colpos and x <= nextpos: 695 #print 'x=', x, 'colpos', colpos, self.col_positions.index(colpos) 696 return self.col_positions.index(colpos) 697 else: 698 #print None 699 pass
700 #return colc 701
702 - def setSelectedRow(self, row):
703 """Set currently selected row and reset multiple row list""" 704 self.currentrow = row 705 self.multiplerowlist = [] 706 self.multiplerowlist.append(row) 707 return
708
709 - def setSelectedCol(self, col):
710 """Set currently selected column""" 711 self.currentcol = col 712 self.multiplecollist = [] 713 self.multiplecollist.append(col) 714 return
715
716 - def setSelectedCells(self, startrow, endrow, startcol, endcol):
717 """Set a block of cells selected""" 718 self.currentrow = startrow 719 self.currentcol = startcol 720 if startrow < 0 or startcol < 0: 721 return 722 if endrow > self.rows or endcol > self.cols: 723 return 724 for r in range(startrow, endrow): 725 self.multiplerowlist.append(r) 726 for c in range(startcol, endcol): 727 self.multiplecollist.append(c) 728 return
729
730 - def getSelectedRow(self):
731 """Get currently selected row""" 732 return self.currentrow
733
734 - def getSelectedColumn(self):
735 """Get currently selected column""" 736 return self.currentcol
737
738 - def select_All(self):
739 """Select all rows and cells""" 740 self.startrow = 0 741 self.endrow = self.rows 742 self.multiplerowlist = range(self.startrow,self.endrow) 743 self.drawMultipleRows(self.multiplerowlist) 744 self.startcol = 0 745 self.endcol = self.cols 746 self.multiplecollist = range(self.startcol, self.endcol) 747 self.drawMultipleCells() 748 return
749
750 - def getCellCoords(self, row, col):
751 """Get x-y coordinates to drawing a cell in a given row/col""" 752 colname=self.model.getColumnName(col) 753 if self.model.columnwidths.has_key(colname): 754 w=self.model.columnwidths[colname] 755 else: 756 w=self.cellwidth 757 h=self.rowheight 758 x_start=self.x_start 759 y_start=self.y_start 760 761 #get nearest rect co-ords for that row/col 762 #x1=x_start+w*col 763 x1=self.col_positions[col] 764 y1=y_start+h*row 765 x2=x1+w 766 y2=y1+h 767 return x1,y1,x2,y2
768
769 - def getCanvasPos(self, row, col):
770 """Get the cell x-y coords as a fraction of canvas size""" 771 if self.rows==0: 772 return None, None 773 x1,y1,x2,y2 = self.getCellCoords(row,col) 774 cx=float(x1)/self.tablewidth 775 cy=float(y1)/(self.rows*self.rowheight) 776 return cx, cy
777
778 - def isInsideTable(self,x,y):
779 """Returns true if x-y coord is inside table bounds""" 780 if self.x_start < x < self.tablewidth and self.y_start < y < self.rows*self.rowheight: 781 return 1 782 else: 783 return 0 784 return answer
785
786 - def setRowHeight(self, h):
787 """Set the row height""" 788 self.rowheight = h 789 return
790
791 - def clearSelected(self):
792 self.delete('rect') 793 self.delete('entry') 794 self.delete('tooltip') 795 self.delete('searchrect') 796 self.delete('colrect') 797 self.delete('multicellrect') 798 799 #self.delete('formulabox') 800 return
801
802 - def gotoprevRow(self):
803 """Programmatically set previous row - eg. for button events""" 804 self.clearSelected() 805 current = self.getSelectedRow() 806 self.setSelectedRow(current-1) 807 self.startrow = current-1 808 self.endrow = current-1 809 #reset multiple selection list 810 self.multiplerowlist=[] 811 self.multiplerowlist.append(self.currentrow) 812 self.drawSelectedRect(self.currentrow, self.currentcol) 813 self.drawSelectedRow() 814 coltype = self.model.getColumnType(self.currentcol) 815 if coltype == 'text' or coltype == 'number': 816 self.drawCellEntry(self.currentrow, self.currentcol) 817 return
818
819 - def gotonextRow(self):
820 """Programmatically set next row - eg. for button events""" 821 self.clearSelected() 822 current = self.getSelectedRow() 823 self.setSelectedRow(current+1) 824 self.startrow = current+1 825 self.endrow = current+1 826 #reset multiple selection list 827 self.multiplerowlist=[] 828 self.multiplerowlist.append(self.currentrow) 829 self.drawSelectedRect(self.currentrow, self.currentcol) 830 self.drawSelectedRow() 831 coltype = self.model.getColumnType(self.currentcol) 832 if coltype == 'text' or coltype == 'number': 833 self.drawCellEntry(self.currentrow, self.currentcol) 834 return
835
836 - def handle_left_click(self, event):
837 """Respond to a single press""" 838 #which row and column is the click inside? 839 self.clearSelected() 840 self.allrows = False 841 rowclicked = self.get_row_clicked(event) 842 colclicked = self.get_col_clicked(event) 843 self.focus_set() 844 if self.mode == 'formula': 845 self.handleFormulaClick(rowclicked, colclicked) 846 return 847 if hasattr(self, 'cellentry'): 848 self.cellentry.destroy() 849 #ensure popup menus are removed if present 850 if hasattr(self, 'rightmenu'): 851 self.rightmenu.destroy() 852 if hasattr(self.tablecolheader, 'rightmenu'): 853 self.tablecolheader.rightmenu.destroy() 854 855 self.startrow = rowclicked 856 self.endrow = rowclicked 857 self.startcol = colclicked 858 self.endcol = colclicked 859 #reset multiple selection list 860 self.multiplerowlist=[] 861 self.multiplerowlist.append(rowclicked) 862 if 0 <= rowclicked < self.rows and 0 <= colclicked < self.cols: 863 self.setSelectedRow(rowclicked) 864 self.setSelectedCol(colclicked) 865 self.drawSelectedRect(self.currentrow, self.currentcol) 866 self.drawSelectedRow() 867 self.tablerowheader.drawSelectedRows(rowclicked) 868 coltype = self.model.getColumnType(colclicked) 869 if coltype == 'text' or coltype == 'number': 870 self.drawCellEntry(rowclicked, colclicked) 871 return
872
873 - def handle_left_release(self,event):
874 self.endrow = self.get_row_clicked(event) 875 return
876
877 - def handle_left_ctrl_click(self, event):
878 """Handle ctrl clicks for multiple row selections""" 879 rowclicked = self.get_row_clicked(event) 880 colclicked = self.get_col_clicked(event) 881 if 0 <= rowclicked < self.rows and 0 <= colclicked < self.cols: 882 if rowclicked not in self.multiplerowlist: 883 self.multiplerowlist.append(rowclicked) 884 else: 885 self.multiplerowlist.remove(rowclicked) 886 self.drawMultipleRows(self.multiplerowlist) 887 if colclicked not in self.multiplecollist: 888 self.multiplecollist.append(colclicked) 889 #print self.multiplecollist 890 self.drawMultipleCells() 891 return
892
893 - def handle_left_shift_click(self, event):
894 """Handle shift click, for selecting multiple rows""" 895 #Has same effect as mouse drag, so just use same method 896 self.handle_mouse_drag(event) 897 return
898
899 - def handle_mouse_drag(self, event):
900 """Handle mouse moved with button held down, multiple selections""" 901 if hasattr(self, 'cellentry'): 902 self.cellentry.destroy() 903 rowover = self.get_row_clicked(event) 904 colover = self.get_col_clicked(event) 905 if colover == None or rowover == None: 906 return 907 908 if rowover >= self.rows or self.startrow > self.rows: 909 return 910 else: 911 self.endrow = rowover 912 #do columns 913 if colover > self.cols or self.startcol > self.cols: 914 return 915 else: 916 self.endcol = colover 917 if self.endcol < self.startcol: 918 self.multiplecollist=range(self.endcol, self.startcol+1) 919 else: 920 self.multiplecollist=range(self.startcol, self.endcol+1) 921 #print self.multiplecollist 922 #draw the selected rows 923 if self.endrow != self.startrow: 924 if self.endrow < self.startrow: 925 self.multiplerowlist=range(self.endrow, self.startrow+1) 926 else: 927 self.multiplerowlist=range(self.startrow, self.endrow+1) 928 self.drawMultipleRows(self.multiplerowlist) 929 self.tablerowheader.drawSelectedRows(self.multiplerowlist) 930 #draw selected cells outline using row and col lists 931 #print self.multiplerowlist 932 self.drawMultipleCells() 933 else: 934 self.multiplerowlist = [] 935 self.multiplerowlist.append(self.currentrow) 936 if len(self.multiplecollist) >= 1: 937 self.drawMultipleCells() 938 self.delete('multiplesel') 939 #print self.multiplerowlist 940 return
941
942 - def handle_arrow_keys(self, event):
943 """Handle arrow keys press""" 944 #print event.keysym 945 946 row = self.get_row_clicked(event) 947 col = self.get_col_clicked(event) 948 x,y = self.getCanvasPos(self.currentrow, 0) 949 if x == None: 950 return 951 952 if event.keysym == 'Up': 953 if self.currentrow == 0: 954 return 955 else: 956 #self.yview('moveto', y) 957 #self.tablerowheader.yview('moveto', y) 958 self.currentrow = self.currentrow -1 959 elif event.keysym == 'Down': 960 if self.currentrow >= self.rows-1: 961 return 962 else: 963 #self.yview('moveto', y) 964 #self.tablerowheader.yview('moveto', y) 965 self.currentrow = self.currentrow +1 966 elif event.keysym == 'Right' or event.keysym == 'Tab': 967 if self.currentcol >= self.cols-1: 968 if self.currentrow < self.rows-1: 969 self.currentcol = 0 970 self.currentrow = self.currentrow +1 971 else: 972 return 973 else: 974 self.currentcol = self.currentcol +1 975 elif event.keysym == 'Left': 976 self.currentcol = self.currentcol -1 977 self.drawSelectedRect(self.currentrow, self.currentcol) 978 coltype = self.model.getColumnType(self.currentcol) 979 if coltype == 'text' or coltype == 'number': 980 self.delete('entry') 981 self.drawCellEntry(self.currentrow, self.currentcol) 982 return
983
984 - def handle_double_click(self, event):
985 """Do double click stuff. Selected row/cols will already have 986 been set with single click binding""" 987 #print 'double click' 988 row = self.get_row_clicked(event) 989 col = self.get_col_clicked(event) 990 #absrow = self.get_AbsoluteRow(row) 991 model=self.getModel() 992 cellvalue = model.getCellRecord(row, col) 993 if Formula.isFormula(cellvalue): 994 self.formula_Dialog(row, col, cellvalue) 995 #self.enterFormula(rowclicked, colclicked) 996 #self.drawCellEntry(self.currentrow, self.currentcol) 997 return
998
999 - def handle_right_click(self, event):
1000 """respond to a right click""" 1001 self.delete('tooltip') 1002 self.tablerowheader.clearSelected() 1003 if hasattr(self, 'rightmenu'): 1004 self.rightmenu.destroy() 1005 rowclicked = self.get_row_clicked(event) 1006 colclicked = self.get_col_clicked(event) 1007 if colclicked == None: 1008 self.rightmenu = self.popupMenu(event, outside=1) 1009 return 1010 1011 if (rowclicked in self.multiplerowlist or self.allrows == True) and colclicked in self.multiplecollist: 1012 self.rightmenu = self.popupMenu(event, rows=self.multiplerowlist, cols=self.multiplecollist) 1013 else: 1014 if 0 <= rowclicked < self.rows and 0 <= colclicked < self.cols: 1015 self.clearSelected() 1016 self.allrows = False 1017 self.setSelectedRow(rowclicked) 1018 self.setSelectedCol(colclicked) 1019 self.drawSelectedRect(self.currentrow, self.currentcol) 1020 self.drawSelectedRow() 1021 if self.isInsideTable(event.x,event.y) == 1: 1022 self.rightmenu = self.popupMenu(event,rows=self.multiplerowlist, cols=self.multiplecollist) 1023 else: 1024 self.rightmenu = self.popupMenu(event, outside=1) 1025 return
1026
1027 - def handle_motion(self, event):
1028 """Handle mouse motion on table""" 1029 self.delete('tooltip') 1030 row = self.get_row_clicked(event) 1031 col = self.get_col_clicked(event) 1032 if 0 <= row < self.rows and 0 <= col < self.cols: 1033 self.drawTooltip(row, col) 1034 1035 return
1036
1037 - def gotonextCell(self, event):
1038 """Move highlighted cell to next cell in row or a new col""" 1039 #print 'next' 1040 if hasattr(self, 'cellentry'): 1041 self.cellentry.destroy() 1042 self.currentcol=self.currentcol+1 1043 if self.currentcol >= self.cols-1: 1044 self.currentrow = self.currentrow +1 1045 self.currentcol = self.currentcol+1 1046 self.drawSelectedRect(self.currentrow, self.currentcol) 1047 return
1048
1049 - def movetoSelectedRow(self, row=None, recname=None):
1050 """Move to selected row, updating table""" 1051 row=self.model.getRecordIndex(recname) 1052 self.setSelectedRow(row) 1053 self.drawSelectedRow() 1054 x,y = self.getCanvasPos(row, 0) 1055 self.yview('moveto', y-0.01) 1056 self.tablecolheader.yview('moveto', y) 1057 return
1058
1059 - def handleFormulaClick(self, row, col):
1060 """Do a dialog for cell formula entry""" 1061 1062 model = self.getModel() 1063 cell = list(model.getRecColNames(row, col)) 1064 #absrow = self.get_AbsoluteRow(row) 1065 self.formulaText.insert(END, str(cell)) 1066 self.formulaText.focus_set() 1067 self.drawSelectedRect(row, col, color='red') 1068 return
1069
1070 - def formula_Dialog(self, row, col, currformula=None):
1071 """Formula dialog""" 1072 self.mode = 'formula' 1073 print self.mode 1074 #absrow = self.get_AbsoluteRow(row) 1075 x1,y1,x2,y2 = self.getCellCoords(row,col) 1076 w=300 1077 h=h=self.rowheight * 3 1078 def close(): 1079 if hasattr(self,'formulaWin'): 1080 self.delete('formulabox') 1081 self.mode = 'normal'
1082 def calculate(): 1083 #get text area contents and do formula 1084 f = self.formulaText.get(1.0, END) 1085 f = f.strip('\n') 1086 self.model.setFormulaAt(f,absrow,col) 1087 value = self.model.doFormula(f) 1088 color = self.model.getColorAt(absrow,col,'fg') 1089 self.drawText(row, col, value, color) 1090 close() 1091 self.mode = 'normal' 1092 return
1093 def clear(): 1094 self.formulaText.delete(1.0, END) 1095 1096 self.formulaFrame = Frame(width=w,height=h,bd=3,relief=RIDGE) 1097 self.formulaText = Text(self.formulaFrame, width=30, height=8, bg='white',relief=GROOVE) 1098 self.formulaText.pack(side=LEFT,padx=2,pady=2) 1099 if currformula != None: 1100 self.formulaText.insert(END, Formula.getFormula(currformula)) 1101 cancelbutton=Button(self.formulaFrame, text='Cancel', 1102 relief=GROOVE,bg='#99ccff',command=close) 1103 cancelbutton.pack(fill=BOTH,padx=2,pady=2) 1104 donebutton=Button(self.formulaFrame, text='Done', 1105 relief=GROOVE,bg='#99ccff',command=calculate) 1106 donebutton.pack(fill=BOTH,padx=2,pady=2) 1107 '''clrbutton=Button(self.formulaFrame, text='Clear', 1108 relief=GROOVE,bg='#99ccff',command=clear) 1109 clrbutton.pack(fill=BOTH,padx=2,pady=2) ''' 1110 #add to canvas 1111 self.formulaWin = self.create_window(x1+self.inset,y1+self.inset, 1112 width=w,height=h, 1113 window=self.formulaFrame,anchor='nw', 1114 tag='formulabox') 1115 self.formulaText.focus_set() 1116 return 1117
1118 - def convertFormulae(self, rows, cols=None):
1119 """Convert the formulas in the cells to their result values""" 1120 if len(self.multiplerowlist) == 0 or len(self.multiplecollist) == 0: 1121 return None 1122 1123 print rows, cols 1124 if cols == None: 1125 cols = range(self.cols) 1126 for r in rows: 1127 #absr=self.get_AbsoluteRow(r) 1128 for c in cols: 1129 val = self.model.getValueAt(r,c) 1130 self.model.setValueAt(val, r, c) 1131 return
1132
1133 - def paste(self, event=None):
1134 """Copy from clipboard""" 1135 print self.parentframe.clipboard_get() 1136 return
1137
1138 - def copyCell(self, rows, cols=None):
1139 """Copy cell contents to a temp internal clipboard""" 1140 row = rows[0]; col = cols[0] 1141 #absrow = self.get_AbsoluteRow(row) 1142 self.clipboard = copy.deepcopy(self.model.getCellRecord(row, col)) 1143 return
1144
1145 - def pasteCell(self, rows, cols=None):
1146 """Paste cell from internal clipboard""" 1147 row = rows[0]; col = cols[0] 1148 #absrow = self.get_AbsoluteRow(row) 1149 val = self.clipboard 1150 self.model.setValueAt(val, row, col) 1151 self.redrawTable() 1152 return
1153
1154 - def copyColumns(self):
1155 """Copy current selected cols""" 1156 M = self.model 1157 coldata = {} 1158 for col in self.multiplecollist: 1159 name = M.columnNames[col] 1160 coldata[name] = M.getColumnData(columnName=name) 1161 return coldata
1162
1163 - def pasteColumns(self, coldata):
1164 """Paste new cols, overwrites existing names""" 1165 M = self.model 1166 for name in coldata: 1167 if name not in M.columnNames: 1168 M.addColumn(name) 1169 for r in range(len(coldata[name])): 1170 val = coldata[name][r] 1171 col = M.columnNames.index(name) 1172 if r >= self.rows: 1173 break 1174 M.setValueAt(val, r, col) 1175 self.redrawTable() 1176 return coldata
1177 1178 # --- Some cell specific actions here --- 1179
1180 - def setcellColor(self, rows, cols=None, newColor=None, key=None, redraw=True):
1181 """Set the cell color for one or more cells and save it in the model color""" 1182 1183 model = self.getModel() 1184 if newColor == None: 1185 import tkColorChooser 1186 ctuple, newColor = tkColorChooser.askcolor(title='pick a color') 1187 if newColor == None: 1188 return 1189 1190 if type(rows) is IntType: 1191 x=rows 1192 rows=[] 1193 rows.append(x) 1194 if self.allrows == True: 1195 #we use all rows if the whole column has been selected 1196 rows = range(0,self.rows) 1197 if cols == None: 1198 cols = range(self.cols) 1199 for col in cols: 1200 for row in rows: 1201 #absrow = self.get_AbsoluteRow(row) 1202 model.setColorAt(row, col, color=newColor, key=key) 1203 #setcolor(absrow, col) 1204 if redraw == True: 1205 self.redrawTable() 1206 return
1207
1208 - def popupMenu(self, event, rows=None, cols=None, outside=None):
1209 """Add left and right click behaviour for canvas, should not have to override 1210 this function, it will take its values from defined dicts in constructor""" 1211 1212 defaultactions = {"Set Fill Color" : lambda : self.setcellColor(rows,cols,key='bg'), 1213 "Set Text Color" : lambda : self.setcellColor(rows,cols,key='fg'), 1214 "Copy" : lambda : self.copyCell(rows, cols), 1215 "Paste" : lambda : self.pasteCell(rows, cols), 1216 "Fill Down" : lambda : self.fillDown(rows, cols), 1217 "Fill Right" : lambda : self.fillAcross(cols, rows), 1218 "Add Row(s)" : lambda : self.addRows(), 1219 "Delete Row(s)" : lambda : self.deleteRow(), 1220 "View Record" : lambda : self.getRecordInfo(row), 1221 "Clear Data" : lambda : self.deleteCells(rows, cols), 1222 "Select All" : self.select_All, 1223 "Auto Fit Columns" : self.autoResizeColumns, 1224 "Filter Records" : self.showFilteringBar, 1225 "New": self.new, 1226 "Load": self.load, 1227 "Save": self.save, 1228 "Import text":self.importTable, 1229 "Export csv": self.exportTable, 1230 "Plot Selected" : self.plotSelected, 1231 "Plot Options" : self.plotSetup, 1232 "Export Table" : self.exportTable, 1233 "Preferences" : self.showtablePrefs, 1234 "Formulae->Value" : lambda : self.convertFormulae(rows, cols)} 1235 1236 main = ["Set Fill Color","Set Text Color","Copy", "Paste", "Fill Down","Fill Right", 1237 "Clear Data", "Add Row(s)" , "Delete Row(s)"] 1238 general = ["Select All", "Auto Fit Columns", "Filter Records", "Preferences"] 1239 filecommands = ['New','Load','Save','Import text','Export csv'] 1240 plotcommands = ['Plot Selected','Plot Options'] 1241 utilcommands = ["View Record", "Formulae->Value"] 1242 1243 def createSubMenu(parent, label, commands): 1244 menu = Menu(parent, tearoff = 0) 1245 popupmenu.add_cascade(label=label,menu=menu) 1246 for action in commands: 1247 menu.add_command(label=action, command=defaultactions[action]) 1248 return menu
1249 1250 def add_commands(fieldtype): 1251 """Add commands to popup menu for column type and specific cell""" 1252 functions = self.columnactions[fieldtype] 1253 for f in functions.keys(): 1254 func = getattr(self, functions[f]) 1255 popupmenu.add_command(label=f, command= lambda : func(row,col)) 1256 return 1257 1258 popupmenu = Menu(self, tearoff = 0) 1259 def popupFocusOut(event): 1260 popupmenu.unpost() 1261 1262 if outside == None: 1263 #if outside table, just show general items 1264 row = self.get_row_clicked(event) 1265 col = self.get_col_clicked(event) 1266 coltype = self.model.getColumnType(col) 1267 def add_defaultcommands(): 1268 """now add general actions for all cells""" 1269 for action in main: 1270 if action == 'Fill Down' and (rows == None or len(rows) <= 1): 1271 continue 1272 if action == 'Fill Right' and (cols == None or len(cols) <= 1): 1273 continue 1274 else: 1275 popupmenu.add_command(label=action, command=defaultactions[action]) 1276 return 1277 1278 if self.columnactions.has_key(coltype): 1279 add_commands(coltype) 1280 add_defaultcommands() 1281 1282 for action in general: 1283 popupmenu.add_command(label=action, command=defaultactions[action]) 1284 1285 popupmenu.add_separator() 1286 createSubMenu(popupmenu, 'File', filecommands) 1287 createSubMenu(popupmenu, 'Plot', plotcommands) 1288 if outside == None: 1289 createSubMenu(popupmenu, 'Utils', utilcommands) 1290 popupmenu.bind("<FocusOut>", popupFocusOut) 1291 popupmenu.focus_set() 1292 popupmenu.post(event.x_root, event.y_root) 1293 return popupmenu 1294 1295 # --- spreadsheet type functions --- 1296
1297 - def fillDown(self, rowlist, collist):
1298 """Fill down a column, or multiple columns""" 1299 model = self.model 1300 #absrow = self.get_AbsoluteRow(rowlist[0]) 1301 #remove first element as we don't want to overwrite it 1302 rowlist.remove(rowlist[0]) 1303 1304 #if this is a formula, we have to treat it specially 1305 for col in collist: 1306 val = self.model.getCellRecord(row, col) 1307 f=val #formula to copy 1308 i=1 1309 for r in rowlist: 1310 #absr = self.get_AbsoluteRow(r) 1311 if Formula.isFormula(f): 1312 newval = model.copyFormula(f, r, col, offset=i) 1313 model.setFormulaAt(newval, r, col) 1314 else: 1315 model.setValueAt(val, r, col) 1316 #print 'setting', val, 'at row', r 1317 i+=1 1318 1319 self.redrawTable() 1320 return
1321
1322 - def fillAcross(self, collist, rowlist):
1323 """Fill across a row, or multiple rows""" 1324 model = self.model 1325 #row = self.currentrow 1326 #absrow = self.get_AbsoluteRow(collist[0]) 1327 frstcol = collist[0] 1328 collist.remove(frstcol) 1329 1330 for row in rowlist: 1331 #absr = self.get_AbsoluteRow(row) 1332 val = self.model.getCellRecord(absr, frstcol) 1333 f=val #formula to copy 1334 i=1 1335 for c in collist: 1336 if Formula.isFormula(f): 1337 newval = model.copyFormula(f, r, c, offset=i, dim='x') 1338 model.setFormulaAt(newval, r, c) 1339 else: 1340 model.setValueAt(val, r, c) 1341 i+=1 1342 self.redrawTable() 1343 return
1344
1345 - def getSelectionValues(self):
1346 """Get values for current multiple cell selection""" 1347 if len(self.multiplerowlist) == 0 or len(self.multiplecollist) == 0: 1348 return None 1349 rows = self.multiplerowlist 1350 cols = self.multiplecollist 1351 model = self.model 1352 if len(rows)<1 or len(cols)<1: 1353 return None 1354 #if only one row selected we plot whole col 1355 if len(rows) == 1: 1356 rows = self.rowrange 1357 lists = [] 1358 1359 for c in cols: 1360 x=[] 1361 for r in rows: 1362 #absr = self.get_AbsoluteRow(r) 1363 val = model.getValueAt(r,c) 1364 if val == None or val == '': 1365 continue 1366 x.append(val) 1367 lists.append(x) 1368 return lists
1369
1370 - def plotSelected(self, graphtype='XY'):
1371 """Plot the selected data using pylab - if possible""" 1372 from Plot import pylabPlotter 1373 if not hasattr(self, 'pyplot'): 1374 self.pyplot = pylabPlotter() 1375 plotdata = [] 1376 for p in self.getSelectionValues(): 1377 x = [] 1378 fail = False 1379 for d in p: 1380 try: 1381 x.append(float(d)) 1382 except: 1383 fail = True 1384 continue 1385 if fail == False: 1386 plotdata.append(x) 1387 1388 pltlabels = self.getplotlabels() 1389 if len(pltlabels) > 2: 1390 self.pyplot.setDataSeries(pltlabels) 1391 self.pyplot.showlegend = 1 1392 self.pyplot.plotCurrent(data=plotdata, graphtype=graphtype) 1393 return
1394
1395 - def plotSetup(self):
1396 """Call pylab plot dialog setup, send data if we haven't already 1397 plotted""" 1398 from Plot import pylabPlotter 1399 if not hasattr(self, 'pyplot'): 1400 self.pyplot = pylabPlotter() 1401 plotdata = self.getSelectionValues() 1402 if not self.pyplot.hasData() and plotdata != None: 1403 print 'has data' 1404 plotdata = self.getSelectionValues() 1405 pltlabels = self.getplotlabels() 1406 self.pyplot.setDataSeries(pltlabels) 1407 self.pyplot.plotSetup(plotdata) 1408 else: 1409 self.pyplot.plotSetup() 1410 return
1411
1412 - def getplotlabels(self):
1413 """Get labels for plot series from col labels""" 1414 pltlabels = [] 1415 for col in self.multiplecollist: 1416 pltlabels.append(self.model.getColumnLabel(col)) 1417 return pltlabels
1418 1419 #--- Drawing stuff --- 1420
1421 - def drawGrid(self, startrow, endrow):
1422 """Draw the table grid lines""" 1423 self.delete('gridline','text') 1424 rows=len(self.rowrange) 1425 cols=self.cols 1426 w = self.cellwidth 1427 h = self.rowheight 1428 x_start=self.x_start 1429 y_start=self.y_start 1430 x_pos=x_start 1431 1432 if self.vertlines==1: 1433 for col in range(cols+1): 1434 x=self.col_positions[col] 1435 self.create_line(x,y_start,x,y_start+rows*h, tag='gridline', 1436 fill=self.grid_color, width=self.linewidth) 1437 if self.horizlines==1: 1438 for row in range(startrow, endrow+1): 1439 y_pos=y_start+row*h 1440 self.create_line(x_start,y_pos,self.tablewidth,y_pos, tag='gridline', 1441 fill=self.grid_color, width=self.linewidth) 1442 return
1443
1444 - def drawRowHeader(self):
1445 """User has clicked to select a cell""" 1446 self.delete('rowheader') 1447 x_start=self.x_start 1448 y_start=self.y_start 1449 h=self.rowheight 1450 rowpos=0 1451 for row in self.rowrange: 1452 x1,y1,x2,y2 = self.getCellCoords(rowpos,0) 1453 self.create_rectangle(0,y1,x_start-2,y2, 1454 fill='gray75', 1455 outline='white', 1456 width=1, 1457 tag='rowheader') 1458 self.create_text(x_start/2,y1+h/2, 1459 text=row+1, 1460 fill='black', 1461 font=self.thefont, 1462 tag='rowheader') 1463 rowpos+=1 1464 return
1465
1466 - def drawSelectedRect(self, row, col, color=None):
1467 """User has clicked to select a cell""" 1468 if col >= self.cols: 1469 return 1470 self.delete('currentrect') 1471 bg = self.selectedcolor 1472 if color == None: 1473 color = 'gray25' 1474 w=3 1475 x1,y1,x2,y2 = self.getCellCoords(row,col) 1476 rect = self.create_rectangle(x1+w/2,y1+w/2,x2-w/2,y2-w/2, 1477 fill=bg, 1478 outline=color, 1479 width=w, 1480 stipple='gray50', 1481 tag='currentrect') 1482 #self.lower('currentrect') 1483 #raise text above all 1484 self.lift('celltext'+str(col)+'_'+str(row)) 1485 return
1486
1487 - def drawRect(self, row, col, color=None, tag=None, delete=1):
1488 """Cell is colored""" 1489 if delete==1: 1490 self.delete('cellbg'+str(row)+str(col)) 1491 if color==None or color==self.cellbackgr: 1492 return 1493 else: 1494 bg=color 1495 if tag==None: 1496 recttag='fillrect' 1497 else: 1498 recttag=tag 1499 w=1 1500 x1,y1,x2,y2 = self.getCellCoords(row,col) 1501 rect = self.create_rectangle(x1+w/2,y1+w/2,x2-w/2,y2-w/2, 1502 fill=bg, 1503 outline=bg, 1504 width=w, 1505 tag=(recttag,'cellbg'+str(row)+str(col))) 1506 self.lower(recttag) 1507 return
1508
1509 - def drawCellEntry(self, row, col, text=None):
1510 """When the user single/double clicks on a text/number cell, bring up entry window""" 1511 1512 if self.editable == False: 1513 return 1514 #absrow = self.get_AbsoluteRow(row) 1515 h=self.rowheight 1516 model=self.getModel() 1517 cellvalue = self.model.getCellRecord(row, col) 1518 if Formula.isFormula(cellvalue): 1519 return 1520 else: 1521 text = self.model.getValueAt(row, col) 1522 x1,y1,x2,y2 = self.getCellCoords(row,col) 1523 w=x2-x1 1524 #Draw an entry window 1525 txtvar = StringVar() 1526 txtvar.set(text) 1527 def callback(e): 1528 value = txtvar.get() 1529 if value == '=': 1530 #do a dialog that gets the formula into a text area 1531 #then they can click on the cells they want 1532 #when done the user presses ok and its entered into the cell 1533 self.cellentry.destroy() 1534 #its all done here.. 1535 self.formula_Dialog(row, col) 1536 return 1537 1538 coltype = self.model.getColumnType(col) 1539 if coltype == 'number': 1540 sta = self.checkDataEntry(e) 1541 if sta == 1: 1542 model.setValueAt(value,row,col) 1543 elif coltype == 'text': 1544 model.setValueAt(value,row,col) 1545 1546 color = self.model.getColorAt(row,col,'fg') 1547 self.drawText(row, col, value, color, align=self.align) 1548 if e.keysym=='Return': 1549 self.delete('entry') 1550 #self.drawRect(row, col) 1551 #self.gotonextCell(e) 1552 return
1553 1554 self.cellentry=Entry(self.parentframe,width=20, 1555 textvariable=txtvar, 1556 bg=self.entrybackgr, 1557 relief=FLAT, 1558 takefocus=1, 1559 font=self.thefont) 1560 self.cellentry.icursor(END) 1561 self.cellentry.bind('<Return>', callback) 1562 self.cellentry.bind('<KeyRelease>', callback) 1563 self.cellentry.focus_set() 1564 self.entrywin=self.create_window(x1+self.inset,y1+self.inset, 1565 width=w-self.inset*2,height=h-self.inset*2, 1566 window=self.cellentry,anchor='nw', 1567 tag='entry') 1568 1569 return 1570
1571 - def checkDataEntry(self,event=None):
1572 """do validation checks on data entry in a widget""" 1573 #if user enters commas, change to points 1574 import re 1575 value=event.widget.get() 1576 if value!='': 1577 try: 1578 value=re.sub(',','.', value) 1579 value=float(value) 1580 1581 except ValueError: 1582 event.widget.configure(bg='red') 1583 return 0 1584 elif value == '': 1585 return 1 1586 return 1
1587
1588 - def drawText(self, row, col, celltxt, fgcolor=None, align=None):
1589 """Draw the text inside a cell area""" 1590 self.delete('celltext'+str(col)+'_'+str(row)) 1591 h=self.rowheight 1592 x1,y1,x2,y2 = self.getCellCoords(row,col) 1593 w=x2-x1 1594 wrap = False 1595 # If celltxt is a number then we make it a string 1596 if type(celltxt) is types.FloatType or type(celltxt) is types.IntType: 1597 celltxt=str(celltxt) 1598 length = len(celltxt) 1599 if length == 0: 1600 return 1601 #if cell width is less than x, print nothing 1602 if w<=10: 1603 return 1604 1605 if fgcolor == None or fgcolor == "None": 1606 fgcolor = 'black' 1607 if align == None: 1608 align = 'center' 1609 elif align == 'w': 1610 x1 = x1-w/2+1 1611 elif align == 'e': 1612 x1 = x1+w/2-1 1613 1614 if w < 15: 1615 celltxt = '.' 1616 else: 1617 fontsize = self.fontsize 1618 colname = self.model.getColumnName(col) 1619 #scaling between canvas and text normalised to about font 14 1620 scale = 8.5 * float(fontsize)/12 1621 size = length * scale 1622 if size > w: 1623 newlength = w / scale 1624 #print w, size, length, newlength 1625 celltxt = celltxt[0:int(math.floor(newlength))] 1626 1627 #if celltxt is dict then we are drawing a hyperlink 1628 if self.isLink(celltxt) == True: 1629 haslink=0 1630 linktext=celltxt['text'] 1631 if len(linktext) > w/scale or w<28: 1632 linktext=linktext[0:int(w/fontsize*1.2)-2]+'..' 1633 if celltxt['link']!=None and celltxt['link']!='': 1634 f,s = self.thefont 1635 linkfont = (f, s, 'underline') 1636 linkcolor='blue' 1637 haslink=1 1638 else: 1639 linkfont = self.thefont 1640 linkcolor=fgcolor 1641 1642 rect = self.create_text(x1+w/2,y1+h/2, 1643 text=linktext, 1644 fill=linkcolor, 1645 font=linkfont, 1646 tag=('text','hlink','celltext'+str(col)+'_'+str(row))) 1647 if haslink == 1: 1648 self.tag_bind(rect, '<Double-Button-1>', self.check_hyperlink) 1649 1650 #just normal text 1651 else: 1652 rect = self.create_text(x1+w/2,y1+h/2, 1653 text=celltxt, 1654 fill=fgcolor, 1655 font=self.thefont, 1656 anchor=align, 1657 tag=('text','celltext'+str(col)+'_'+str(row))) 1658 return
1659 1667
1668 - def drawSelectedRow(self):
1669 """Draw the highlight rect for the currently selected row""" 1670 self.delete('rowrect') 1671 row = self.currentrow 1672 x1,y1,x2,y2 = self.getCellCoords(row,0) 1673 x2 = self.tablewidth 1674 rect = self.create_rectangle(x1,y1,x2,y2, 1675 fill=self.rowselectedcolor, 1676 outline=self.rowselectedcolor, 1677 tag='rowrect') 1678 self.lower('rowrect') 1679 self.lower('fillrect') 1680 self.tablerowheader.drawSelectedRows(self.currentrow) 1681 return
1682
1683 - def drawSelectedCol(self, col=None, delete=1):
1684 """Draw an outline rect fot the current column selection""" 1685 if delete == 1: 1686 self.delete('colrect') 1687 if col == None: 1688 col=self.currentcol 1689 w=2 1690 x1,y1,x2,y2 = self.getCellCoords(0,col) 1691 y2 = self.rows * self.rowheight 1692 rect = self.create_rectangle(x1+w/2,y1+w/2,x2,y2+w/2, 1693 outline='blue',width=w, 1694 tag='colrect') 1695 return
1696
1697 - def drawMultipleRows(self, rowlist):
1698 """Draw more than one row selection""" 1699 self.delete('multiplesel') 1700 for r in rowlist: 1701 if r not in self.visiblerows or r > self.rows-1: 1702 continue 1703 x1,y1,x2,y2 = self.getCellCoords(r,0) 1704 x2 = self.tablewidth 1705 rect = self.create_rectangle(x1,y1,x2,y2, 1706 fill=self.multipleselectioncolor, 1707 outline=self.rowselectedcolor, 1708 tag=('multiplesel','rowrect')) 1709 self.lower('multiplesel') 1710 self.lower('fillrect') 1711 return
1712
1713 - def drawMultipleCells(self):
1714 """Draw an outline box for multiple cell selection""" 1715 self.delete('multicellrect') 1716 rows = self.multiplerowlist 1717 cols = self.multiplecollist 1718 w=2 1719 x1,y1,a,b = self.getCellCoords(rows[0],cols[0]) 1720 c,d,x2,y2 = self.getCellCoords(rows[len(rows)-1],cols[len(cols)-1]) 1721 rect = self.create_rectangle(x1+w/2,y1+w/2,x2,y2, 1722 outline='blue',width=w,activefill='red',activestipple='gray25', 1723 tag='multicellrect') 1724 1725 return
1726
1727 - def drawTooltip(self, row, col):
1728 """Draw a tooltip showing contents of cell""" 1729 1730 #absrow = self.get_AbsoluteRow(row) 1731 x1,y1,x2,y2 = self.getCellCoords(row,col) 1732 w=x2-x1 1733 text = self.model.getValueAt(row,col) 1734 if isinstance(text, dict): 1735 if text.has_key('link'): 1736 text = text['link'] 1737 1738 1739 # If text is a number we make it a string 1740 if type(text) is types.FloatType or type is types.IntType: 1741 text=str(text) 1742 if text == NoneType or text == '' or len(str(text))<=3: 1743 return 1744 1745 sfont = tkFont.Font (family='Arial', size=12,weight='bold') 1746 obj = self.create_text(x1+w/1.5,y2,text=text, 1747 anchor='w', 1748 font=sfont,tag='tooltip') 1749 1750 box = self.bbox(obj) 1751 x1=box[0]-1 1752 y1=box[1]-1 1753 x2=box[2]+1 1754 y2=box[3]+1 1755 1756 rect = self.create_rectangle(x1+1,y1+1,x2+1,y2+1,tag='tooltip',fill='black') 1757 rect2 = self.create_rectangle(x1,y1,x2,y2,tag='tooltip',fill='lightyellow') 1758 self.lift(obj) 1759 return
1760
1761 - def setcellbackgr(self):
1762 clr = self.getaColor(self.cellbackgr) 1763 if clr != None: 1764 self.cellbackgr = clr 1765 return
1766
1767 - def setgrid_color(self):
1768 clr = self.getaColor(self.grid_color) 1769 if clr != None: 1770 self.grid_color = clr 1771 1772 return
1773
1774 - def setrowselectedcolor(self):
1775 clr = self.getaColor(self.rowselectedcolor) 1776 if clr != None: 1777 self.rowselectedcolor = clr 1778 return
1779
1780 - def getaColor(self, oldcolor):
1781 import tkColorChooser 1782 ctuple, newcolor = tkColorChooser.askcolor(title='pick a color', initialcolor=oldcolor, 1783 parent=self.parentframe) 1784 if ctuple == None: 1785 return None 1786 return str(newcolor)
1787 1788 #--- Preferences stuff --- 1789
1790 - def showtablePrefs(self, prefs=None):
1791 """Show table options dialog using an instance of prefs""" 1792 #self.prefs = prefs 1793 if self.prefs == None: 1794 self.loadPrefs() 1795 self.prefswindow=Toplevel() 1796 x,y,w,h = self.getGeometry(self.master) 1797 self.prefswindow.geometry('+%s+%s' %(x+w/2,y+h/2)) 1798 self.prefswindow.title('Preferences') 1799 self.prefswindow.resizable(width=FALSE, height=FALSE) 1800 1801 frame1=Frame(self.prefswindow) 1802 frame1.pack(side=LEFT) 1803 frame2=Frame(self.prefswindow) 1804 frame2.pack() 1805 def close_prefsdialog(): 1806 self.prefswindow.destroy()
1807 row=0 1808 Checkbutton(frame1, text="Show horizontal lines", variable=self.horizlinesvar, 1809 onvalue=1, offvalue=0).grid(row=row,column=0, columnspan=2, sticky='news') 1810 row=row+1 1811 Checkbutton(frame1, text="Show vertical lines", variable=self.vertlinesvar, 1812 onvalue=1, offvalue=0).grid(row=row,column=0, columnspan=2, sticky='news') 1813 row=row+1 1814 Checkbutton(frame1, text="Alternate Row Color", variable=self.alternaterowsvar, 1815 onvalue=1, offvalue=0).grid(row=row,column=0, columnspan=2, sticky='news') 1816 row=row+1 1817 lblrowheight=Label(frame1,text='Row Height:') 1818 lblrowheight.grid(row=row,column=0,padx=3,pady=2) 1819 rowheightentry=Scale(frame1,from_=12,to=50,resolution=1,orient='horizontal', 1820 relief='ridge',variable=self.rowheightvar) 1821 rowheightentry.grid(row=row,column=1,padx=3,pady=2) 1822 row=row+1 1823 lblcellwidth=Label(frame1,text='Cell Width:') 1824 lblcellwidth.grid(row=row,column=0,padx=3,pady=2) 1825 cellwidthentry=Scale(frame1,from_=20,to=500,resolution=10,orient='horizontal', 1826 relief='ridge',variable=self.cellwidthvar) 1827 cellwidthentry.grid(row=row,column=1,padx=3,pady=2) 1828 row=row+1 1829 1830 lbllinewidth=Label(frame1,text='Line Width:') 1831 lbllinewidth.grid(row=row,column=0,padx=3,pady=2) 1832 linewidthentry=Scale(frame1,from_=0,to=10,resolution=1,orient='horizontal', 1833 relief='ridge',variable=self.linewidthvar) 1834 linewidthentry.grid(row=row,column=1,padx=3,pady=2) 1835 row=row+1 1836 1837 rowhdrwidth=Label(frame1,text='Row Header Width:') 1838 rowhdrwidth.grid(row=row,column=0,padx=3,pady=2) 1839 rowhdrentry=Scale(frame1,from_=0,to=300,resolution=10,orient='horizontal', 1840 relief='ridge',variable=self.rowheaderwidthvar) 1841 rowhdrentry.grid(row=row,column=1,padx=3,pady=2) 1842 row=row+1 1843 1844 #fonts 1845 fts = self.getFonts() 1846 1847 def setFont(): 1848 self.thefont = self.fontbox.getcurselection() 1849 return 1850 1851 import Pmw 1852 self.fontbox = Pmw.ScrolledListBox(frame2, 1853 items=(fts), 1854 labelpos='w', 1855 label_text='Font:', 1856 listbox_height = 6, 1857 selectioncommand = setFont, 1858 usehullsize = 1, 1859 hull_width = 200, 1860 hull_height = 80) 1861 self.fontbox.setvalue(self.prefs.get('celltextfont')) 1862 self.fontbox.grid(row=row,column=0, columnspan=2, sticky='nes', padx=3,pady=2) 1863 row=row+1 1864 1865 lblfontsize=Label(frame2,text='Text Size:') 1866 lblfontsize.grid(row=row,column=0,padx=3,pady=2) 1867 fontsizeentry=Scale(frame2,from_=6,to=50,resolution=1,orient='horizontal', 1868 relief='ridge',variable=self.celltextsizevar) 1869 1870 fontsizeentry.grid(row=row,column=1, sticky='wens',padx=3,pady=2) 1871 row=row+1 1872 1873 #cell alignment 1874 lbl=Label(frame2,text='Alignment:') 1875 lbl.grid(row=row,column=0,padx=3,pady=2) 1876 alignentry_button=Menubutton(frame2,textvariable=self.cellalignvar, 1877 relief=RAISED,width=16) 1878 alignentry_menu=Menu(alignentry_button,tearoff=0) 1879 alignentry_button['menu'] = alignentry_menu 1880 alignments=['center','w','e'] 1881 for text in alignments: 1882 alignentry_menu.add_radiobutton(label=text, 1883 variable=self.cellalignvar, 1884 value=text, 1885 indicatoron=1) 1886 alignentry_button.grid(row=row,column=1, sticky='nes', padx=3,pady=2) 1887 row=row+1 1888 1889 #colors 1890 cellbackgrbutton = Button(frame2, text='table background', bg=self.cellbackgr, 1891 relief='groove', command=self.setcellbackgr) 1892 cellbackgrbutton.grid(row=row,column=0,columnspan=2, sticky='news',padx=3,pady=2) 1893 row=row+1 1894 grid_colorbutton = Button(frame2, text='grid color', bg=self.grid_color, 1895 foreground='black', highlightcolor='white', 1896 relief='groove', command=self.setgrid_color) 1897 grid_colorbutton.grid(row=row,column=0,columnspan=2, sticky='news',padx=3,pady=2) 1898 row=row+1 1899 rowselectedcolorbutton = Button(frame2, text='row highlight color', bg=self.rowselectedcolor, 1900 foreground='black', highlightcolor='white', 1901 relief='groove', command=self.setrowselectedcolor) 1902 rowselectedcolorbutton.grid(row=row,column=0,columnspan=2, sticky='news',padx=3,pady=2) 1903 row=row+1 1904 1905 frame=Frame(self.prefswindow) 1906 frame.pack() 1907 # Apply Button 1908 b = Button(frame, text="Apply Settings", command=self.applyPrefs) 1909 b.grid(row=row,column=1,columnspan=2,sticky='news',padx=4,pady=4) 1910 1911 # Close button 1912 c=Button(frame,text='Close', command=close_prefsdialog) 1913 c.grid(row=row,column=0,sticky='news',padx=4,pady=4) 1914 self.prefswindow.focus_set() 1915 self.prefswindow.grab_set() 1916 self.prefswindow.wait_window() 1917 return self.prefswindow 1918
1919 - def getFonts(self):
1920 fonts = set(list(tkFont.families())) 1921 fonts = sorted(list(fonts)) 1922 return fonts
1923
1924 - def loadPrefs(self, prefs=None):
1925 """Load table specific prefs from the prefs instance used 1926 if they are not present, create them.""" 1927 1928 if prefs==None: 1929 prefs=Preferences('Table',{'check_for_update':1}) 1930 self.prefs = prefs 1931 defaultprefs = {'horizlines':self.horizlines, 'vertlines':self.vertlines, 1932 'alternaterows':self.alternaterows, 1933 'rowheight':self.rowheight, 1934 'cellwidth':120, 1935 'autoresizecols': 0, 1936 'align': 'center', 1937 'celltextsize':11, 'celltextfont':'Arial', 1938 'cellbackgr': self.cellbackgr, 'grid_color': self.grid_color, 1939 'linewidth' : self.linewidth, 1940 'rowselectedcolor': self.rowselectedcolor, 1941 'rowheaderwidth': self.rowheaderwidth} 1942 1943 for prop in defaultprefs.keys(): 1944 try: 1945 self.prefs.get(prop); 1946 except: 1947 self.prefs.set(prop, defaultprefs[prop]) 1948 self.defaultprefs = defaultprefs 1949 1950 #Create tkvars for dialog 1951 self.rowheightvar = IntVar() 1952 self.rowheightvar.set(self.prefs.get('rowheight')) 1953 self.rowheight = self.rowheightvar.get() 1954 self.cellwidthvar = IntVar() 1955 self.cellwidthvar.set(self.prefs.get('cellwidth')) 1956 self.cellwidth = self.cellwidthvar.get() 1957 self.cellalignvar = StringVar() 1958 self.cellalignvar.set(self.prefs.get('align')) 1959 self.align = self.cellalignvar.get() 1960 self.linewidthvar = IntVar() 1961 self.linewidthvar.set(self.prefs.get('linewidth')) 1962 self.horizlinesvar = IntVar() 1963 self.horizlinesvar.set(self.prefs.get('horizlines')) 1964 self.vertlinesvar = IntVar() 1965 self.vertlinesvar.set(self.prefs.get('vertlines')) 1966 self.alternaterowsvar = IntVar() 1967 self.alternaterowsvar.set(self.prefs.get('alternaterows')) 1968 self.celltextsizevar = IntVar() 1969 self.celltextsizevar.set(self.prefs.get('celltextsize')) 1970 self.cellbackgr = self.prefs.get('cellbackgr') 1971 self.grid_color = self.prefs.get('grid_color') 1972 self.rowselectedcolor = self.prefs.get('rowselectedcolor') 1973 self.fontsize = self.celltextsizevar.get() 1974 self.thefont = (self.prefs.get('celltextfont'), self.prefs.get('celltextsize')) 1975 self.rowheaderwidthvar = IntVar() 1976 self.rowheaderwidthvar.set(self.prefs.get('rowheaderwidth')) 1977 self.rowheaderwidth = self.rowheaderwidthvar.get() 1978 return
1979
1980 - def savePrefs(self):
1981 """Save and set the prefs""" 1982 try: 1983 self.prefs.set('horizlines', self.horizlinesvar.get()) 1984 self.horizlines = self.horizlinesvar.get() 1985 self.prefs.set('vertlines', self.vertlinesvar.get()) 1986 self.vertlines = self.vertlinesvar.get() 1987 self.prefs.set('alternaterows', self.alternaterowsvar.get()) 1988 self.alternaterows = self.alternaterowsvar.get() 1989 self.prefs.set('rowheight', self.rowheightvar.get()) 1990 self.rowheight = self.rowheightvar.get() 1991 self.prefs.set('cellwidth', self.cellwidthvar.get()) 1992 self.cellwidth = self.cellwidthvar.get() 1993 self.prefs.set('align', self.cellalignvar.get()) 1994 self.align = self.cellalignvar.get() 1995 self.prefs.set('linewidth', self.linewidthvar.get()) 1996 self.linewidth = self.linewidthvar.get() 1997 self.prefs.set('celltextsize', self.celltextsizevar.get()) 1998 self.prefs.set('celltextfont', self.fontbox.getcurselection()) 1999 self.prefs.set('cellbackgr', self.cellbackgr) 2000 self.prefs.set('grid_color', self.grid_color) 2001 self.prefs.set('rowselectedcolor', self.rowselectedcolor) 2002 self.prefs.set('rowheaderwidth', self.rowheaderwidth) 2003 self.rowheaderwidth = self.rowheaderwidthvar.get() 2004 self.thefont = (self.prefs.get('celltextfont'), self.prefs.get('celltextsize')) 2005 self.fontsize = self.prefs.get('celltextsize') 2006 2007 except ValueError: 2008 pass 2009 self.prefs.save_prefs() 2010 return
2011
2012 - def applyPrefs(self):
2013 """Apply prefs to the table by redrawing""" 2014 self.savePrefs() 2015 self.redrawTable() 2016 return
2017
2018 - def AskForColorButton(self, frame, text, func):
2019 def SetColor(): 2020 import tkColorChooser 2021 ctuple, variable = tkColorChooser.askcolor(title='pick a color', 2022 initialcolor=self.cellbackgr) 2023 2024 return
2025 bgcolorbutton = Button(frame, text=text,command=SetColor) 2026 return bgcolorbutton 2027 2041
2042 - def show_progressbar(self,message=None):
2043 """Show progress bar window for loading of data""" 2044 progress_win=Toplevel() # Open a new window 2045 progress_win.title("Please Wait") 2046 #progress_win.geometry('+%d+%d' %(self.parentframe.rootx+200,self.parentframe.rooty+200)) 2047 #force on top 2048 progress_win.grab_set() 2049 progress_win.transient(self.parentframe) 2050 if message==None: 2051 message='Working' 2052 lbl = Label(progress_win,text=message,font='Arial 16') 2053 2054 lbl.grid(row=0,column=0,columnspan=2,sticky='news',padx=6,pady=4) 2055 progrlbl = Label(progress_win,text='Progress:') 2056 progrlbl.grid(row=1,column=0,sticky='news',padx=2,pady=4) 2057 import ProgressBar 2058 self.bar = ProgressBar.ProgressBar(progress_win) 2059 self.bar.frame.grid(row=1,column=1,columnspan=2,padx=2,pady=4) 2060 2061 return progress_win
2062
2063 - def updateModel(self, model):
2064 """Call this method to update the table model""" 2065 self.model = model 2066 self.rows = self.model.getRowCount() 2067 self.cols = self.model.getColumnCount() 2068 self.tablewidth = (self.cellwidth)*self.cols 2069 self.tablecolheader = ColumnHeader(self.parentframe, self) 2070 self.tablerowheader = RowHeader(self.parentframe, self) 2071 self.createTableFrame() 2072 return
2073
2074 - def new(self):
2075 """Clears all the data and makes a new table""" 2076 from Dialogs import MultipleValDialog 2077 mpDlg = MultipleValDialog(title='Create new table', 2078 initialvalues=(10, 4), 2079 labels=('rows','columns'), 2080 types=('int','int'), 2081 parent=self.parentframe) 2082 2083 if mpDlg.result == True: 2084 rows = mpDlg.results[0] 2085 cols = mpDlg.results[1] 2086 model = TableModel(rows=rows,columns=cols) 2087 self.updateModel(model) 2088 return
2089
2090 - def load(self, filename=None):
2091 """load from a file""" 2092 if filename == None: 2093 filename = tkFileDialog.askopenfilename(parent=self.master, 2094 defaultextension='.table', 2095 initialdir=os.getcwd(), 2096 filetypes=[("pickle","*.table"), 2097 ("All files","*.*")]) 2098 if not os.path.exists(filename): 2099 print 'file does not exist' 2100 return 2101 if filename: 2102 self.model.load(filename) 2103 self.redrawTable() 2104 return
2105
2106 - def save(self, filename=None):
2107 """Save model to pickle file""" 2108 if filename == None: 2109 filename = tkFileDialog.asksaveasfilename(parent=self.master, 2110 defaultextension='.table', 2111 initialdir=os.getcwd(), 2112 filetypes=[("pickle","*.table"), 2113 ("All files","*.*")]) 2114 if filename: 2115 self.model.save(filename) 2116 return
2117
2118 - def importTable(self):
2119 """Import from csv file""" 2120 from Tables_IO import TableImporter 2121 importer = TableImporter() 2122 importdialog = importer.import_Dialog(self.master) 2123 self.master.wait_window(importdialog) 2124 model = TableModel() 2125 model.importDict(importer.data) 2126 self.updateModel(model) 2127 return
2128
2129 - def exportTable(self, filename=None):
2130 """Do a simple export of the cell contents to csv""" 2131 from Tables_IO import TableExporter 2132 exporter = TableExporter() 2133 exporter.ExportTableData(self) 2134 return
2135 2136 @classmethod
2137 - def checkOSType(cls):
2138 """Check the OS we are in""" 2139 ostyp='' 2140 var_s=['OSTYPE','OS'] 2141 for var in var_s: 2142 if os.environ.has_key(var): 2143 ostyp=string.lower(os.environ[var]) 2144 2145 ostyp=ostyp.lower() 2146 if ostyp.find('windows')!=-1: 2147 ostyp='windows' 2148 elif ostyp.find('darwin')!=-1 or ostyp.find('apple')!=-1: 2149 ostyp='mac' 2150 elif ostyp.find('linux')!=-1: 2151 ostyp='linux' 2152 else: 2153 ostyp='unknown' 2154 try: 2155 info=os.uname() 2156 except: 2157 pass 2158 ostyp=info[0].lower() 2159 if ostyp.find('darwin')!=-1: 2160 ostyp='mac' 2161 return ostyp
2162
2163 - def getGeometry(self, frame):
2164 """Get frame geometry""" 2165 return frame.winfo_rootx(), frame.winfo_rooty(), frame.winfo_width(), frame.winfo_height()
2166
2167 -class ColumnHeader(Canvas):
2168 """Class that takes it's size and rendering from a parent table 2169 and column names from the table model.""" 2170
2171 - def __init__(self, parent=None, table=None):
2172 Canvas.__init__(self, parent, bg='gray25', width=500, height=20) 2173 self.thefont='Arial 14' 2174 if table != None: 2175 self.table = table 2176 self.height = 20 2177 self.model = self.table.getModel() 2178 self.config(width=self.table.width) 2179 #self.colnames = self.model.columnNames 2180 self.columnlabels = self.model.columnlabels 2181 self.bind('<Button-1>',self.handle_left_click) 2182 self.bind("<ButtonRelease-1>", self.handle_left_release) 2183 self.bind('<B1-Motion>', self.handle_mouse_drag) 2184 self.bind('<Motion>', self.handle_mouse_move) 2185 self.bind('<Shift-Button-1>', self.handle_left_shift_click) 2186 if self.table.ostyp=='mac': 2187 #For mac we bind Shift, left-click to right click 2188 self.bind("<Button-2>", self.handle_right_click) 2189 self.bind('<Shift-Button-1>',self.handle_right_click) 2190 else: 2191 self.bind("<Button-3>", self.handle_right_click) 2192 self.thefont = self.table.thefont 2193 return
2194
2195 - def redraw(self):
2196 cols = self.model.getColumnCount() 2197 self.tablewidth=self.table.tablewidth 2198 self.configure(scrollregion=(0,0, self.table.tablewidth+self.table.x_start, self.height)) 2199 self.delete('gridline','text') 2200 self.delete('rect') 2201 self.atdivider = None 2202 2203 h=self.height 2204 x_start=self.table.x_start 2205 if cols == 0: 2206 return 2207 for col in self.table.visiblecols: 2208 colname=self.model.columnNames[col] 2209 if not self.model.columnlabels.has_key(colname): 2210 self.model.columnlabels[colname]=colname 2211 collabel = self.model.columnlabels[colname] 2212 if self.model.columnwidths.has_key(colname): 2213 w = self.model.columnwidths[colname] 2214 else: 2215 w = self.table.cellwidth 2216 x=self.table.col_positions[col] 2217 2218 if len(collabel)>w/10: 2219 collabel=collabel[0:int(w)/12]+'.' 2220 line = self.create_line(x, 0, x, h, tag=('gridline', 'vertline'), 2221 fill='white', width=2) 2222 2223 self.create_text(x+w/2,h/2, 2224 text=collabel, 2225 fill='white', 2226 font=self.thefont, 2227 tag='text') 2228 2229 2230 x=self.table.col_positions[col+1] 2231 self.create_line(x,0, x,h, tag='gridline', 2232 fill='white', width=2) 2233 2234 return
2235
2236 - def handle_left_click(self,event):
2237 """Does cell selection when mouse is clicked on canvas""" 2238 self.delete('rect') 2239 self.table.delete('entry') 2240 self.table.delete('multicellrect') 2241 colclicked = self.table.get_col_clicked(event) 2242 if colclicked == None: 2243 return 2244 #set all rows selected 2245 self.table.allrows = True 2246 self.table.setSelectedCol(colclicked) 2247 2248 if self.atdivider == 1: 2249 return 2250 self.drawRect(self.table.currentcol) 2251 #also draw a copy of the rect to be dragged 2252 self.draggedcol = None 2253 self.drawRect(self.table.currentcol, tag='dragrect', 2254 color='red', outline='white') 2255 if hasattr(self, 'rightmenu'): 2256 self.rightmenu.destroy() 2257 #finally, draw the selected col on the table 2258 self.table.drawSelectedCol() 2259 return
2260
2261 - def handle_left_release(self,event):
2262 """When mouse released implement resize or col move""" 2263 self.delete('dragrect') 2264 if self.atdivider == 1: 2265 #col = self.table.get_col_clicked(event) 2266 x=int(self.canvasx(event.x)) 2267 col = self.table.currentcol 2268 x1,y1,x2,y2 = self.table.getCellCoords(0,col) 2269 newwidth=x - x1 2270 if newwidth < 5: 2271 newwidth=5 2272 self.table.resizeColumn(col, newwidth) 2273 self.table.delete('resizeline') 2274 self.delete('resizeline') 2275 self.delete('resizesymbol') 2276 self.atdivider = 0 2277 return 2278 self.delete('resizesymbol') 2279 #move column 2280 if self.draggedcol != None and self.table.currentcol != self.draggedcol: 2281 self.model.moveColumn(self.table.currentcol, self.draggedcol) 2282 self.table.setSelectedCol(self.draggedcol) 2283 self.table.redrawTable() 2284 self.table.drawSelectedCol(self.table.currentcol) 2285 self.drawRect(self.table.currentcol) 2286 return
2287
2288 - def handle_mouse_drag(self, event):
2289 """Handle column drag, will be either to move cols or resize""" 2290 x=int(self.canvasx(event.x)) 2291 if self.atdivider == 1: 2292 self.table.delete('resizeline') 2293 self.delete('resizeline') 2294 self.table.create_line(x, 0, x, self.table.rowheight*self.table.rows, 2295 width=2, fill='gray', tag='resizeline') 2296 self.create_line(x, 0, x, self.height, 2297 width=2, fill='gray', tag='resizeline') 2298 return 2299 else: 2300 w = self.table.cellwidth 2301 self.draggedcol = self.table.get_col_clicked(event) 2302 x1, y1, x2, y2 = self.coords('dragrect') 2303 x=int(self.canvasx(event.x)) 2304 y = self.canvasy(event.y) 2305 self.move('dragrect', x-x1-w/2, 0) 2306 2307 return
2308
2309 - def within(self, val, l, d):
2310 """Utility funtion to see if val is within d of any 2311 items in the list l""" 2312 for v in l: 2313 if abs(val-v) <= d: 2314 return 1 2315 return 0
2316
2317 - def handle_mouse_move(self, event):
2318 """Handle mouse moved in header, if near divider draw resize symbol""" 2319 self.delete('resizesymbol') 2320 w=self.table.cellwidth 2321 h=self.height 2322 x_start=self.table.x_start 2323 #x = event.x 2324 x=int(self.canvasx(event.x)) 2325 if x > self.tablewidth+w: 2326 return 2327 #if event x is within x pixels of divider, draw resize symbol 2328 if x!=x_start and self.within(x, self.table.col_positions, 4): 2329 col = self.table.get_col_clicked(event) 2330 if col == None: 2331 return 2332 self.draw_resize_symbol(col) 2333 self.atdivider = 1 2334 else: 2335 self.atdivider = 0 2336 return
2337
2338 - def handle_right_click(self, event):
2339 """respond to a right click""" 2340 self.handle_left_click(event) 2341 self.rightmenu = self.popupMenu(event) 2342 return
2343
2344 - def handle_right_release(self, event):
2345 self.rightmenu.destroy() 2346 return
2347
2348 - def handle_left_shift_click(self, event):
2349 """Handle shift click, for selecting multiple cols""" 2350 self.table.delete('colrect') 2351 self.delete('rect') 2352 currcol = self.table.currentcol 2353 colclicked = self.table.get_col_clicked(event) 2354 if colclicked > currcol: 2355 self.table.multiplecollist = range(currcol, colclicked+1) 2356 elif colclicked < currcol: 2357 self.table.multiplecollist = range(colclicked, currcol+1) 2358 else: 2359 return 2360 2361 #print self.table.multiplecollist 2362 for c in self.table.multiplecollist: 2363 self.drawRect(c, delete=0) 2364 self.table.drawSelectedCol(c, delete=0) 2365 return
2366
2367 - def popupMenu(self, event):
2368 """Add left and right click behaviour for column header""" 2369 colname = self.model.columnNames[self.table.currentcol] 2370 collabel = self.model.columnlabels[colname] 2371 currcol = self.table.currentcol 2372 popupmenu = Menu(self, tearoff = 0) 2373 def popupFocusOut(event): 2374 popupmenu.unpost()
2375 popupmenu.add_command(label="Rename Column", command=self.relabel_Column) 2376 popupmenu.add_command(label="Sort by "+ collabel, command=lambda : self.table.sortTable(currcol)) 2377 popupmenu.add_command(label="Sort by "+ collabel +' (descending)', command=lambda : self.table.sortTable(currcol,reverse=1)) 2378 popupmenu.add_command(label="Delete This Column", command=self.table.deleteColumn) 2379 popupmenu.add_command(label="Add New Column", command=self.table.addColumn) 2380 2381 popupmenu.bind("<FocusOut>", popupFocusOut) 2382 #self.bind("<Button-3>", popupFocusOut) 2383 popupmenu.focus_set() 2384 popupmenu.post(event.x_root, event.y_root) 2385 return popupmenu
2386
2387 - def relabel_Column(self):
2388 col=self.table.currentcol 2389 ans = tkSimpleDialog.askstring("New column name?", "Enter new name:") 2390 if ans !=None: 2391 if ans == '': 2392 tkMessageBox.showwarning("Error", "Name should not be blank.") 2393 return 2394 else: 2395 self.model.relabel_Column(col, ans) 2396 self.redraw() 2397 return
2398
2399 - def draw_resize_symbol(self, col):
2400 """Draw a symbol to show that col can be resized when mouse here""" 2401 self.delete('resizesymbol') 2402 w=self.table.cellwidth 2403 h=self.height 2404 #if x_pos > self.tablewidth: 2405 # return 2406 wdth=1 2407 hfac1=0.2 2408 hfac2=0.4 2409 x_start=self.table.x_start 2410 x1,y1,x2,y2 = self.table.getCellCoords(0,col) 2411 2412 self.create_polygon(x2-3,h/4, x2-10,h/2, x2-3,h*3/4, tag='resizesymbol', 2413 fill='white', outline='gray', width=wdth) 2414 self.create_polygon(x2+2,h/4, x2+10,h/2, x2+2,h*3/4, tag='resizesymbol', 2415 fill='white', outline='gray', width=wdth) 2416 return
2417
2418 - def drawRect(self,col, tag=None, color=None, outline=None, delete=1):
2419 """User has clicked to select a col""" 2420 if tag==None: 2421 tag='rect' 2422 if color==None: 2423 color='#0099CC' 2424 if outline==None: 2425 outline='gray25' 2426 if delete == 1: 2427 self.delete(tag) 2428 w=2 2429 x1,y1,x2,y2 = self.table.getCellCoords(0,col) 2430 rect = self.create_rectangle(x1,y1-w,x2,self.height, 2431 fill=color, 2432 outline=outline, 2433 width=w, 2434 stipple='gray50', 2435 tag=tag) 2436 self.lower(tag) 2437 return
2438
2439 -class RowHeader(Canvas):
2440 """Class that displays the row headings on the table 2441 takes it's size and rendering from the parent table 2442 This also handles row/record selection as opposed to cell 2443 selection"""
2444 - def __init__(self, parent=None, table=None, width=40):
2445 Canvas.__init__(self, parent, bg='gray75', width=width, height=None) 2446 2447 if table != None: 2448 self.table = table 2449 self.width = width 2450 self.x_start = 0 2451 self.inset = 1 2452 self.config(height = self.table.height) 2453 self.startrow = self.endrow = None 2454 self.model = self.table.getModel() 2455 self.bind('<Button-1>',self.handle_left_click) 2456 self.bind("<ButtonRelease-1>", self.handle_left_release) 2457 self.bind("<Control-Button-1>", self.handle_left_ctrl_click) 2458 self.bind('<Button-3>',self.handle_right_click) 2459 self.bind('<B1-Motion>', self.handle_mouse_drag) 2460 #self.bind('<Shift-Button-1>', self.handle_left_shift_click) 2461 return
2462
2463 - def redraw(self, align='center', showkeys=False):
2464 """Redraw row header""" 2465 2466 self.height = self.table.rowheight * self.table.rows+10 2467 self.configure(scrollregion=(0,0, self.width, self.height)) 2468 self.delete('rowheader','text') 2469 self.delete('rect') 2470 w = float(self.width) 2471 h = self.table.rowheight 2472 x = self.x_start+w/2 2473 if align == 'w': 2474 x = x-w/2+3 2475 elif align == 'e': 2476 x = x+w/2-3 2477 for row in self.table.visiblerows: 2478 if showkeys == True: 2479 text = self.model.getRecName(row) 2480 else: 2481 text = row+1 2482 x1,y1,x2,y2 = self.table.getCellCoords(row,0) 2483 self.create_rectangle(0,y1,w-1,y2, 2484 fill='gray75', 2485 outline='white', 2486 width=1, 2487 tag='rowheader') 2488 self.create_text(x,y1+h/2, 2489 text=text, 2490 fill='black', 2491 font=self.table.thefont, 2492 tag='text', anchor=align) 2493 return
2494
2495 - def setWidth(self, w):
2496 """Set width""" 2497 self.width = w 2498 self.redraw() 2499 return
2500
2501 - def clearSelected(self):
2502 self.delete('rect') 2503 return
2504
2505 - def handle_left_click(self, event):
2506 rowclicked = self.table.get_row_clicked(event) 2507 self.startrow = rowclicked 2508 if 0 <= rowclicked < self.table.rows: 2509 self.delete('rect') 2510 self.table.delete('entry') 2511 self.table.delete('multicellrect') 2512 #set row selected 2513 self.table.setSelectedRow(rowclicked) 2514 self.table.drawSelectedRow() 2515 self.drawSelectedRows(self.table.currentrow) 2516 return
2517
2518 - def handle_left_release(self,event):
2519 2520 return
2521
2522 - def handle_left_ctrl_click(self, event):
2523 """Handle ctrl clicks - for multiple row selections""" 2524 rowclicked = self.table.get_row_clicked(event) 2525 multirowlist = self.table.multiplerowlist 2526 if 0 <= rowclicked < self.table.rows: 2527 if rowclicked not in multirowlist: 2528 multirowlist.append(rowclicked) 2529 else: 2530 multirowlist.remove(rowclicked) 2531 self.table.drawMultipleRows(multirowlist) 2532 self.drawSelectedRows(multirowlist) 2533 return
2534
2535 - def handle_right_click(self,event):
2536 2537 return
2538 2539 '''def handle_mouse_drag(self, event): 2540 """Handle mouse drag for mult row selection""" 2541 rowover = self.table.get_row_clicked(event) 2542 colover = self.table.get_col_clicked(event) 2543 if colover == None or rowover == None: 2544 return''' 2545
2546 - def handle_mouse_drag(self, event):
2547 """Handle mouse moved with button held down, multiple selections""" 2548 if hasattr(self, 'cellentry'): 2549 self.cellentry.destroy() 2550 rowover = self.table.get_row_clicked(event) 2551 colover = self.table.get_col_clicked(event) 2552 if rowover == None: 2553 return 2554 if rowover >= self.table.rows or self.startrow > self.table.rows: 2555 return 2556 else: 2557 self.endrow = rowover 2558 #draw the selected rows 2559 if self.endrow != self.startrow: 2560 if self.endrow < self.startrow: 2561 rowlist=range(self.endrow, self.startrow+1) 2562 else: 2563 rowlist=range(self.startrow, self.endrow+1) 2564 self.drawSelectedRows(rowlist) 2565 self.table.multiplerowlist = rowlist 2566 self.table.drawMultipleRows(rowlist) 2567 else: 2568 self.table.multiplerowlist = [] 2569 self.table.multiplerowlist.append(rowover) 2570 self.drawSelectedRows(rowover) 2571 self.table.drawMultipleRows(self.table.multiplerowlist) 2572 return
2573
2574 - def drawSelectedRows(self, rows=None):
2575 """Draw selected rows, accepts a list or integer""" 2576 self.delete('rect') 2577 if type(rows) is not ListType: 2578 rowlist=[] 2579 rowlist.append(rows) 2580 else: 2581 rowlist = rows 2582 for r in rowlist: 2583 if r not in self.table.visiblerows: 2584 continue 2585 self.drawRect(r, delete=0) 2586 return
2587
2588 - def drawRect(self, row=None, tag=None, color=None, outline=None, delete=1):
2589 """Draw a rect representing row selection""" 2590 if tag==None: 2591 tag='rect' 2592 if color==None: 2593 color='#0099CC' 2594 if outline==None: 2595 outline='gray25' 2596 if delete == 1: 2597 self.delete(tag) 2598 w=0 2599 i = self.inset 2600 x1,y1,x2,y2 = self.table.getCellCoords(row, 0) 2601 rect = self.create_rectangle(0+i,y1+i,self.width-i,y2, 2602 fill=color, 2603 outline=outline, 2604 width=w, 2605 tag=tag) 2606 self.lift('text') 2607 return
2608
2609 -class AutoScrollbar(Scrollbar):
2610 """a scrollbar that hides itself if it's not needed. only 2611 works if you use the grid geometry manager."""
2612 - def set(self, lo, hi):
2613 if float(lo) <= 0.0 and float(hi) >= 1.0: 2614 # grid_remove is currently missing from Tkinter! 2615 self.tk.call("grid", "remove", self) 2616 else: 2617 self.grid() 2618 Scrollbar.set(self, lo, hi)
2619 - def pack(self, **kw):
2620 raise TclError, "cannot use pack with this widget"
2621 - def place(self, **kw):
2622 raise TclError, "cannot use place with this widget"
2623