#!/usr/bin/env python """ Implements the core TableCanvas class. Created Oct 2008 Copyright (C) Damien Farrell This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ from __future__ import absolute_import, division, print_function try: from tkinter import * from tkinter.ttk import * except: from Tkinter import * from ttk import * if (sys.version_info > (3, 0)): from tkinter import filedialog, messagebox, simpledialog from tkinter import font import tkinter.colorchooser as tkColorChooser else: import tkFileDialog as filedialog import tkSimpleDialog as simpledialog import tkMessageBox as messagebox import tkFont as font import tkColorChooser from .TableModels import TableModel from .TableFormula import Formula from .Prefs import Preferences from .Dialogs import * import math, time import os, types import copy import platform class TableCanvas(Canvas): """A tkinter class for providing table functionality""" def __init__(self, parent=None, model=None, data=None, read_only=False, width=None, height=None, rows=10, cols=5, **kwargs): Canvas.__init__(self, parent, bg='white', width=width, height=height, relief=GROOVE, scrollregion=(0,0,300,200)) self.parentframe = parent #get platform into a variable self.ostyp = self.checkOSType() self.platform = platform.system() self.width = width self.height = height self.set_defaults() self.currentpage = None self.navFrame = None self.currentrow = 0 self.currentcol = 0 self.reverseorder = 0 self.startrow = self.endrow = None self.startcol = self.endcol = None self.allrows = False #for selected all rows without setting multiplerowlist self.multiplerowlist=[] self.multiplecollist=[] self.col_positions=[] #record current column grid positions self.mode = 'normal' self.read_only = read_only self.filtered = False self.loadPrefs() #set any options passed in kwargs to overwrite defaults and prefs for key in kwargs: self.__dict__[key] = kwargs[key] if data is not None: self.model = TableModel() self.model.importDict(data) elif model is not None: self.model = model else: self.model = TableModel(rows=rows,columns=cols) self.rows = self.model.getRowCount() self.cols = self.model.getColumnCount() self.tablewidth = (self.cellwidth)*self.cols self.do_bindings() #initial sort order self.model.setSortOrder() #column specific actions, define for every column type in the model #when you add a column type you should edit this dict self.columnactions = {'text' : {"Edit": 'drawCellEntry' }, 'number' : {"Edit": 'drawCellEntry' }} self.setFontSize() return def set_defaults(self): """Set default settings""" self.cellwidth=150 self.maxcellwidth=200 self.rowheight=20 self.horizlines=1 self.vertlines=1 self.alternaterows=0 self.autoresizecols = 0 self.inset=2 self.x_start=0 self.y_start=1 self.linewidth=1.0 self.rowheaderwidth=40 self.showkeynamesinheader=False self.thefont = ('Arial',12) self.cellbackgr = '#F7F7FA' self.entrybackgr = 'white' self.grid_color = '#ABB1AD' self.selectedcolor = 'yellow' self.rowselectedcolor = '#CCCCFF' self.multipleselectioncolor = '#ECD672' return def setFontSize(self): """Set font size to match font, we need to get rid of fontsize as a separate variable?""" if hasattr(self, 'thefont') and type(self.thefont) is tuple: self.fontsize = self.thefont[1] return def mouse_wheel(self, event): """Handle mouse wheel scroll for windows""" if event.num == 5 or event.delta == -120: event.widget.yview_scroll(1, UNITS) self.tablerowheader.yview_scroll(1, UNITS) if event.num == 4 or event.delta == 120: if self.canvasy(0) < 0: return event.widget.yview_scroll(-1, UNITS) self.tablerowheader.yview_scroll(-1, UNITS) self.redrawVisible() return def do_bindings(self): """Bind keys and mouse clicks, this can be overriden""" self.bind("",self.handle_left_click) self.bind("",self.handle_double_click) self.bind("", self.handle_left_ctrl_click) self.bind("", self.handle_left_shift_click) self.bind("", self.handle_left_release) if self.ostyp=='mac': #For mac we bind Shift, left-click to right click self.bind("", self.handle_right_click) self.bind('',self.handle_right_click) else: self.bind("", self.handle_right_click) self.bind('', self.handle_mouse_drag) self.bind('', self.handle_motion) self.bind_all("", self.deleteRow) self.bind_all("", self.addRow) self.bind_all("", self.clearData) self.bind_all("", self.paste) #if not hasattr(self,'parentapp'): # self.parentapp = self.parentframe self.parentframe.master.bind_all("", self.handle_arrow_keys) self.parentframe.master.bind_all("", self.handle_arrow_keys) self.parentframe.master.bind_all("", self.handle_arrow_keys) self.parentframe.master.bind_all("", self.handle_arrow_keys) self.parentframe.master.bind_all("", self.handle_arrow_keys) self.parentframe.master.bind_all("", self.handle_arrow_keys) self.parentframe.master.bind_all("", self.handle_arrow_keys) #if 'windows' in self.platform: self.bind("", self.mouse_wheel) self.bind('', self.mouse_wheel) self.bind('', self.mouse_wheel) self.focus_set() return def getModel(self): """Get the current table model""" return self.model def setModel(self, model): """Set a new model - requires redraw to reflect changes""" self.model = model return def createfromDict(self, data): """Attempt to create a new model/table from a dict""" try: namefield=self.namefield except: namefield=data.keys()[0] self.model = TableModel() self.model.importDict(data, namefield=namefield) self.model.setSortOrder(0,reverse=self.reverseorder) return def createTableFrame(self, callback=None): self.show(callback) return def show(self, callback=None): """Adds column header and scrollbars and combines them with the current table adding all to the master frame provided in constructor. Table is then redrawn.""" #Add the table and header to the frame self.tablerowheader = RowHeader(self.parentframe, self, width=self.rowheaderwidth) self.tablecolheader = ColumnHeader(self.parentframe, self) self.Yscrollbar = AutoScrollbar(self.parentframe,orient=VERTICAL,command=self.set_yviews) self.Yscrollbar.grid(row=1,column=2,rowspan=1,sticky='news',pady=0,ipady=0) self.Xscrollbar = AutoScrollbar(self.parentframe,orient=HORIZONTAL,command=self.set_xviews) self.Xscrollbar.grid(row=2,column=1,columnspan=1,sticky='news') self['xscrollcommand'] = self.Xscrollbar.set self['yscrollcommand'] = self.Yscrollbar.set self.tablecolheader['xscrollcommand'] = self.Xscrollbar.set self.tablerowheader['yscrollcommand'] = self.Yscrollbar.set self.parentframe.rowconfigure(1,weight=1) self.parentframe.columnconfigure(1,weight=1) self.tablecolheader.grid(row=0,column=1,rowspan=1,sticky='news',pady=0,ipady=0) self.tablerowheader.grid(row=1,column=0,rowspan=1,sticky='news',pady=0,ipady=0) self.grid(row=1,column=1,rowspan=1,sticky='news',pady=0,ipady=0) self.adjustColumnWidths() self.redrawTable(callback=callback) self.parentframe.bind("", self.redrawVisible) self.tablecolheader.xview("moveto", 0) self.xview("moveto", 0) return def getVisibleRegion(self): """Get visible region of table to display""" x1, y1 = self.canvasx(0), self.canvasy(0) #w, h = self.winfo_width(), self.winfo_height() w,h= self.master.winfo_width(), self.master.winfo_height() if w <= 1.0 or h <= 1.0: w, h = self.master.winfo_width(), self.master.winfo_height() x2, y2 = self.canvasx(w), self.canvasy(h) return x1, y1, x2, y2 def getRowPosition(self, y): """Get current row from canvas position""" h = self.rowheight y_start = self.y_start row = (int(y)-y_start)/h if row < 0: return 0 if row > self.rows: row = self.rows return row def getColPosition(self, x): """Get current col from canvas position""" x_start = self.x_start w = self.cellwidth i=0 col=0 for c in self.col_positions: col = i if c+w>=x: break i+=1 return col def getVisibleRows(self, y1, y2): """Get the visible row range""" start = int(self.getRowPosition(y1)) end = int(self.getRowPosition(y2))+1 if end > self.rows: end = self.rows return start, end def getVisibleCols(self, x1, x2): """Get the visible column range""" start = self.getColPosition(x1) end = self.getColPosition(x2)+1 if end > self.cols: end = self.cols return start, end def redrawVisible(self, event=None, callback=None): """Redraw the visible portion of the canvas""" model = self.model self.rows = self.model.getRowCount() self.cols = self.model.getColumnCount() self.tablewidth = (self.cellwidth) * self.cols self.configure(bg=self.cellbackgr) self.setColPositions() #are we drawing a filtered subset of the recs? if self.filtered == True and self.model.filteredrecs != None: self.rows = len(self.model.filteredrecs) self.delete('colrect') self.rowrange = range(0,self.rows) self.configure(scrollregion=(0,0, self.tablewidth+self.x_start, self.rowheight*self.rows+10)) x1, y1, x2, y2 = self.getVisibleRegion() startvisiblerow, endvisiblerow = self.getVisibleRows(y1, y2) self.visiblerows = range(startvisiblerow, endvisiblerow) startvisiblecol, endvisiblecol = self.getVisibleCols(x1, x2) self.visiblecols = range(startvisiblecol, endvisiblecol) if self.cols == 0 or self.rows == 0: self.delete('entry') self.delete('rowrect') self.delete('currentrect') self.delete('gridline','text') self.tablerowheader.redraw() return self.drawGrid(startvisiblerow, endvisiblerow) align = self.align self.delete('fillrect') for row in self.visiblerows: if callback != None: callback() for col in self.visiblecols: colname = model.getColumnName(col) bgcolor = model.getColorAt(row,col, 'bg') fgcolor = model.getColorAt(row,col, 'fg') text = model.getValueAt(row,col) self.drawText(row, col, text, fgcolor, align) if bgcolor != None: self.drawRect(row,col, color=bgcolor) #self.drawSelectedCol() self.tablecolheader.redraw() self.tablerowheader.redraw(align=self.align, showkeys=self.showkeynamesinheader) #self.setSelectedRow(self.currentrow) self.drawSelectedRow() self.drawSelectedRect(self.currentrow, self.currentcol) #print self.multiplerowlist if len(self.multiplerowlist)>1: self.tablerowheader.drawSelectedRows(self.multiplerowlist) self.drawMultipleRows(self.multiplerowlist) self.drawMultipleCells() return def redrawTable(self, event=None, callback=None): self.redrawVisible(event, callback) return def redraw(self, event=None, callback=None): self.redrawVisible(event, callback) return def redrawCell(self, row=None, col=None, recname=None, colname=None): """Redraw a specific cell only""" if row == None and recname != None: row = self.model.getRecordIndex(recname) if col == None and colname != None: col = self.model.getColumnIndex(colname) bgcolor = self.model.getColorAt(row,col, 'bg') fgcolor = self.model.getColorAt(row,col, 'fg') text = self.model.getValueAt(row,col) self.drawText(row, col, text, fgcolor) if bgcolor != None: self.drawRect(row,col, color=bgcolor) return def adjustColumnWidths(self): """Optimally adjust col widths to accomodate the longest entry in each column - usually only called on first redraw""" #self.cols = self.model.getColumnCount() try: fontsize = self.thefont[1] except: fontsize = self.fontsize scale = 8.5 * float(fontsize)/12 for col in range(self.cols): colname = self.model.getColumnName(col) if colname in self.model.columnwidths: w = self.model.columnwidths[colname] else: w = self.cellwidth maxlen = self.model.getlongestEntry(col) size = maxlen * scale if size < w: continue #print col, size, self.cellwidth if size >= self.maxcellwidth: size = self.maxcellwidth self.model.columnwidths[colname] = size + float(fontsize)/12*6 return def autoResizeColumns(self): """Automatically set nice column widths and draw""" self.adjustColumnWidths() self.redrawTable() return def setColPositions(self): """Determine current column grid positions""" self.col_positions=[] w=self.cellwidth x_pos=self.x_start self.col_positions.append(x_pos) for col in range(self.cols): colname=self.model.getColumnName(col) if colname in self.model.columnwidths: x_pos=x_pos+self.model.columnwidths[colname] else: x_pos=x_pos+w self.col_positions.append(x_pos) self.tablewidth = self.col_positions[len(self.col_positions)-1] return def sortTable(self, columnIndex=0, columnName=None, reverse=0): """Set up sort order dict based on currently selected field""" self.model.setSortOrder(columnIndex, columnName, reverse) self.redrawTable() return def set_xviews(self,*args): """Set the xview of table and col header""" self.xview(*args) self.tablecolheader.xview(*args) self.redrawVisible() return def set_yviews(self,*args): """Set the xview of table and row header""" self.yview(*args) self.tablerowheader.yview(*args) self.redrawVisible() return def addRow(self, key=None, **kwargs): """Add new row""" key = self.model.addRow(key, **kwargs) self.redrawTable() self.setSelectedRow(self.model.getRecordIndex(key)) return def addRows(self, num=None): """Add new rows""" if num == None: num = simpledialog.askinteger("Now many rows?", "Number of rows:",initialvalue=1, parent=self.parentframe) if not num: return keys = self.model.autoAddRows(num) self.redrawTable() self.setSelectedRow(self.model.getRecordIndex(keys[0])) return def addColumn(self, newname=None): """Add a new column""" if newname == None: coltypes = self.getModel().getDefaultTypes() d = MultipleValDialog(title='New Column', initialvalues=(coltypes, ''), labels=('Column Type','Name'), types=('list','string'), parent=self.parentframe) if d.result == None: return else: coltype = d.results[0] newname = d.results[1] if newname != None: if newname in self.getModel().columnNames: messagebox.showwarning("Name exists", "Name already exists!", parent=self.parentframe) else: self.model.addColumn(newname) self.parentframe.configure(width=self.width) self.redrawTable() return def deleteRow(self): """Delete a row""" if len(self.multiplerowlist)>1: n = messagebox.askyesno("Delete", "Delete Selected Records?", parent=self.parentframe) if n == True: rows = self.multiplerowlist self.model.deleteRows(rows) self.clearSelected() self.setSelectedRow(0) self.redrawTable() else: n = messagebox.askyesno("Delete", "Delete This Record?", parent=self.parentframe) if n: row = self.getSelectedRow() self.model.deleteRow(row) self.setSelectedRow(row-1) self.clearSelected() self.redrawTable() return def deleteColumn(self): """Delete currently selected column""" n = messagebox.askyesno("Delete", "Delete This Column?", parent=self.parentframe) if n: col = self.getSelectedColumn() self.model.deleteColumn(col) self.currentcol = self.currentcol - 1 self.redrawTable() return def deleteCells(self, rows, cols): """Clear the cell contents""" n = messagebox.askyesno("Clear Confirm", "Clear this data?", parent=self.parentframe) if not n: return for col in cols: for row in rows: #absrow = self.get_AbsoluteRow(row) self.model.deleteCellRecord(row, col) self.redrawCell(row,col) return def clearData(self, evt=None): """Delete cells from gui event""" rows = self.multiplerowlist cols = self.multiplecollist self.deleteCells(rows, cols) return def autoAddColumns(self, numcols=None): """Automatically add x number of cols""" if numcols == None: numcols = simpledialog.askinteger("Auto add rows.", "How many empty columns?", parent=self.parentframe) self.model.auto_AddColumns(numcols) self.parentframe.configure(width=self.width) self.redrawTable() return def getRecordInfo(self, row): """Show the record for this row""" model = self.model #We need a custom dialog for allowing field entries here #absrow = self.get_AbsoluteRow(row) d = RecordViewDialog(title="Record Details", parent=self.parentframe, table=self, row=row) return def findValue(self, searchstring=None, findagain=None): """Return the row/col for the input value""" if searchstring == None: searchstring = simpledialog.askstring("Search table.", "Enter search value", parent=self.parentframe) found=0 if findagain == None or not hasattr(self,'foundlist'): self.foundlist=[] if self.model!=None: for row in range(self.rows): for col in range(self.cols): text = str(self.model.getValueAt(row,col)) if text=='' or text==None: continue cell=row,col if findagain == 1 and cell in self.foundlist: continue if text.lower().find(searchstring.lower())!=-1: print ('found in',row,col) found=1 #highlight cell self.delete('searchrect') self.drawRect(row, col, color='red', tag='searchrect', delete=0) self.lift('searchrect') self.lift('celltext'+str(col)+'_'+str(row)) #add row/col to foundlist self.foundlist.append(cell) #need to scroll to centre the cell here.. x,y = self.getCanvasPos(row, col) self.xview('moveto', x) self.yview('moveto', y) self.tablecolheader.xview('moveto', x) self.tablerowheader.yview('moveto', y) return row, col if found==0: self.delete('searchrect') print ('nothing found') return None def showAll(self): self.model.filteredrecs = None self.filtered = False self.redrawTable() return def doFilter(self, event=None): """Filter the table display by some column values. We simply pass the model search function to the the filtering class and that handles everything else. See filtering frame class for how searching is done. """ if self.model==None: return names = self.filterframe.doFiltering(searchfunc=self.model.filterBy) #create a list of filtered recs self.model.filteredrecs = names self.filtered = True self.redrawTable() return def createFilteringBar(self, parent=None, fields=None): """Add a filter frame""" if parent == None: parent = Toplevel() parent.title('Filter Records') x,y,w,h = self.getGeometry(self.master) parent.geometry('+%s+%s' %(x,y+h)) if fields == None: fields = self.model.columnNames from .Filtering import FilterFrame self.filterframe = FilterFrame(parent, fields, self.doFilter, self.closeFilterFrame) self.filterframe.pack() return parent def showFilteringBar(self): if not hasattr(self, 'filterwin') or self.filterwin == None: self.filterwin = self.createFilteringBar() self.filterwin.protocol("WM_DELETE_WINDOW", self.closeFilterFrame) else: self.filterwin.lift() return def closeFilterFrame(self): """Callback for closing filter frame""" self.filterwin.destroy() self.filterwin = None self.showAll() return def resizeColumn(self, col, width): """Resize a column by dragging""" #print 'resizing column', col #recalculate all col positions.. colname=self.model.getColumnName(col) self.model.columnwidths[colname]=width self.setColPositions() self.redrawTable() self.drawSelectedCol(self.currentcol) return def get_currentRecord(self): """Get the currently selected record""" rec = self.model.getRecordAtRow(self.currentrow) return rec def get_currentColName(self): """Get the currently selected record name""" colname = self.mo(self.currentcol) return colname def get_currentRecordName(self): """Get the currently selected record name""" recname = self.model.getRecName(self.currentrow) return recname def get_selectedRecordNames(self): """Get a list of the current multiple selection, if any""" recnames=[] for row in self.multiplerowlist: recnames.append(self.model.getRecName(row)) return recnames def get_currentRecCol(self): """Get the clicked rec and col names as a tuple""" recname = self.get_currentRecordName() colname = self.get_currentColName() return (recname, colname) def get_row_clicked(self, event): """get row where event on canvas occurs""" h=self.rowheight #get coord on canvas, not window, need this if scrolling y = int(self.canvasy(event.y)) y_start=self.y_start rowc = int((int(y)-y_start)/h) #rowc = math.floor(rowc) #print 'event.y',event.y, 'y',y #print 'rowclicked', rowc return rowc def get_col_clicked(self,event): """get col where event on canvas occurs""" w=self.cellwidth x = int(self.canvasx(event.x)) x_start=self.x_start #print self.col_positions for colpos in self.col_positions: try: nextpos=self.col_positions[self.col_positions.index(colpos)+1] except: nextpos=self.tablewidth if x > colpos and x <= nextpos: #print 'x=', x, 'colpos', colpos, self.col_positions.index(colpos) return self.col_positions.index(colpos) else: #print None pass #return colc def setSelectedRow(self, row): """Set currently selected row and reset multiple row list""" self.currentrow = row self.multiplerowlist = [] self.multiplerowlist.append(row) return def setSelectedCol(self, col): """Set currently selected column""" self.currentcol = col self.multiplecollist = [] self.multiplecollist.append(col) return def setSelectedCells(self, startrow, endrow, startcol, endcol): """Set a block of cells selected""" self.currentrow = startrow self.currentcol = startcol if startrow < 0 or startcol < 0: return if endrow > self.rows or endcol > self.cols: return for r in range(startrow, endrow): self.multiplerowlist.append(r) for c in range(startcol, endcol): self.multiplecollist.append(c) return def getSelectedRow(self): """Get currently selected row""" return self.currentrow def getSelectedColumn(self): """Get currently selected column""" return self.currentcol def select_All(self): """Select all rows and cells""" self.startrow = 0 self.endrow = self.rows self.multiplerowlist = range(self.startrow,self.endrow) self.drawMultipleRows(self.multiplerowlist) self.startcol = 0 self.endcol = self.cols self.multiplecollist = range(self.startcol, self.endcol) self.drawMultipleCells() return def getCellCoords(self, row, col): """Get x-y coordinates to drawing a cell in a given row/col""" colname=self.model.getColumnName(col) if colname in self.model.columnwidths: w=self.model.columnwidths[colname] else: w=self.cellwidth h=self.rowheight x_start=self.x_start y_start=self.y_start #get nearest rect co-ords for that row/col #x1=x_start+w*col x1=self.col_positions[col] y1=y_start+h*row x2=x1+w y2=y1+h return x1,y1,x2,y2 def getCanvasPos(self, row, col): """Get the cell x-y coords as a fraction of canvas size""" if self.rows==0: return None, None x1,y1,x2,y2 = self.getCellCoords(row,col) cx=float(x1)/self.tablewidth cy=float(y1)/(self.rows*self.rowheight) return cx, cy def isInsideTable(self,x,y): """Returns true if x-y coord is inside table bounds""" if self.x_start < x < self.tablewidth and self.y_start < y < self.rows*self.rowheight: return 1 else: return 0 return answer def setRowHeight(self, h): """Set the row height""" self.rowheight = h return def clearSelected(self): self.delete('rect') self.delete('entry') self.delete('tooltip') self.delete('searchrect') self.delete('colrect') self.delete('multicellrect') #self.delete('formulabox') return def gotoprevRow(self): """Programmatically set previous row - eg. for button events""" self.clearSelected() current = self.getSelectedRow() self.setSelectedRow(current-1) self.startrow = current-1 self.endrow = current-1 #reset multiple selection list self.multiplerowlist=[] self.multiplerowlist.append(self.currentrow) self.drawSelectedRect(self.currentrow, self.currentcol) self.drawSelectedRow() coltype = self.model.getColumnType(self.currentcol) if coltype == 'text' or coltype == 'number': self.drawCellEntry(self.currentrow, self.currentcol) return def gotonextRow(self): """Programmatically set next row - eg. for button events""" self.clearSelected() current = self.getSelectedRow() self.setSelectedRow(current+1) self.startrow = current+1 self.endrow = current+1 #reset multiple selection list self.multiplerowlist=[] self.multiplerowlist.append(self.currentrow) self.drawSelectedRect(self.currentrow, self.currentcol) self.drawSelectedRow() coltype = self.model.getColumnType(self.currentcol) if coltype == 'text' or coltype == 'number': self.drawCellEntry(self.currentrow, self.currentcol) return def handle_left_click(self, event): """Respond to a single press""" #which row and column is the click inside? self.clearSelected() self.allrows = False rowclicked = self.get_row_clicked(event) colclicked = self.get_col_clicked(event) self.focus_set() if self.mode == 'formula': self.handleFormulaClick(rowclicked, colclicked) return if hasattr(self, 'cellentry'): self.cellentry.destroy() #ensure popup menus are removed if present if hasattr(self, 'rightmenu'): self.rightmenu.destroy() if hasattr(self.tablecolheader, 'rightmenu'): self.tablecolheader.rightmenu.destroy() self.startrow = rowclicked self.endrow = rowclicked self.startcol = colclicked self.endcol = colclicked #reset multiple selection list self.multiplerowlist=[] self.multiplerowlist.append(rowclicked) if rowclicked is None or colclicked is None: return if self.read_only is True: return if 0 <= rowclicked < self.rows and 0 <= colclicked < self.cols: self.setSelectedRow(rowclicked) self.setSelectedCol(colclicked) self.drawSelectedRect(self.currentrow, self.currentcol) self.drawSelectedRow() self.tablerowheader.drawSelectedRows(rowclicked) coltype = self.model.getColumnType(colclicked) if coltype == 'text' or coltype == 'number': self.drawCellEntry(rowclicked, colclicked) return def handle_left_release(self,event): self.endrow = self.get_row_clicked(event) return def handle_left_ctrl_click(self, event): """Handle ctrl clicks for multiple row selections""" rowclicked = self.get_row_clicked(event) colclicked = self.get_col_clicked(event) if 0 <= rowclicked < self.rows and 0 <= colclicked < self.cols: if rowclicked not in self.multiplerowlist: self.multiplerowlist.append(rowclicked) else: self.multiplerowlist.remove(rowclicked) self.drawMultipleRows(self.multiplerowlist) if colclicked not in self.multiplecollist: self.multiplecollist.append(colclicked) #print self.multiplecollist self.drawMultipleCells() return def handle_left_shift_click(self, event): """Handle shift click, for selecting multiple rows""" #Has same effect as mouse drag, so just use same method self.handle_mouse_drag(event) return def handle_mouse_drag(self, event): """Handle mouse moved with button held down, multiple selections""" if hasattr(self, 'cellentry'): self.cellentry.destroy() rowover = self.get_row_clicked(event) colover = self.get_col_clicked(event) if colover == None or rowover == None: return if rowover >= self.rows or self.startrow > self.rows: return else: self.endrow = rowover #do columns if colover > self.cols or self.startcol > self.cols: return else: self.endcol = colover if self.startcol is None or self.endcol is None: return if self.endcol < self.startcol: self.multiplecollist = range(self.endcol, self.startcol+1) else: self.multiplecollist = range(self.startcol, self.endcol+1) #print self.multiplecollist #draw the selected rows if self.endrow != self.startrow: if self.endrow < self.startrow: self.multiplerowlist = range(self.endrow, self.startrow+1) else: self.multiplerowlist = range(self.startrow, self.endrow+1) self.drawMultipleRows(self.multiplerowlist) self.tablerowheader.drawSelectedRows(self.multiplerowlist) #draw selected cells outline using row and col lists #print self.multiplerowlist self.drawMultipleCells() else: self.multiplerowlist = [] self.multiplerowlist.append(self.currentrow) if len(self.multiplecollist) >= 1: self.drawMultipleCells() self.delete('multiplesel') #print self.multiplerowlist return def handle_arrow_keys(self, event): """Handle arrow keys press""" #print event.keysym row = self.get_row_clicked(event) col = self.get_col_clicked(event) x,y = self.getCanvasPos(self.currentrow, 0) if x == None: return if event.keysym == 'Up': if self.currentrow == 0: return else: self.currentrow = self.currentrow -1 elif event.keysym == 'Down': if self.currentrow >= self.rows-1: return else: self.currentrow = self.currentrow +1 elif event.keysym == 'Right' or event.keysym == 'Tab': if self.currentcol >= self.cols-1: if self.currentrow < self.rows-1: self.currentcol = 0 self.currentrow = self.currentrow +1 else: return else: self.currentcol = self.currentcol +1 elif event.keysym == 'Left': if self.currentcol == 0: if self.currentrow == 0: return else: self.currentcol = self.cols-1 self.currentrow = self.currentrow - 1 else: self.currentcol = self.currentcol -1 self.drawSelectedRect(self.currentrow, self.currentcol) coltype = self.model.getColumnType(self.currentcol) if coltype == 'text' or coltype == 'number': self.delete('entry') self.drawCellEntry(self.currentrow, self.currentcol) return def handle_double_click(self, event): """Do double click stuff. Selected row/cols will already have been set with single click binding""" '''row = self.get_row_clicked(event) col = self.get_col_clicked(event) model=self.getModel() cellvalue = model.getCellRecord(row, col) if Formula.isFormula(cellvalue): self.formula_Dialog(row, col, cellvalue)''' return def handle_right_click(self, event): """respond to a right click""" if self.read_only is True: return self.delete('tooltip') self.tablerowheader.clearSelected() if hasattr(self, 'rightmenu'): self.rightmenu.destroy() rowclicked = self.get_row_clicked(event) colclicked = self.get_col_clicked(event) if colclicked == None: self.rightmenu = self.popupMenu(event, outside=1) return if (rowclicked in self.multiplerowlist or self.allrows == True) and colclicked in self.multiplecollist: self.rightmenu = self.popupMenu(event, rows=self.multiplerowlist, cols=self.multiplecollist) else: if 0 <= rowclicked < self.rows and 0 <= colclicked < self.cols: self.clearSelected() self.allrows = False self.setSelectedRow(rowclicked) self.setSelectedCol(colclicked) self.drawSelectedRect(self.currentrow, self.currentcol) self.drawSelectedRow() if self.isInsideTable(event.x,event.y) == 1: self.rightmenu = self.popupMenu(event,rows=self.multiplerowlist, cols=self.multiplecollist) else: self.rightmenu = self.popupMenu(event, outside=1) return def handle_motion(self, event): """Handle mouse motion on table""" self.delete('tooltip') row = self.get_row_clicked(event) col = self.get_col_clicked(event) if row == None or col == None: return if 0 <= row < self.rows and 0 <= col < self.cols: self.drawTooltip(row, col) return def gotonextCell(self, event): """Move highlighted cell to next cell in row or a new col""" #print 'next' if hasattr(self, 'cellentry'): self.cellentry.destroy() self.currentcol=self.currentcol+1 if self.currentcol >= self.cols-1: self.currentrow = self.currentrow +1 self.currentcol = self.currentcol+1 self.drawSelectedRect(self.currentrow, self.currentcol) return def movetoSelectedRow(self, row=None, recname=None): """Move to selected row, updating table""" row=self.model.getRecordIndex(recname) self.setSelectedRow(row) self.drawSelectedRow() x,y = self.getCanvasPos(row, 0) self.yview('moveto', y-0.01) self.tablecolheader.yview('moveto', y) return def handleFormulaClick(self, row, col): """Do a dialog for cell formula entry""" model = self.getModel() cell = list(model.getRecColNames(row, col)) #absrow = self.get_AbsoluteRow(row) self.formulaText.insert(END, str(cell)) self.formulaText.focus_set() self.drawSelectedRect(row, col, color='red') return def formula_Dialog(self, row, col, currformula=None): """Formula dialog""" self.mode = 'formula' print (self.mode) x1,y1,x2,y2 = self.getCellCoords(row,col) w=300 h=h=self.rowheight * 3 def close(): if hasattr(self,'formulaWin'): self.delete('formulabox') self.mode = 'normal' def calculate(): #get text area contents and do formula f = self.formulaText.get(1.0, END) f = f.strip('\n') self.model.setFormulaAt(f,row,col) value = self.model.doFormula(f) color = self.model.getColorAt(row,col,'fg') self.drawText(row, col, value, color) close() self.mode = 'normal' return def clear(): self.formulaText.delete(1.0, END) self.formulaFrame = Frame(width=w,height=h,bd=3) self.formulaText = Text(self.formulaFrame, width=30, height=8, bg='white',relief=GROOVE) self.formulaText.pack(side=LEFT,padx=2,pady=2) if currformula != None: self.formulaText.insert(END, Formula.getFormula(currformula)) cancelbutton=Button(self.formulaFrame, text='Cancel', bg='#99ccff',command=close) cancelbutton.pack(fill=BOTH,padx=2,pady=2) donebutton=Button(self.formulaFrame, text='Done', bg='#99ccff',command=calculate) donebutton.pack(fill=BOTH,padx=2,pady=2) '''clrbutton=Button(self.formulaFrame, text='Clear', bg='#99ccff',command=clear) clrbutton.pack(fill=BOTH,padx=2,pady=2) ''' #add to canvas self.formulaWin = self.create_window(x1+self.inset,y1+self.inset, width=w,height=h, window=self.formulaFrame,anchor='nw', tag='formulabox') self.formulaText.focus_set() return def convertFormulae(self, rows, cols=None): """Convert the formulas in the cells to their result values""" if len(self.multiplerowlist) == 0 or len(self.multiplecollist) == 0: return None print (rows, cols) if cols == None: cols = range(self.cols) for r in rows: #absr=self.get_AbsoluteRow(r) for c in cols: val = self.model.getValueAt(r,c) self.model.setValueAt(val, r, c) return def paste(self, event=None): """Copy from clipboard""" print (self.parentframe.clipboard_get()) return def copyCell(self, rows, cols=None): """Copy cell contents to a temp internal clipboard""" row = rows[0]; col = cols[0] #absrow = self.get_AbsoluteRow(row) self.clipboard = copy.deepcopy(self.model.getCellRecord(row, col)) return def pasteCell(self, rows, cols=None): """Paste cell from internal clipboard""" row = rows[0]; col = cols[0] #absrow = self.get_AbsoluteRow(row) val = self.clipboard self.model.setValueAt(val, row, col) self.redrawTable() return def copyColumns(self): """Copy current selected cols""" M = self.model coldata = {} for col in self.multiplecollist: name = M.columnNames[col] coldata[name] = M.getColumnData(columnName=name) return coldata def pasteColumns(self, coldata): """Paste new cols, overwrites existing names""" M = self.model for name in coldata: if name not in M.columnNames: M.addColumn(name) for r in range(len(coldata[name])): val = coldata[name][r] col = M.columnNames.index(name) if r >= self.rows: break M.setValueAt(val, r, col) self.redrawTable() return coldata # --- Some cell specific actions here --- def setcellColor(self, rows, cols=None, newColor=None, key=None, redraw=True): """Set the cell color for one or more cells and save it in the model color""" model = self.getModel() if newColor == None: ctuple, newColor = tkColorChooser.askcolor(title='pick a color') if newColor == None: return if type(rows) is int: x=rows rows=[] rows.append(x) if self.allrows == True: #we use all rows if the whole column has been selected rows = range(0,self.rows) if cols == None: cols = range(self.cols) for col in cols: for row in rows: #absrow = self.get_AbsoluteRow(row) model.setColorAt(row, col, color=newColor, key=key) #setcolor(absrow, col) if redraw == True: self.redrawTable() return def popupMenu(self, event, rows=None, cols=None, outside=None): """Add left and right click behaviour for canvas, should not have to override this function, it will take its values from defined dicts in constructor""" defaultactions = {"Set Fill Color" : lambda : self.setcellColor(rows,cols,key='bg'), "Set Text Color" : lambda : self.setcellColor(rows,cols,key='fg'), "Copy" : lambda : self.copyCell(rows, cols), "Paste" : lambda : self.pasteCell(rows, cols), "Fill Down" : lambda : self.fillDown(rows, cols), "Fill Right" : lambda : self.fillAcross(cols, rows), "Add Row(s)" : lambda : self.addRows(), "Delete Row(s)" : lambda : self.deleteRow(), "View Record" : lambda : self.getRecordInfo(row), "Clear Data" : lambda : self.deleteCells(rows, cols), "Select All" : self.select_All, "Auto Fit Columns" : self.autoResizeColumns, "Filter Records" : self.showFilteringBar, "New": self.new, "Load": self.load, "Save": self.save, "Import text":self.importTable, "Export csv": self.exportTable, "Plot Selected" : self.plotSelected, "Plot Options" : self.plotSetup, "Export Table" : self.exportTable, "Preferences" : self.showtablePrefs, "Formulae->Value" : lambda : self.convertFormulae(rows, cols)} main = ["Set Fill Color","Set Text Color","Copy", "Paste", "Fill Down","Fill Right", "Clear Data"] general = ["Select All", "Add Row(s)" , "Delete Row(s)", "Auto Fit Columns", "Filter Records", "Preferences"] filecommands = ['New','Load','Save','Import text','Export csv'] plotcommands = ['Plot Selected','Plot Options'] def createSubMenu(parent, label, commands): menu = Menu(parent, tearoff = 0) popupmenu.add_cascade(label=label,menu=menu) for action in commands: menu.add_command(label=action, command=defaultactions[action]) return menu def add_commands(fieldtype): """Add commands to popup menu for column type and specific cell""" functions = self.columnactions[fieldtype] for f in functions.keys(): func = getattr(self, functions[f]) popupmenu.add_command(label=f, command= lambda : func(row,col)) return popupmenu = Menu(self, tearoff = 0) def popupFocusOut(event): popupmenu.unpost() if outside == None: #if outside table, just show general items row = self.get_row_clicked(event) col = self.get_col_clicked(event) coltype = self.model.getColumnType(col) def add_defaultcommands(): """now add general actions for all cells""" for action in main: if action == 'Fill Down' and (rows == None or len(rows) <= 1): continue if action == 'Fill Right' and (cols == None or len(cols) <= 1): continue else: popupmenu.add_command(label=action, command=defaultactions[action]) return if coltype in self.columnactions: add_commands(coltype) add_defaultcommands() for action in general: popupmenu.add_command(label=action, command=defaultactions[action]) popupmenu.add_separator() createSubMenu(popupmenu, 'File', filecommands) createSubMenu(popupmenu, 'Plot', plotcommands) popupmenu.bind("", popupFocusOut) popupmenu.focus_set() popupmenu.post(event.x_root, event.y_root) return popupmenu # --- spreadsheet type functions --- def fillDown(self, rowlist, collist): """Fill down a column, or multiple columns""" model = self.model #absrow = self.get_AbsoluteRow(rowlist[0]) #remove first element as we don't want to overwrite it rowlist.remove(rowlist[0]) #if this is a formula, we have to treat it specially for col in collist: val = self.model.getCellRecord(row, col) f=val #formula to copy i=1 for r in rowlist: #absr = self.get_AbsoluteRow(r) if Formula.isFormula(f): newval = model.copyFormula(f, r, col, offset=i) model.setFormulaAt(newval, r, col) else: model.setValueAt(val, r, col) #print 'setting', val, 'at row', r i+=1 self.redrawTable() return def fillAcross(self, collist, rowlist): """Fill across a row, or multiple rows""" model = self.model #row = self.currentrow #absrow = self.get_AbsoluteRow(collist[0]) frstcol = collist[0] collist.remove(frstcol) for row in rowlist: #absr = self.get_AbsoluteRow(row) val = self.model.getCellRecord(absr, frstcol) f=val #formula to copy i=1 for c in collist: if Formula.isFormula(f): newval = model.copyFormula(f, r, c, offset=i, dim='x') model.setFormulaAt(newval, r, c) else: model.setValueAt(val, r, c) i+=1 self.redrawTable() return def getSelectionValues(self): """Get values for current multiple cell selection""" if len(self.multiplerowlist) == 0 or len(self.multiplecollist) == 0: return None rows = self.multiplerowlist cols = self.multiplecollist model = self.model if len(rows)<1 or len(cols)<1: return None #if only one row selected we plot whole col if len(rows) == 1: rows = self.rowrange lists = [] for c in cols: x=[] for r in rows: #absr = self.get_AbsoluteRow(r) val = model.getValueAt(r,c) if val == None or val == '': continue x.append(val) lists.append(x) return lists def plotSelected(self, graphtype='XY'): """Plot the selected data using pylab - if possible""" from .Plot import pylabPlotter if not hasattr(self, 'pyplot'): self.pyplot = pylabPlotter() plotdata = [] for p in self.getSelectionValues(): x = [] fail = False for d in p: try: x.append(float(d)) except: fail = True continue if fail == False: plotdata.append(x) pltlabels = self.getplotlabels() if len(pltlabels) > 2: self.pyplot.setDataSeries(pltlabels) self.pyplot.showlegend = 1 self.pyplot.plotCurrent(data=plotdata, graphtype=graphtype) return def plotSetup(self): """Call pylab plot dialog setup, send data if we haven't already plotted""" from PylabPlot import pylabPlotter if not hasattr(self, 'pyplot'): self.pyplot = pylabPlotter() plotdata = self.getSelectionValues() if not self.pyplot.hasData() and plotdata != None: print ('has data') plotdata = self.getSelectionValues() pltlabels = self.getplotlabels() self.pyplot.setDataSeries(pltlabels) self.pyplot.plotSetup(plotdata) else: self.pyplot.plotSetup() return def getplotlabels(self): """Get labels for plot series from col labels""" pltlabels = [] for col in self.multiplecollist: pltlabels.append(self.model.getColumnLabel(col)) return pltlabels #--- Drawing stuff --- def drawGrid(self, startrow, endrow): """Draw the table grid lines""" self.delete('gridline','text') rows=len(self.rowrange) cols=self.cols w = self.cellwidth h = self.rowheight x_start=self.x_start y_start=self.y_start x_pos=x_start if self.vertlines==1: for col in range(cols+1): x=self.col_positions[col] self.create_line(x,y_start,x,y_start+rows*h, tag='gridline', fill=self.grid_color, width=self.linewidth) if self.horizlines==1: for row in range(startrow, endrow+1): y_pos=y_start+row*h self.create_line(x_start,y_pos,self.tablewidth,y_pos, tag='gridline', fill=self.grid_color, width=self.linewidth) return def drawRowHeader(self): """User has clicked to select a cell""" self.delete('rowheader') x_start=self.x_start y_start=self.y_start h=self.rowheight rowpos=0 for row in self.rowrange: x1,y1,x2,y2 = self.getCellCoords(rowpos,0) self.create_rectangle(0,y1,x_start-2,y2, fill='gray75', outline='white', width=1, tag='rowheader') self.create_text(x_start/2,y1+h/2, text=row+1, fill='black', font=self.thefont, tag='rowheader') rowpos+=1 return def drawSelectedRect(self, row, col, color=None): """User has clicked to select a cell""" if col >= self.cols: return self.delete('currentrect') bg = self.selectedcolor if color == None: color = 'gray25' w=3 x1,y1,x2,y2 = self.getCellCoords(row,col) rect = self.create_rectangle(x1+w/2,y1+w/2,x2-w/2,y2-w/2, fill=bg, outline=color, width=w, stipple='gray50', tag='currentrect') #self.lower('currentrect') #raise text above all self.lift('celltext'+str(col)+'_'+str(row)) return def drawRect(self, row, col, color=None, tag=None, delete=1): """Cell is colored""" if delete==1: self.delete('cellbg'+str(row)+str(col)) if color==None or color==self.cellbackgr: return else: bg=color if tag==None: recttag='fillrect' else: recttag=tag w=1 x1,y1,x2,y2 = self.getCellCoords(row,col) rect = self.create_rectangle(x1+w/2,y1+w/2,x2-w/2,y2-w/2, fill=bg, outline=bg, width=w, tag=(recttag,'cellbg'+str(row)+str(col))) self.lower(recttag) return def drawCellEntry(self, row, col, text=None): """When the user single/double clicks on a text/number cell, bring up entry window""" if self.read_only == True: return #absrow = self.get_AbsoluteRow(row) h=self.rowheight model=self.getModel() cellvalue = self.model.getCellRecord(row, col) if Formula.isFormula(cellvalue): return else: text = self.model.getValueAt(row, col) x1,y1,x2,y2 = self.getCellCoords(row,col) w=x2-x1 #Draw an entry window txtvar = StringVar() txtvar.set(text) def callback(e): value = txtvar.get() if value == '=': #do a dialog that gets the formula into a text area #then they can click on the cells they want #when done the user presses ok and its entered into the cell self.cellentry.destroy() #its all done here.. self.formula_Dialog(row, col) return coltype = self.model.getColumnType(col) if coltype == 'number': sta = self.checkDataEntry(e) if sta == 1: model.setValueAt(value,row,col) elif coltype == 'text': model.setValueAt(value,row,col) color = self.model.getColorAt(row,col,'fg') self.drawText(row, col, value, color, align=self.align) if e.keysym=='Return': self.delete('entry') #self.drawRect(row, col) #self.gotonextCell(e) return self.cellentry=Entry(self.parentframe,width=20, textvariable=txtvar, #bg=self.entrybackgr, #relief=FLAT, takefocus=1, font=self.thefont) self.cellentry.icursor(END) self.cellentry.bind('', callback) self.cellentry.bind('', callback) self.cellentry.focus_set() self.entrywin=self.create_window(x1+self.inset,y1+self.inset, width=w-self.inset*2,height=h-self.inset*2, window=self.cellentry,anchor='nw', tag='entry') return def checkDataEntry(self,event=None): """do validation checks on data entry in a widget""" #if user enters commas, change to points import re value=event.widget.get() if value!='': try: value=re.sub(',','.', value) value=float(value) except ValueError: event.widget.configure(bg='red') return 0 elif value == '': return 1 return 1 def drawText(self, row, col, celltxt, fgcolor=None, align=None): """Draw the text inside a cell area""" self.delete('celltext'+str(col)+'_'+str(row)) h=self.rowheight x1,y1,x2,y2 = self.getCellCoords(row,col) w=x2-x1 wrap = False pad=5 # If celltxt is a number then we make it a string if type(celltxt) is float or type(celltxt) is int: celltxt=str(celltxt) length = len(celltxt) if length == 0: return #if cell width is less than x, print nothing if w<=10: return if fgcolor == None or fgcolor == "None": fgcolor = 'black' if align == None: align = 'w' if align == 'w': x1 = x1-w/2+pad elif align == 'e': x1 = x1+w/2-pad if w < 18: celltxt = '.' else: fontsize = self.fontsize colname = self.model.getColumnName(col) #scaling between canvas and text normalised to about font 14 scale = 8.5 * float(fontsize)/12 size = length * scale if size > w: newlength = w / scale #print w, size, length, newlength celltxt = celltxt[0:int(math.floor(newlength))] #if celltxt is dict then we are drawing a hyperlink if self.isLink(celltxt) == True: haslink=0 linktext=celltxt['text'] if len(linktext) > w/scale or w<28: linktext=linktext[0:int(w/fontsize*1.2)-2]+'..' if celltxt['link']!=None and celltxt['link']!='': f,s = self.thefont linkfont = (f, s, 'underline') linkcolor='blue' haslink=1 else: linkfont = self.thefont linkcolor=fgcolor rect = self.create_text(x1+w/2,y1+h/2, text=linktext, fill=linkcolor, font=linkfont, tag=('text','hlink','celltext'+str(col)+'_'+str(row))) if haslink == 1: self.tag_bind(rect, '', self.check_hyperlink) #just normal text else: rect = self.create_text(x1+w/2,y1+h/2, text=celltxt, fill=fgcolor, font=self.thefont, anchor=align, tag=('text','celltext'+str(col)+'_'+str(row))) return def isLink(self, cell): """Checks if cell is a hyperlink, without using isinstance""" try: if 'link' in cell: return True except: return False def drawSelectedRow(self): """Draw the highlight rect for the currently selected row""" self.delete('rowrect') row = self.currentrow x1,y1,x2,y2 = self.getCellCoords(row,0) x2 = self.tablewidth rect = self.create_rectangle(x1,y1,x2,y2, fill=self.rowselectedcolor, outline=self.rowselectedcolor, tag='rowrect') self.lower('rowrect') self.lower('fillrect') self.tablerowheader.drawSelectedRows(self.currentrow) return def drawSelectedCol(self, col=None, delete=1): """Draw an outline rect fot the current column selection""" if delete == 1: self.delete('colrect') if col == None: col=self.currentcol w=2 x1,y1,x2,y2 = self.getCellCoords(0,col) y2 = self.rows * self.rowheight rect = self.create_rectangle(x1+w/2,y1+w/2,x2,y2+w/2, outline='blue',width=w, tag='colrect') return def drawMultipleRows(self, rowlist): """Draw more than one row selection""" self.delete('multiplesel') for r in rowlist: if r not in self.visiblerows or r > self.rows-1: continue x1,y1,x2,y2 = self.getCellCoords(r,0) x2 = self.tablewidth rect = self.create_rectangle(x1,y1,x2,y2, fill=self.multipleselectioncolor, outline=self.rowselectedcolor, tag=('multiplesel','rowrect')) self.lower('multiplesel') self.lower('fillrect') return def drawMultipleCells(self): """Draw an outline box for multiple cell selection""" self.delete('multicellrect') rows = self.multiplerowlist cols = self.multiplecollist w=2 x1,y1,a,b = self.getCellCoords(rows[0],cols[0]) c,d,x2,y2 = self.getCellCoords(rows[len(rows)-1],cols[len(cols)-1]) rect = self.create_rectangle(x1+w/2,y1+w/2,x2,y2, outline='blue',width=w,activefill='red',activestipple='gray25', tag='multicellrect') return def drawTooltip(self, row, col): """Draw a tooltip showing contents of cell""" x1,y1,x2,y2 = self.getCellCoords(row,col) w=x2-x1 text = self.model.getValueAt(row,col) if isinstance(text, dict): if 'link' in text: text = text['link'] # If text is a number we make it a string if type(text) is float or type is int: text = str(text) if text == None or text == '' or len(str(text))<=10: return sfont = font.Font(family='Arial', size=12,weight='bold') obj = self.create_text(x1+w/1.5,y2,text=text, anchor='w', font=sfont,tag='tooltip') box = self.bbox(obj) x1=box[0]-1 y1=box[1]-1 x2=box[2]+1 y2=box[3]+1 rect = self.create_rectangle(x1+1,y1+1,x2+1,y2+1,tag='tooltip',fill='black') rect2 = self.create_rectangle(x1,y1,x2,y2,tag='tooltip',fill='lightyellow') self.lift(obj) return def setcellbackgr(self): clr = self.getaColor(self.cellbackgr) if clr != None: self.cellbackgr = clr return def setgrid_color(self): clr = self.getaColor(self.grid_color) if clr != None: self.grid_color = clr return def setrowselectedcolor(self): clr = self.getaColor(self.rowselectedcolor) if clr != None: self.rowselectedcolor = clr return def getaColor(self, oldcolor): ctuple, newcolor = tkColorChooser.askcolor(title='pick a color', initialcolor=oldcolor, parent=self.parentframe) if ctuple == None: return None return str(newcolor) #--- Preferences stuff --- def showtablePrefs(self, prefs=None): """Show table options dialog using an instance of prefs""" #self.prefs = prefs if self.prefs == None: self.loadPrefs() self.prefswindow=Toplevel() x,y,w,h = self.getGeometry(self.master) self.prefswindow.geometry('+%s+%s' %(int(x+w/2),int(y+h/2))) self.prefswindow.title('Preferences') self.prefswindow.resizable(width=FALSE, height=FALSE) frame1=Frame(self.prefswindow) frame1.pack(side=LEFT) frame2=Frame(self.prefswindow) frame2.pack() def close_prefsdialog(): self.prefswindow.destroy() row=0 Checkbutton(frame1, text="Show horizontal lines", variable=self.horizlinesvar, onvalue=1, offvalue=0).grid(row=row,column=0, columnspan=2, sticky='news') row=row+1 Checkbutton(frame1, text="Show vertical lines", variable=self.vertlinesvar, onvalue=1, offvalue=0).grid(row=row,column=0, columnspan=2, sticky='news') row=row+1 Checkbutton(frame1, text="Alternate Row Color", variable=self.alternaterowsvar, onvalue=1, offvalue=0).grid(row=row,column=0, columnspan=2, sticky='news') row=row+1 lblrowheight = Label(frame1,text='Row Height:') lblrowheight.grid(row=row,column=0,padx=3,pady=2) rowheightentry = Spinbox(frame1,from_=12,to=50,width=10, textvariable=self.rowheightvar) rowheightentry.grid(row=row,column=1,padx=3,pady=2) row=row+1 lblcellwidth = Label(frame1,text='Cell Width:') lblcellwidth.grid(row=row,column=0,padx=3,pady=2) cellwidthentry = Spinbox(frame1,from_=20,to=500, width=10, textvariable=self.cellwidthvar) cellwidthentry.grid(row=row,column=1,padx=3,pady=2) row=row+1 lbllinewidth = Label(frame1,text='Line Width:') lbllinewidth.grid(row=row,column=0,padx=3,pady=2) linewidthentry = Spinbox(frame1,from_=0,to=10,width=10, textvariable=self.linewidthvar) linewidthentry.grid(row=row,column=1,padx=3,pady=2) row=row+1 rowhdrwidth = Label(frame1,text='Row Header Width:') rowhdrwidth.grid(row=row,column=0,padx=3,pady=2) rowhdrentry = Spinbox(frame1,from_=0,to=300, width=10, textvariable=self.rowheaderwidthvar) rowhdrentry.grid(row=row,column=1,padx=3,pady=2) row=row+1 #fonts fts = self.getFonts() self.fontvar = StringVar() self.fontvar.set(self.prefs.get('celltextfont')) def setFont(*args): self.thefont = self.fontvar.get() return self.fontbox = Combobox(frame2, values=(fts), text='Font:', height = 6, textvariable=self.fontvar) self.fontvar.trace('w', setFont) Label(frame2,text='Font:').grid(row=row,column=0,padx=3,pady=2) self.fontbox.grid(row=row,column=1, columnspan=2, sticky='nes', padx=3,pady=2) row=row+1 lblfontsize=Label(frame2,text='Text Size:') lblfontsize.grid(row=row,column=0,padx=3,pady=2) fontsizeentry = Spinbox(frame2,from_=6,to=50, width=20, textvariable=self.celltextsizevar) fontsizeentry.grid(row=row,column=1, sticky='wens',padx=3,pady=2) row=row+1 #cell alignment lbl=Label(frame2,text='Alignment:') lbl.grid(row=row,column=0,padx=3,pady=2) alignentry_button = Menubutton(frame2,textvariable=self.cellalignvar, width=16) alignentry_menu = Menu(alignentry_button,tearoff=0) alignentry_button['menu'] = alignentry_menu alignments=['w','e','center'] for text in alignments: alignentry_menu.add_radiobutton(label=text, variable=self.cellalignvar, value=text, indicatoron=1) alignentry_button.grid(row=row,column=1, sticky='nes', padx=3,pady=2) row=row+1 #colors style = Style() style.configure("cb.TButton", background=self.cellbackgr) cellbackgrbutton = Button(frame2, text='table background', style="cb.TButton", #bg=self.cellbackgr, command=self.setcellbackgr) cellbackgrbutton.grid(row=row,column=0,columnspan=2, sticky='news',padx=3,pady=2) row=row+1 style = Style() style.configure("gc.TButton", background=self.grid_color) grid_colorbutton = Button(frame2, text='grid color', style="gc.TButton", #bg=self.grid_color, command=self.setgrid_color) grid_colorbutton.grid(row=row,column=0,columnspan=2, sticky='news',padx=3,pady=2) row=row+1 style = Style() style.configure("rhc.TButton", background=self.rowselectedcolor) rowselectedcolorbutton = Button(frame2, text='row highlight color', style="rhc.TButton", command=self.setrowselectedcolor) rowselectedcolorbutton.grid(row=row,column=0,columnspan=2, sticky='news',padx=3,pady=2) row=row+1 frame=Frame(self.prefswindow) frame.pack() # Apply Button b = Button(frame, text="Apply Settings", command=self.applyPrefs) b.grid(row=row,column=1,columnspan=2,sticky='news',padx=4,pady=4) # Close button c=Button(frame,text='Close', command=close_prefsdialog) c.grid(row=row,column=0,sticky='news',padx=4,pady=4) self.prefswindow.focus_set() self.prefswindow.grab_set() self.prefswindow.wait_window() return self.prefswindow def getFonts(self): fonts = set(list(font.families())) fonts = sorted(list(fonts)) return fonts def loadPrefs(self, prefs=None): """Load table specific prefs from the prefs instance used if they are not present, create them.""" if prefs == None: prefs=Preferences('Table',{'check_for_update':1}) self.prefs = prefs defaultprefs = {'horizlines':self.horizlines, 'vertlines':self.vertlines, 'alternaterows':self.alternaterows, 'rowheight':self.rowheight, 'cellwidth':100, 'autoresizecols': 0, 'align': 'w', 'celltextsize':11, 'celltextfont':'Arial', 'cellbackgr': self.cellbackgr, 'grid_color': self.grid_color, 'linewidth' : self.linewidth, 'rowselectedcolor': self.rowselectedcolor, 'rowheaderwidth': self.rowheaderwidth} #print (self.prefs.__dict__) for prop in defaultprefs: if not prop in self.prefs.prefs: #print (defaultprefs[prop]) if defaultprefs[prop] != 'None': self.prefs.set(prop, defaultprefs[prop]) self.defaultprefs = defaultprefs #Create tkvars for dialog self.rowheightvar = IntVar() self.rowheightvar.set(self.prefs.get('rowheight')) self.rowheight = self.rowheightvar.get() self.cellwidthvar = IntVar() self.cellwidthvar.set(self.prefs.get('cellwidth')) self.cellwidth = self.cellwidthvar.get() self.cellalignvar = StringVar() self.cellalignvar.set(self.prefs.get('align')) self.align = self.cellalignvar.get() self.linewidthvar = IntVar() self.linewidthvar.set(self.prefs.get('linewidth')) self.horizlinesvar = IntVar() self.horizlinesvar.set(self.prefs.get('horizlines')) self.vertlinesvar = IntVar() self.vertlinesvar.set(self.prefs.get('vertlines')) self.alternaterowsvar = IntVar() self.alternaterowsvar.set(self.prefs.get('alternaterows')) self.celltextsizevar = IntVar() self.celltextsizevar.set(self.prefs.get('celltextsize')) self.cellbackgr = self.prefs.get('cellbackgr') self.grid_color = self.prefs.get('grid_color') self.rowselectedcolor = self.prefs.get('rowselectedcolor') self.fontsize = self.celltextsizevar.get() self.thefont = (self.prefs.get('celltextfont'), self.prefs.get('celltextsize')) self.rowheaderwidthvar = IntVar() self.rowheaderwidthvar.set(self.prefs.get('rowheaderwidth')) self.rowheaderwidth = self.rowheaderwidthvar.get() return def savePrefs(self): """Save and set the prefs""" try: self.prefs.set('horizlines', self.horizlinesvar.get()) self.horizlines = self.horizlinesvar.get() self.prefs.set('vertlines', self.vertlinesvar.get()) self.vertlines = self.vertlinesvar.get() self.prefs.set('alternaterows', self.alternaterowsvar.get()) self.alternaterows = self.alternaterowsvar.get() self.prefs.set('rowheight', self.rowheightvar.get()) self.rowheight = self.rowheightvar.get() self.prefs.set('cellwidth', self.cellwidthvar.get()) self.cellwidth = self.cellwidthvar.get() self.prefs.set('align', self.cellalignvar.get()) self.align = self.cellalignvar.get() self.prefs.set('linewidth', self.linewidthvar.get()) self.linewidth = self.linewidthvar.get() self.prefs.set('celltextsize', self.celltextsizevar.get()) self.prefs.set('celltextfont', self.fontvar.get()) self.prefs.set('cellbackgr', self.cellbackgr) self.prefs.set('grid_color', self.grid_color) self.prefs.set('rowselectedcolor', self.rowselectedcolor) self.prefs.set('rowheaderwidth', self.rowheaderwidth) self.rowheaderwidth = self.rowheaderwidthvar.get() self.thefont = (self.prefs.get('celltextfont'), self.prefs.get('celltextsize')) self.fontsize = self.prefs.get('celltextsize') except ValueError as e: print (e) pass self.prefs.save_prefs() return def applyPrefs(self): """Apply prefs to the table by redrawing""" self.savePrefs() self.redrawTable() return def AskForColorButton(self, frame, text, func): def SetColor(): ctuple, variable = tkColorChooser.askcolor(title='pick a color', initialcolor=self.cellbackgr) return bgcolorbutton = Button(frame, text=text,command=SetColor) return bgcolorbutton def check_hyperlink(self,event=None): """Check if a hyperlink was clicked""" row = self.get_row_clicked(event) col = self.get_col_clicked(event) #absrow = self.get_AbsoluteRow(row) recdata = self.model.getValueAt(row, col) try: link = recdata['link'] import webbrowser webbrowser.open(link,autoraise=1) except: pass return def show_progressbar(self,message=None): """Show progress bar window for loading of data""" progress_win=Toplevel() # Open a new window progress_win.title("Please Wait") #progress_win.geometry('+%d+%d' %(self.parentframe.rootx+200,self.parentframe.rooty+200)) #force on top progress_win.grab_set() progress_win.transient(self.parentframe) if message==None: message='Working' lbl = Label(progress_win,text=message,font='Arial 16') lbl.grid(row=0,column=0,columnspan=2,sticky='news',padx=6,pady=4) progrlbl = Label(progress_win,text='Progress:') progrlbl.grid(row=1,column=0,sticky='news',padx=2,pady=4) import ProgressBar self.bar = ProgressBar.ProgressBar(progress_win) self.bar.frame.grid(row=1,column=1,columnspan=2,padx=2,pady=4) return progress_win def updateModel(self, model): """Call this method to update the table model""" self.model = model self.rows = self.model.getRowCount() self.cols = self.model.getColumnCount() self.tablewidth = (self.cellwidth)*self.cols self.tablecolheader = ColumnHeader(self.parentframe, self) self.tablerowheader = RowHeader(self.parentframe, self) self.createTableFrame() return def new(self): """Clears all the data and makes a new table""" mpDlg = MultipleValDialog(title='Create new table', initialvalues=(10, 4), labels=('rows','columns'), types=('int','int'), parent=self.parentframe) if mpDlg.result == True: rows = mpDlg.results[0] cols = mpDlg.results[1] model = TableModel(rows=rows,columns=cols) self.updateModel(model) return def load(self, filename=None): """load from a file""" if filename == None: filename = filedialog.askopenfilename(parent=self.master, defaultextension='.table', initialdir=os.getcwd(), filetypes=[("pickle","*.table"), ("All files","*.*")]) if not os.path.exists(filename): print ('file does not exist') return if filename: self.model.load(filename) self.redrawTable() return def save(self, filename=None): """Save model to pickle file""" if filename == None: filename = filedialog.asksaveasfilename(parent=self.master, defaultextension='.table', initialdir=os.getcwd(), filetypes=[("pickle","*.table"), ("All files","*.*")]) if filename: self.model.save(filename) return def importTable(self): self.importCSV() def importCSV(self, filename=None): """Import from csv file""" if filename is None: from .Tables_IO import TableImporter importer = TableImporter() importdialog = importer.import_Dialog(self.master) self.master.wait_window(importdialog) model = TableModel() model.importDict(importer.data) else: model = TableModel() model.importCSV(filename) self.updateModel(model) return def exportTable(self, filename=None): """Do a simple export of the cell contents to csv""" from .Tables_IO import TableExporter exporter = TableExporter() exporter.ExportTableData(self) return @classmethod def checkOSType(cls): """Check the OS we are in""" ostyp='' var_s=['OSTYPE','OS'] for var in var_s: if var in os.environ: try: ostyp = str.lower(os.environ[var]) except: ostyp = os.environ[var].lower() ostyp=ostyp.lower() if ostyp.find('windows')!=-1: ostyp='windows' elif ostyp.find('darwin')!=-1 or ostyp.find('apple')!=-1: ostyp='mac' elif ostyp.find('linux')!=-1: ostyp='linux' else: ostyp='unknown' try: info=os.uname() except: pass ostyp=info[0].lower() if ostyp.find('darwin')!=-1: ostyp='mac' return ostyp def getGeometry(self, frame): """Get frame geometry""" return frame.winfo_rootx(), frame.winfo_rooty(), frame.winfo_width(), frame.winfo_height() class ColumnHeader(Canvas): """Class that takes it's size and rendering from a parent table and column names from the table model.""" def __init__(self, parent=None, table=None): Canvas.__init__(self, parent, bg='gray25', width=500, height=20) self.thefont='Arial 14' if table != None: self.table = table self.height = 20 self.model = self.table.getModel() self.config(width=self.table.width) #self.colnames = self.model.columnNames self.columnlabels = self.model.columnlabels self.bind('',self.handle_left_click) self.bind("", self.handle_left_release) self.bind('', self.handle_mouse_drag) self.bind('', self.handle_mouse_move) self.bind('', self.handle_left_shift_click) if self.table.ostyp=='mac': #For mac we bind Shift, left-click to right click self.bind("", self.handle_right_click) self.bind('',self.handle_right_click) else: self.bind("", self.handle_right_click) self.thefont = self.table.thefont return def redraw(self): cols = self.model.getColumnCount() self.tablewidth=self.table.tablewidth self.configure(scrollregion=(0,0, self.table.tablewidth+self.table.x_start, self.height)) self.delete('gridline','text') self.delete('rect') self.atdivider = None align='w' pad=4 h=self.height #x_start=self.table.x_start if cols == 0: return for col in self.table.visiblecols: colname = self.model.columnNames[col] if not colname in self.model.columnlabels: self.model.columnlabels[colname]=colname collabel = self.model.columnlabels[colname] if colname in self.model.columnwidths: w = self.model.columnwidths[colname] else: w = self.table.cellwidth x = self.table.col_positions[col] if len(collabel)>w/10: collabel = collabel[0:int(w/12)]+'.' line = self.create_line(x, 0, x, h, tag=('gridline', 'vertline'), fill='white', width=2) self.create_text(x+pad,h/2, text=collabel, anchor=align, fill='white', font=self.thefont, tag='text') x=self.table.col_positions[col+1] self.create_line(x,0, x,h, tag='gridline', fill='white', width=2) return def handle_left_click(self,event): """Does cell selection when mouse is clicked on canvas""" self.delete('rect') self.table.delete('entry') self.table.delete('multicellrect') colclicked = self.table.get_col_clicked(event) if colclicked == None: return #set all rows selected self.table.allrows = True self.table.setSelectedCol(colclicked) if self.atdivider == 1: return self.drawRect(self.table.currentcol) #also draw a copy of the rect to be dragged self.draggedcol = None self.drawRect(self.table.currentcol, tag='dragrect', color='red', outline='white') if hasattr(self, 'rightmenu'): self.rightmenu.destroy() #finally, draw the selected col on the table self.table.drawSelectedCol() return def handle_left_release(self,event): """When mouse released implement resize or col move""" self.delete('dragrect') if self.atdivider == 1: #col = self.table.get_col_clicked(event) x=int(self.canvasx(event.x)) col = self.table.currentcol x1,y1,x2,y2 = self.table.getCellCoords(0,col) newwidth=x - x1 if newwidth < 5: newwidth=5 self.table.resizeColumn(col, newwidth) self.table.delete('resizeline') self.delete('resizeline') self.delete('resizesymbol') self.atdivider = 0 return self.delete('resizesymbol') #move column if self.draggedcol != None and self.table.currentcol != self.draggedcol: self.model.moveColumn(self.table.currentcol, self.draggedcol) self.table.setSelectedCol(self.draggedcol) self.table.redrawTable() self.table.drawSelectedCol(self.table.currentcol) self.drawRect(self.table.currentcol) return def handle_mouse_drag(self, event): """Handle column drag, will be either to move cols or resize""" x=int(self.canvasx(event.x)) if self.atdivider == 1: self.table.delete('resizeline') self.delete('resizeline') self.table.create_line(x, 0, x, self.table.rowheight*self.table.rows, width=2, fill='gray', tag='resizeline') self.create_line(x, 0, x, self.height, width=2, fill='gray', tag='resizeline') return else: w = self.table.cellwidth self.draggedcol = self.table.get_col_clicked(event) x1, y1, x2, y2 = self.coords('dragrect') x=int(self.canvasx(event.x)) y = self.canvasy(event.y) self.move('dragrect', x-x1-w/2, 0) return def within(self, val, l, d): """Utility funtion to see if val is within d of any items in the list l""" for v in l: if abs(val-v) <= d: return 1 return 0 def handle_mouse_move(self, event): """Handle mouse moved in header, if near divider draw resize symbol""" self.delete('resizesymbol') w=self.table.cellwidth h=self.height x_start=self.table.x_start #x = event.x x=int(self.canvasx(event.x)) if x > self.tablewidth+w: return #if event x is within x pixels of divider, draw resize symbol if x!=x_start and self.within(x, self.table.col_positions, 4): col = self.table.get_col_clicked(event) if col == None: return self.draw_resize_symbol(col) self.atdivider = 1 else: self.atdivider = 0 return def handle_right_click(self, event): """respond to a right click""" self.handle_left_click(event) if self.table.read_only == False: self.rightmenu = self.popupMenu(event) return def handle_right_release(self, event): self.rightmenu.destroy() return def handle_left_shift_click(self, event): """Handle shift click, for selecting multiple cols""" self.table.delete('colrect') self.delete('rect') currcol = self.table.currentcol colclicked = self.table.get_col_clicked(event) if colclicked > currcol: self.table.multiplecollist = range(currcol, colclicked+1) elif colclicked < currcol: self.table.multiplecollist = range(colclicked, currcol+1) else: return #print self.table.multiplecollist for c in self.table.multiplecollist: self.drawRect(c, delete=0) self.table.drawSelectedCol(c, delete=0) return def popupMenu(self, event): """Add left and right click behaviour for column header""" colname = self.model.columnNames[self.table.currentcol] collabel = self.model.columnlabels[colname] currcol = self.table.currentcol popupmenu = Menu(self, tearoff = 0) def popupFocusOut(event): popupmenu.unpost() popupmenu.add_command(label="Rename Column", command=self.relabel_Column) popupmenu.add_command(label="Sort by "+ collabel, command=lambda : self.table.sortTable(currcol)) popupmenu.add_command(label="Sort by "+ collabel +' (descending)', command=lambda : self.table.sortTable(currcol,reverse=1)) popupmenu.add_command(label="Delete This Column", command=self.table.deleteColumn) popupmenu.add_command(label="Add New Column", command=self.table.addColumn) popupmenu.bind("", popupFocusOut) #self.bind("", popupFocusOut) popupmenu.focus_set() popupmenu.post(event.x_root, event.y_root) return popupmenu def relabel_Column(self): col=self.table.currentcol ans = simpledialog.askstring("New column name?", "Enter new name:") if ans !=None: if ans == '': messagebox.showwarning("Error", "Name should not be blank.") return else: self.model.relabel_Column(col, ans) self.redraw() return def draw_resize_symbol(self, col): """Draw a symbol to show that col can be resized when mouse here""" self.delete('resizesymbol') w=self.table.cellwidth h=self.height wdth=1 hfac1=0.2 hfac2=0.4 x_start=self.table.x_start x1,y1,x2,y2 = self.table.getCellCoords(0,col) self.create_polygon(x2-3,h/4, x2-10,h/2, x2-3,h*3/4, tag='resizesymbol', fill='white', outline='gray', width=wdth) self.create_polygon(x2+2,h/4, x2+10,h/2, x2+2,h*3/4, tag='resizesymbol', fill='white', outline='gray', width=wdth) return def drawRect(self,col, tag=None, color=None, outline=None, delete=1): """User has clicked to select a col""" if tag==None: tag='rect' if color==None: color='#0099CC' if outline==None: outline='gray25' if delete == 1: self.delete(tag) w=2 x1,y1,x2,y2 = self.table.getCellCoords(0,col) rect = self.create_rectangle(x1,y1-w,x2,self.height, fill=color, outline=outline, width=w, stipple='gray50', tag=tag) self.lower(tag) return class RowHeader(Canvas): """Class that displays the row headings on the table takes it's size and rendering from the parent table This also handles row/record selection as opposed to cell selection""" def __init__(self, parent=None, table=None, width=40): Canvas.__init__(self, parent, bg='gray75', width=width, height=None) if table != None: self.table = table self.width = width self.x_start = 0 self.inset = 1 self.config(height = self.table.height) self.startrow = self.endrow = None self.model = self.table.getModel() self.bind('',self.handle_left_click) self.bind("", self.handle_left_release) self.bind("", self.handle_left_ctrl_click) self.bind('',self.handle_right_click) self.bind('', self.handle_mouse_drag) #self.bind('', self.handle_left_shift_click) return def redraw(self, align='w', showkeys=False): """Redraw row header""" self.height = self.table.rowheight * self.table.rows+10 self.configure(scrollregion=(0,0, self.width, self.height)) self.delete('rowheader','text') self.delete('rect') w = float(self.width) h = self.table.rowheight x = self.x_start+w/2 if align == 'w': x = x-w/2+3 elif align == 'e': x = x+w/2-3 for row in self.table.visiblerows: if showkeys == True: text = self.model.getRecName(row) else: text = row+1 x1,y1,x2,y2 = self.table.getCellCoords(row,0) self.create_rectangle(0,y1,w-1,y2, fill='gray75', outline='white', width=1, tag='rowheader') self.create_text(x,y1+h/2, text=text, fill='black', font=self.table.thefont, tag='text', anchor=align) return def setWidth(self, w): """Set width""" self.width = w self.redraw() return def clearSelected(self): self.delete('rect') return def handle_left_click(self, event): rowclicked = self.table.get_row_clicked(event) self.startrow = rowclicked if 0 <= rowclicked < self.table.rows: self.delete('rect') self.table.delete('entry') self.table.delete('multicellrect') #set row selected self.table.setSelectedRow(rowclicked) self.table.drawSelectedRow() self.drawSelectedRows(self.table.currentrow) return def handle_left_release(self,event): return def handle_left_ctrl_click(self, event): """Handle ctrl clicks - for multiple row selections""" rowclicked = self.table.get_row_clicked(event) multirowlist = self.table.multiplerowlist if 0 <= rowclicked < self.table.rows: if rowclicked not in multirowlist: multirowlist.append(rowclicked) else: multirowlist.remove(rowclicked) self.table.drawMultipleRows(multirowlist) self.drawSelectedRows(multirowlist) return def handle_right_click(self,event): return '''def handle_mouse_drag(self, event): """Handle mouse drag for mult row selection""" rowover = self.table.get_row_clicked(event) colover = self.table.get_col_clicked(event) if colover == None or rowover == None: return''' def handle_mouse_drag(self, event): """Handle mouse moved with button held down, multiple selections""" if hasattr(self, 'cellentry'): self.cellentry.destroy() rowover = self.table.get_row_clicked(event) colover = self.table.get_col_clicked(event) if rowover == None: return if rowover >= self.table.rows or self.startrow > self.table.rows: return else: self.endrow = rowover #draw the selected rows if self.endrow != self.startrow: if self.endrow < self.startrow: rowlist=range(self.endrow, self.startrow+1) else: rowlist=range(self.startrow, self.endrow+1) self.drawSelectedRows(rowlist) self.table.multiplerowlist = rowlist self.table.drawMultipleRows(rowlist) else: self.table.multiplerowlist = [] self.table.multiplerowlist.append(rowover) self.drawSelectedRows(rowover) self.table.drawMultipleRows(self.table.multiplerowlist) return def drawSelectedRows(self, rows=None): """Draw selected rows, accepts a list or integer""" self.delete('rect') if type(rows) is not list: rowlist=[] rowlist.append(rows) else: rowlist = rows for r in rowlist: if r not in self.table.visiblerows: continue self.drawRect(r, delete=0) return def drawRect(self, row=None, tag=None, color=None, outline=None, delete=1): """Draw a rect representing row selection""" if tag==None: tag='rect' if color==None: color='#0099CC' if outline==None: outline='gray25' if delete == 1: self.delete(tag) w=0 i = self.inset x1,y1,x2,y2 = self.table.getCellCoords(row, 0) rect = self.create_rectangle(0+i,y1+i,self.width-i,y2, fill=color, outline=outline, width=w, tag=tag) self.lift('text') return class AutoScrollbar(Scrollbar): """a scrollbar that hides itself if it's not needed. only works if you use the grid geometry manager.""" def set(self, lo, hi): if float(lo) <= 0.0 and float(hi) >= 1.0: # grid_remove is currently missing from Tkinter! self.tk.call("grid", "remove", self) else: self.grid() Scrollbar.set(self, lo, hi) #def pack(self, **kw): # raise TclError, "cannot use pack with this widget" #def place(self, **kw): # raise TclError, "cannot use place with this widget"