# Version 0.4
# Author: Miguel Martinez Lopez
# Email: Uncomment the next line to see my email
# print "Author's email: ", "61706c69636163696f6e616d656469646140676d61696c2e636f6d".decode("hex")
from Tkinter import *
from collections import defaultdict
import itertools
class PanedGrid(Frame):
def __init__(self, master, orient=HORIZONTAL, minsize=20, columnWidths = {}, rowHeights={}, fixedPanes = []):
Frame.__init__(self,master)
if orient in (HORIZONTAL, VERTICAL):
self.orient = orient
else:
raise Exception("orient must be 'horizontal' or 'vertical, not '%s'." % orient)
if type(minsize) is int:
self.minsize = minsize
else:
raise Exception("Minsize must be an integer.")
self.fixedPanes = set(fixedPanes)
self.__packSideBetweenPanes = LEFT if self.orient == HORIZONTAL else TOP
self.__packSideInsidePane = TOP if self.orient == HORIZONTAL else LEFT
self.__cellFill = X if self.orient == HORIZONTAL else Y
self.userDefinedWidths = columnWidths
self.userDefinedHeights = rowHeights
self.__panes = {}
self.builded = False
self.clear_table()
def clear_table(self):
if not self.builded:
self.__gridOfWidgets = {}
self.__rows = defaultdict(set)
self.__columns = defaultdict(set)
self.__rowHeights = {}
self.__columnWidths = {}
self.userDefinedWidths = {}
self.userDefinedHeights = {}
else:
raise Exception("You can't clear the table one this is builded.")
def clear_definedWidths(self):
self.userDefinedWidths = {}
def clear_definedHeights(self):
self.userDefinedHeights = {}
def rowIndices(self):
return self.__rows.keys()
def columnIndices(self):
return self.__columns.keys()
def numberOfRows(self):
return len(self.__rows)
def numberOfColumns(self):
return len(self.__columns)
def update_cell(self, coordinates, widget):
if not self.builded:
raise Exception("First you must to build the panedgrid.")
if coordinates in self.__gridOfWidgets:
master = self.__gridOfWidgets[coordinates].pack_info()['in']
self.__gridOfWidgets[coordinates].destroy()
self.__gridOfWidgets[coordinates] = widget
widget.pack(in_=master, expand=YES, fill=BOTH)
else:
raise Exception("There is no widget with coordiantes: %s." % coordinates)
def build(self):
if self.builded:
raise Exception("You have just builded the grid before.")
if not self.__gridOfWidgets: return
if self.calculate_dimensions:
self.__calculate_list_of_cell_widths_and_heights()
self.panedwindows = []
listOfCellCoordinates = list(itertools.product(self.__rows, self.__columns))
if self.orient == HORIZONTAL:
listOfCellCoordinates.sort(key = lambda item: item[1])
else:
listOfCellCoordinates.sort()
pane_index = self.__columns[0] if self.orient == HORIZONTAL else self.__rows[0]
position_of_main_index = 1 if self.orient == HORIZONTAL else 0
newPanedWindow = True
for cell_coordinates in listOfCellCoordinates:
main_index = cell_coordinates[position_of_main_index]
if pane_index != main_index:
# Creating a new pane
pane_index = main_index
if pane_index in self.fixedPanes:
frameOfPane = Frame(self)
frameOfPane.pack(side=self.__packSideBetweenPanes)
newPanedWindow = True
else:
if newPanedWindow:
paneMaster = PanedWindow(self,orient=self.orient, bd=0, sashwidth=3)
paneMaster.index_range = (pane_index, pane_index + 1 )
paneMaster.pack(side=self.__packSideBetweenPanes)
self.panedwindows.append(paneMaster)
newPanedWindow = False
else:
first_index = paneMaster.index_range[0]
last_index = pane_index + 1
paneMaster.index_range = (first_index, last_index)
frameOfPane = Frame(paneMaster)
frameOfPane.pack()
paneMaster.add(frameOfPane)
paneMaster.paneconfigure(frameOfPane, minsize=self.minsize)
sizeOfPane = self.__columnWidths[pane_index] if self.orient == HORIZONTAL else self.__rowHeights[pane_index]
if self.orient == HORIZONTAL:
frameOfPane.configure(width=sizeOfPane, height=self.tableHeight)
else:
frameOfPane.configure(width=self.tableWidth, height=sizeOfPane)
frameOfPane.pack_propagate(False)
self.__panes[pane_index] = frameOfPane
cellFrame = Frame(frameOfPane, bd=1, relief=SUNKEN)
cellFrame.pack(side=self.__packSideInsidePane, expand=YES, fill=self.__cellFill)
cellFrame.pack_propagate(False)
widget = self.__gridOfWidgets[cell_coordinates]
if widget:
widget.lift()
widget.pack(in_=cellFrame, expand=YES, fill=BOTH)
if self.orient == HORIZONTAL:
cellFrame.configure(
height=self.__rowHeights[ cell_coordinates[0] ],
)
else:
cellFrame.configure(
width=self.__columnWidths[ cell_coordinates[1] ]
)
self.builded = True
def __calculate_list_of_cell_widths_and_heights(self):
self.__rowHeights = self.userDefinedHeights.copy()
self.__columnWidths = self.userDefinedWidths.copy()
for coordinate, widget in self.__gridOfWidgets.items():
i , j = coordinate
if i not in self.userDefinedHeights:
self.__rowHeights[i] = max(self.__rowHeights.get(i,0),widget.winfo_reqheight())
if j not in self.userDefinedWidths:
self.__columnWidths[j] = max(self.__columnWidths.get(j,0),widget.winfo_reqwidth())
self.calculate_dimensions = False
@property
def tableHeight(self):
return sum(self.__rowHeights.values())
@property
def tableWidth(self):
return sum(self.__columnWidths.values())
def rowconfigure(self,row, height=None, fixed = None):
if row not in self.__rows:
raise Exception("Row %d doesn't exists." % column)
if height is not None:
if type(height) is not int: raise Exception("The height must be an integer:%s"%height)
self.userDefinedHeights[row] = height
if self.orient == VERTICAL:
self.__panes[row].configure(height=height)
else:
for column in self.__rows[row]:
cellFrame = self.__gridOfWidgets[row,column].pack_info()['in']
cellFrame.configure(height=height)
if fixed is not None:
if self.orient == HORIZONTAL:
raise Exception("You can't set fixed property on a row because 'orient' is VERTICAL.")
self.fix_pane(index, fixed)
def columnconfigure(self,column, width=None, fixed = None):
if column not in self.__columns:
raise Exception("Column %d doesn't exists." % column)
if width is not None:
if type(width) is not int: raise Exception("The width must be an integer:%s"%width)
self.userDefinedWidths[column] = width
if self.orient == HORIZONTAL:
self.__panes[column].configure(width=width)
else:
for row in self.__columns[column]:
cellFrame = self.__gridOfWidgets[row,column].pack_info()['in']
cellFrame.configure(width=width)
if fixed is not None:
if self.orient == VERTICAL:
raise Exception("You can't set fixed property on a column because 'orient' is HORIZONTAL.")
self.fix_pane(index, fixed)
def fix_pane(index, fixed=True):
if self.builded:
raise Exception("You can't modify the fixed of the panes once you builded the grid.")
if fixed == True:
self.fixedPanes.add(index)
elif fixed == False:
self.fixedPanes.discard(index)
else:
raise Exception("fixed must be 'True' or 'False'.")
def fix_all_panes(self):
if self.builded:
raise Exception("You can't modify the fixed of the panes once you builded the grid.")
fixedPanes_list = self.__columns.keys() if self.orient == HORIZONTAL else self.__rows.keys()
self.fixedPanes = set(fixedPanes_list)
def realease_all_panes(self):
if self.builded:
raise Exception("You can't modify the fixed of the panes once you builded the grid.")
self.fixedPanes = set()
def is_a_fixed_pane(self, index ):
paneWidget = self.__panes[index]
parent = self.nametowidget( paneWidgetid.winfo_parent() )
if parent.__class__.name == 'PanedWindow':
return True
else:
return False
def get_row_height(self, row):
if self.calculate_dimensions:
self.__calculate_list_of_cell_widths_and_heights()
if self.orient == HORIZONTAL:
if self.builded:
return self.__panes[row].winfo_height()
else:
return self.__rowHeights[row]
else:
return self.__rowHeights[row]
def get_column_width(self, column):
if self.calculate_dimensions:
self.__calculate_list_of_cell_widths_and_heights()
if self.orient == HORIZONTAL:
return self.__columnWidths[column]
else:
if self.builded:
return self.__panes[column].winfo_width()
else:
return self.__columnWidths[column]
def __getitem__(self, coordinates):
return self.__gridOfWidgets.get(coordinates)
def __setitem__(self,coordinates, widget):
if self.builded:
raise Exception("The panedgrid was builded. Use cell_update() instead.")
self.__gridOfWidgets[coordinates] = widget
i,j = coordinates
self.__rows[i].add(j)
self.__columns[j].add(i)
self.calculate_dimensions = True
def __delitem__(self, coordinates ):
if self.builded:
raise Exception("The panedgrid was builded. Use cell_update() instead.")
del self.__gridOfWidgets[coordinates]
i,j = coordinates
self.__rows[i].discard(j)
if not self.__rows[i]: del self.__rows[i]
self.__columns[j].discard(i)
if not self.__columns[j]: del self.__columns[j]
self.calculate_dimensions = True
def grid(self, *args, **kargs):
if not self.builded: self.build()
Frame.grid(self,*args, **kargs)
def pack(self, *args, **kargs):
if not self.builded: self.build()
Frame.pack(self,*args, **kargs)
def test():
import random
root = Tk()
def table_of_random_widgets(master, numRows,numColumns, **kwargs):
import ttk
demo = PanedGrid(master, **kwargs)
tkinterWidgets = (
(Label, {'text':'This is a label'}),
(Label, {'text':'This is another label', 'bg':'yellow'}),
(Checkbutton,{}),
(Button, {'text':'Click me'}),
(ttk.Combobox, {'values':('item1', 'item2','item3','item4')})
)
for i in range(numRows):
for j in range(numColumns):
widgetClass, args = random.choice(tkinterWidgets)
widget = widgetClass(demo,**args)
demo[i,j] = widget
return demo
Label(root, text="orient=VERTICAL, minsize = 20, fixed rows = [2,5]").pack(anchor=NW, pady=6, padx='2m')
table = table_of_random_widgets(root, numRows=6,numColumns=5, orient=VERTICAL, minsize=20, fixedPanes=[2,5])
table.pack(anchor=NW,pady=2, padx='2m')
emptySpace = Frame(root, height =20)
emptySpace.pack()
Label(root, text='''orient=HORIZONTAL, minsize = 30, fixed columns = [2,5], columnWidths={0:200, 2: 150, 3:250}, rowHeights={0:150}\nOn this table we will update later the cell (1,2)''', justify=LEFT).pack(anchor=NW, pady=6, padx='2m')
table = table_of_random_widgets(root, numRows=3,numColumns=6, minsize=30, fixedPanes=[2,5], columnWidths={0:200, 2: 150, 3:250}, rowHeights={0:90})
table.pack(anchor=NW,pady=2, padx='2m')
table.update_cell((1,2), Label(table,text="Cell updated",bg="green") )
emptySpace = Frame(root, height =20)
emptySpace.pack()
print "On the last table:"
print "\tnumber of rows: ", table.numberOfRows()
print "\tnumber of columns: ", table.numberOfColumns()
root.mainloop()
if __name__ == '__main__':
test()
Diff to Previous Revision
--- revision 10 2014-06-04 15:20:48
+++ revision 11 2014-06-14 02:11:09
@@ -1,14 +1,13 @@
-# Version 0.3
+# Version 0.4
# Author: Miguel Martinez Lopez
# Email: Uncomment the next line to see my email
# print "Author's email: ", "61706c69636163696f6e616d656469646140676d61696c2e636f6d".decode("hex")
from Tkinter import *
-from collections import OrderedDict, defaultdict
+from collections import defaultdict
import itertools
-
-class PannedGrid(Frame):
+class PanedGrid(Frame):
def __init__(self, master, orient=HORIZONTAL, minsize=20, columnWidths = {}, rowHeights={}, fixedPanes = []):
Frame.__init__(self,master)
@@ -45,8 +44,8 @@
self.__rows = defaultdict(set)
self.__columns = defaultdict(set)
- self.rowHeightList = {}
- self.columnWidthList = {}
+ self.__rowHeights = {}
+ self.__columnWidths = {}
self.userDefinedWidths = {}
self.userDefinedHeights = {}
@@ -74,7 +73,7 @@
def update_cell(self, coordinates, widget):
if not self.builded:
- raise Exception("First you must to build the pannedgrid.")
+ raise Exception("First you must to build the panedgrid.")
if coordinates in self.__gridOfWidgets:
master = self.__gridOfWidgets[coordinates].pack_info()['in']
@@ -91,9 +90,10 @@
if not self.__gridOfWidgets: return
- self.pannedwindows = []
-
- self.__calculate_list_of_cell_widths_and_heights()
+ if self.calculate_dimensions:
+ self.__calculate_list_of_cell_widths_and_heights()
+
+ self.panedwindows = []
listOfCellCoordinates = list(itertools.product(self.__rows, self.__columns))
@@ -104,28 +104,34 @@
pane_index = self.__columns[0] if self.orient == HORIZONTAL else self.__rows[0]
- position_of_outer_index = 1 if self.orient == HORIZONTAL else 0
+ position_of_main_index = 1 if self.orient == HORIZONTAL else 0
newPanedWindow = True
for cell_coordinates in listOfCellCoordinates:
- outer_index = cell_coordinates[position_of_outer_index]
- if pane_index != outer_index:
+ main_index = cell_coordinates[position_of_main_index]
+ if pane_index != main_index:
# Creating a new pane
- pane_index = outer_index
-
- if outer_index in self.fixedPanes:
+ pane_index = main_index
+
+ if pane_index in self.fixedPanes:
frameOfPane = Frame(self)
frameOfPane.pack(side=self.__packSideBetweenPanes)
newPanedWindow = True
else:
if newPanedWindow:
- paneMaster = PanedWindow(self,orient=self.orient )
+ paneMaster = PanedWindow(self,orient=self.orient, bd=0, sashwidth=3)
+ paneMaster.index_range = (pane_index, pane_index + 1 )
+
paneMaster.pack(side=self.__packSideBetweenPanes)
- self.pannedwindows.append(paneMaster)
+ self.panedwindows.append(paneMaster)
newPanedWindow = False
+ else:
+ first_index = paneMaster.index_range[0]
+ last_index = pane_index + 1
+ paneMaster.index_range = (first_index, last_index)
frameOfPane = Frame(paneMaster)
frameOfPane.pack()
@@ -133,7 +139,7 @@
paneMaster.paneconfigure(frameOfPane, minsize=self.minsize)
- sizeOfPane = self.columnWidthList[outer_index] if self.orient == HORIZONTAL else self.rowHeightList[outer_index]
+ sizeOfPane = self.__columnWidths[pane_index] if self.orient == HORIZONTAL else self.__rowHeights[pane_index]
if self.orient == HORIZONTAL:
frameOfPane.configure(width=sizeOfPane, height=self.tableHeight)
@@ -156,34 +162,36 @@
if self.orient == HORIZONTAL:
cellFrame.configure(
- height=self.rowHeightList[ cell_coordinates[0] ],
+ height=self.__rowHeights[ cell_coordinates[0] ],
)
else:
cellFrame.configure(
- width=self.columnWidthList[ cell_coordinates[1] ]
+ width=self.__columnWidths[ cell_coordinates[1] ]
)
self.builded = True
def __calculate_list_of_cell_widths_and_heights(self):
- self.rowHeightList = self.userDefinedHeights.copy()
- self.columnWidthList = self.userDefinedWidths.copy()
+ self.__rowHeights = self.userDefinedHeights.copy()
+ self.__columnWidths = self.userDefinedWidths.copy()
for coordinate, widget in self.__gridOfWidgets.items():
i , j = coordinate
if i not in self.userDefinedHeights:
- self.rowHeightList[i] = max(self.rowHeightList.get(i,0),widget.winfo_reqheight())
+ self.__rowHeights[i] = max(self.__rowHeights.get(i,0),widget.winfo_reqheight())
if j not in self.userDefinedWidths:
- self.columnWidthList[j] = max(self.columnWidthList.get(j,0),widget.winfo_reqwidth())
+ self.__columnWidths[j] = max(self.__columnWidths.get(j,0),widget.winfo_reqwidth())
+
+ self.calculate_dimensions = False
@property
def tableHeight(self):
- return sum(self.rowHeightList.values())
+ return sum(self.__rowHeights.values())
@property
def tableWidth(self):
- return sum(self.columnWidthList.values())
+ return sum(self.__columnWidths.values())
def rowconfigure(self,row, height=None, fixed = None):
@@ -207,7 +215,7 @@
if self.orient == HORIZONTAL:
raise Exception("You can't set fixed property on a row because 'orient' is VERTICAL.")
- self.fixPane(index, fixed)
+ self.fix_pane(index, fixed)
def columnconfigure(self,column, width=None, fixed = None):
if column not in self.__columns:
@@ -229,9 +237,9 @@
if self.orient == VERTICAL:
raise Exception("You can't set fixed property on a column because 'orient' is HORIZONTAL.")
- self.fixPane(index, fixed)
-
- def fixPane(index, fixed=True):
+ self.fix_pane(index, fixed)
+
+ def fix_pane(index, fixed=True):
if self.builded:
raise Exception("You can't modify the fixed of the panes once you builded the grid.")
@@ -242,27 +250,72 @@
else:
raise Exception("fixed must be 'True' or 'False'.")
- def fixAllPanes():
+ def fix_all_panes(self):
if self.builded:
raise Exception("You can't modify the fixed of the panes once you builded the grid.")
fixedPanes_list = self.__columns.keys() if self.orient == HORIZONTAL else self.__rows.keys()
self.fixedPanes = set(fixedPanes_list)
- def realeaseAllPanes():
+ def realease_all_panes(self):
+ if self.builded:
+ raise Exception("You can't modify the fixed of the panes once you builded the grid.")
+
self.fixedPanes = set()
+
+ def is_a_fixed_pane(self, index ):
+ paneWidget = self.__panes[index]
+
+ parent = self.nametowidget( paneWidgetid.winfo_parent() )
+
+ if parent.__class__.name == 'PanedWindow':
+ return True
+ else:
+ return False
+
+ def get_row_height(self, row):
+ if self.calculate_dimensions:
+ self.__calculate_list_of_cell_widths_and_heights()
+
+ if self.orient == HORIZONTAL:
+ if self.builded:
+ return self.__panes[row].winfo_height()
+ else:
+ return self.__rowHeights[row]
+ else:
+ return self.__rowHeights[row]
+
+ def get_column_width(self, column):
+ if self.calculate_dimensions:
+ self.__calculate_list_of_cell_widths_and_heights()
+
+ if self.orient == HORIZONTAL:
+ return self.__columnWidths[column]
+ else:
+ if self.builded:
+ return self.__panes[column].winfo_width()
+ else:
+ return self.__columnWidths[column]
def __getitem__(self, coordinates):
return self.__gridOfWidgets.get(coordinates)
def __setitem__(self,coordinates, widget):
+ if self.builded:
+ raise Exception("The panedgrid was builded. Use cell_update() instead.")
+
self.__gridOfWidgets[coordinates] = widget
i,j = coordinates
self.__rows[i].add(j)
self.__columns[j].add(i)
+ self.calculate_dimensions = True
+
def __delitem__(self, coordinates ):
+ if self.builded:
+ raise Exception("The panedgrid was builded. Use cell_update() instead.")
+
del self.__gridOfWidgets[coordinates]
i,j = coordinates
@@ -273,6 +326,7 @@
self.__columns[j].discard(i)
if not self.__columns[j]: del self.__columns[j]
+ self.calculate_dimensions = True
def grid(self, *args, **kargs):
if not self.builded: self.build()
@@ -292,7 +346,7 @@
def table_of_random_widgets(master, numRows,numColumns, **kwargs):
import ttk
- demo = PannedGrid(master, **kwargs)
+ demo = PanedGrid(master, **kwargs)
tkinterWidgets = (
(Label, {'text':'This is a label'}),