1
2
3
4
5
6
7
8
9
10 """
11 Tkinter widgets for displaying multi-column listboxes and tables.
12 """
13
14 from Tkinter import *
15 import operator
16
17
18
19
20
22 """
23 A multi-column listbox, where the current selection applies to an
24 entire row. Based on the 'U{MultiListbox Tkinter widget
25 <http://code.activestate.com/recipes/52266/>}' recipe from the
26 Python Cookbook.
27
28 For the most part, C{MultiListbox}'s methods just delegate to its
29 contained listboxes. For any methods that do not have docstrings,
30 see C{Tkinter.Listbox} for a description of what that method does.
31 """
32
33
34
35
36
37 FRAME_CONFIG = dict(background='#888',
38 takefocus=True,
39 highlightthickness=1)
40
41
42 LABEL_CONFIG = dict(borderwidth=1, relief='raised',
43 font='helvetica -16 bold',
44 background='#444', foreground='white')
45
46
47 LISTBOX_CONFIG = dict(borderwidth=1,
48 selectborderwidth=0,
49 highlightthickness=0,
50 exportselection=False,
51 selectbackground='#888',
52 activestyle='none',
53 takefocus=False)
54
55
56
57
58
59 - def __init__(self, master, columns, column_weights=None, cnf={}, **kw):
60 """
61 Construct a new multi-column listbox widget.
62
63 @param master: The widget that should contain the new
64 multi-column listbox.
65
66 @param columns: Specifies what columns should be included in
67 the new multi-column listbox. If C{columns} is an integer,
68 the it is the number of columns to include. If it is
69 a list, then its length indicates the number of columns
70 to include; and each element of the list will be used as
71 a label for the corresponding column.
72
73 @param cnf, kw: Configuration parameters for this widget.
74 Use C{label_*} to configure all labels; and C{listbox_*}
75 to configure all listboxes. E.g.:
76
77 >>> mlb = MultiListbox(master, 5, label_foreground='red')
78 """
79
80 if isinstance(columns, int):
81 columns = range(columns)
82 include_labels = False
83 else:
84 include_labels = True
85
86 if len(columns) == 0:
87 raise ValuError("Expected at least one column")
88
89
90 self._column_names = tuple(columns)
91 self._listboxes = []
92 self._labels = []
93
94
95 if column_weights is None:
96 column_weights = [1] * len(columns)
97 elif len(column_weights) != len(columns):
98 raise ValueError('Expected one column_weight for each column')
99 self._column_weights = column_weights
100
101
102 Frame.__init__(self, master, **self.FRAME_CONFIG)
103 self.grid_rowconfigure(1, weight=1)
104 for i, label in enumerate(self._column_names):
105 self.grid_columnconfigure(i, weight=column_weights[i])
106
107
108 if include_labels:
109 l = Label(self, text=label, **self.LABEL_CONFIG)
110 self._labels.append(l)
111 l.grid(column=i, row=0, sticky='news', padx=0, pady=0)
112 l.column_index = i
113
114
115 lb = Listbox(self, **self.LISTBOX_CONFIG)
116 self._listboxes.append(lb)
117 lb.grid(column=i, row=1, sticky='news', padx=0, pady=0)
118 lb.column_index = i
119
120
121 lb.bind('<Button-1>', self._select)
122 lb.bind('<B1-Motion>', self._select)
123
124 lb.bind('<Button-4>', lambda e: self._scroll(-1))
125 lb.bind('<Button-5>', lambda e: self._scroll(+1))
126 lb.bind('<MouseWheel>', lambda e: self._scroll(e.delta))
127
128 lb.bind('<Button-2>', lambda e: self.scan_mark(e.x, e.y))
129 lb.bind('<B2-Motion>', lambda e: self.scan_dragto(e.x, e.y))
130
131
132 lb.bind('<B1-Leave>', lambda e: 'break')
133
134 l.bind('<Button-1>', self._resize_column)
135
136
137
138 self.bind('<Button-1>', self._resize_column)
139
140
141 self.bind('<Up>', lambda e: self.select(delta=-1))
142 self.bind('<Down>', lambda e: self.select(delta=1))
143 self.bind('<Prior>', lambda e: self.select(delta=-self._pagesize()))
144 self.bind('<Next>', lambda e: self.select(delta=self._pagesize()))
145
146
147 self.configure(cnf, **kw)
148
149
150
151
152
154 """
155 Callback used to resize a column of the table. Return C{True}
156 if the column is actually getting resized (if the user clicked
157 on the far left or far right 5 pixels of a label); and
158 C{False} otherwies.
159 """
160
161
162 if event.widget.bind('<ButtonRelease>'):
163 return False
164
165
166 self._resize_column_index = None
167 if event.widget is self:
168 for i, lb in enumerate(self._listboxes):
169 if abs(event.x-(lb.winfo_x()+lb.winfo_width())) < 10:
170 self._resize_column_index = i
171 elif event.x > (event.widget.winfo_width()-5):
172 self._resize_column_index = event.widget.column_index
173 elif event.x < 5 and event.widget.column_index != 0:
174 self._resize_column_index = event.widget.column_index-1
175
176
177 if self._resize_column_index is not None:
178 event.widget.bind('<Motion>', self._resize_column_motion_cb)
179 event.widget.bind('<ButtonRelease-%d>' % event.num,
180 self._resize_column_buttonrelease_cb)
181 return True
182 else:
183 return False
184
186 lb = self._listboxes[self._resize_column_index]
187 charwidth = lb.winfo_width() / float(lb['width'])
188
189 x1 = event.x + event.widget.winfo_x()
190 x2 = lb.winfo_x() + lb.winfo_width()
191
192 lb['width'] = max(3, lb['width'] + int((x1-x2)/charwidth))
193
197
198
199
200
201
202 column_names = property(lambda self: self._column_names, doc="""
203 A tuple containing the names of the columns used by this
204 multi-column listbox.""")
205
206 column_labels = property(lambda self: tuple(self._labels), doc="""
207 A tuple containing the C{Tkinter.Label} widgets used to
208 display the label of each column. If this multi-column
209 listbox was created without labels, then this will be an empty
210 tuple. These widgets will all be augmented with a
211 C{column_index} attribute, which can be used to determine
212 which column they correspond to. This can be convenient,
213 e.g., when defining callbacks for bound events.""")
214
215 listboxes = property(lambda self: tuple(self._listboxes), doc="""
216 A tuple containing the C{Tkinter.Listbox} widgets used to
217 display individual columns. These widgets will all be
218 augmented with a C{column_index} attribute, which can be used
219 to determine which column they correspond to. This can be
220 convenient, e.g., when defining callbacks for bound events.""")
221
222
223
224
225
232
237
238 - def _pagesize(self):
239 """@return: The number of rows that makes up one page"""
240 return int(self.index('@0,1000000')) - int(self.index('@0,0'))
241
242
243
244
245
246 - def select(self, index=None, delta=None, see=True):
247 """
248 Set the selected row. If C{index} is specified, then select
249 row C{index}. Otherwise, if C{delta} is specified, then move
250 the current selection by C{delta} (negative numbers for up,
251 positive numbers for down). This will not move the selection
252 past the top or the bottom of the list.
253
254 @param see: If true, then call C{self.see()} with the newly
255 selected index, to ensure that it is visible.
256 """
257 if (index is not None) and (delta is not None):
258 raise ValueError('specify index or delta, but not both')
259
260
261 if delta is not None:
262 if len(self.curselection()) == 0:
263 index = -1 + delta
264 else:
265 index = int(self.curselection()[0]) + delta
266
267
268 self.selection_clear(0, 'end')
269
270
271 if index is not None:
272 index = min(max(index, 0), self.size()-1)
273
274 self.selection_set(index)
275 if see: self.see(index)
276
277
278
279
280
300
302 """
303 Configure this widget. This is equivalent to
304 C{self.configure({key,val})}. See L{configure()}.
305 """
306 self.configure({key:val})
307
315
331
340
341
342
343
344
345 - def insert(self, index, *rows):
346 """
347 Insert the given row or rows into the table, at the given
348 index. Each row value should be a tuple of cell values, one
349 for each column in the row. Index may be an integer or any of
350 the special strings (such as C{'end'}) accepted by
351 C{Tkinter.Listbox}.
352 """
353 for elt in rows:
354 if len(elt) != len(self._column_names):
355 raise ValueError('rows should be tuples whose length '
356 'is equal to the number of columns')
357 for (lb,elts) in zip(self._listboxes, zip(*rows)):
358 lb.insert(index, *elts)
359
360 - def get(self, first, last=None):
361 """
362 Return the value(s) of the specified row(s). If C{last} is
363 not specified, then return a single row value; otherwise,
364 return a list of row values. Each row value is a tuple of
365 cell values, one for each column in the row.
366 """
367 values = [lb.get(first, last) for lb in self._listboxes]
368 if last:
369 return [tuple(row) for row in zip(*values)]
370 else:
371 return tuple(values)
372
373 - def bbox(self, row, col):
374 """
375 Return the bounding box for the given table cell, relative to
376 this widget's top-left corner. The bounding box is a tuple
377 of integers C{(left, top, width, height)}.
378 """
379 dx, dy, _, _ = self.grid_bbox(row=0, column=col)
380 x, y, w, h = self._listboxes[col].bbox(row)
381 return int(x)+int(dx), int(y)+int(dy), int(w), int(h)
382
383
384
385
386
388 """
389 Hide the given column. The column's state is still
390 maintained: its values will still be returned by L{get()}, and
391 you must supply its values when calling L{insert()}. It is
392 safe to call this on a column that is already hidden.
393
394 @see: L{show_column()}
395 """
396 if self._labels:
397 self._labels[col_index].grid_forget()
398 self.listboxes[col_index].grid_forget()
399 self.grid_columnconfigure(col_index, weight=0)
400
402 """
403 Display a column that has been hidden using L{hide_column()}.
404 It is safe to call this on a column that is not hidden.
405 """
406 weight = self._column_weights[col_index]
407 if self._labels:
408 self._labels[col_index].grid(column=col_index, row=0,
409 sticky='news', padx=0, pady=0)
410 self._listboxes[col_index].grid(column=col_index, row=1,
411 sticky='news', padx=0, pady=0)
412 self.grid_columnconfigure(col_index, weight=weight)
413
414
415
416
417
419 """
420 Add a binding to each C{Tkinter.Label} widget in this
421 mult-column listbox that will call C{func} in response to the
422 event C{sequence}.
423
424 @return: A list of the identifiers of replaced binding
425 functions (if any), allowing for their deletion (to
426 prevent a memory leak).
427 """
428 return [label.bind(sequence, func, add)
429 for label in self.column_labels]
430
432 """
433 Add a binding to each C{Tkinter.Listbox} widget in this
434 mult-column listbox that will call C{func} in response to the
435 event C{sequence}.
436
437 @return: A list of the identifiers of replaced binding
438 functions (if any), allowing for their deletion (to
439 prevent a memory leak).
440 """
441 for listbox in self.listboxes:
442 listbox.bind(sequence, func, add)
443
445 """
446 Add a binding to each C{Tkinter.Label} and C{Tkinter.Listbox}
447 widget in this mult-column listbox that will call C{func} in
448 response to the event C{sequence}.
449
450 @return: A list of the identifiers of replaced binding
451 functions (if any), allowing for their deletion (to
452 prevent a memory leak).
453 """
454 return (self.bind_to_labels(sequence, func, add) +
455 self.bind_to_listboxes(sequence, func, add))
456
457
458
459
460
461
467 return self._listboxes[0].itemcget(*args, **kwargs)
468 - def size(self, *args, **kwargs):
469 return self._listboxes[0].size(*args, **kwargs)
470 - def index(self, *args, **kwargs):
471 return self._listboxes[0].index(*args, **kwargs)
472 - def nearest(self, *args, **kwargs):
473 return self._listboxes[0].nearest(*args, **kwargs)
474
475
477 for lb in self._listboxes: lb.activate(*args, **kwargs)
478 - def delete(self, *args, **kwargs):
479 for lb in self._listboxes: lb.delete(*args, **kwargs)
481 for lb in self._listboxes: lb.scan_mark(*args, **kwargs)
483 for lb in self._listboxes: lb.scan_dragto(*args, **kwargs)
484 - def see(self, *args, **kwargs):
485 for lb in self._listboxes: lb.see(*args, **kwargs)
492 - def yview(self, *args, **kwargs):
493 for lb in self._listboxes: lb.yview(*args, **kwargs)
495 for lb in self._listboxes: lb.yview_moveto(*args, **kwargs)
498
499
500
501
502
503 itemconfig = itemconfigure
504 rowconfig = rowconfigure
505 columnconfig = columnconfigure
506 select_anchor = selection_anchor
507 select_clear = selection_clear
508 select_includes = selection_includes
509 select_set = selection_set
510
511
512
513
514
515
516
517
518
519
520
521
523 """
524 A display widget for a table of values, based on a L{MultiListbox}
525 widget. For many purposes, C{Table} can be treated as a
526 list-of-lists. E.g., table[i] is a list of the values for row i;
527 and table.append(row) adds a new row with the given lits of
528 values. Individual cells can be accessed using table[i,j], which
529 refers to the j-th column of the i-th row. This can be used to
530 both read and write values from the table. E.g.:
531
532 >>> table[i,j] = 'hello'
533
534 The column (j) can be given either as an index number, or as a
535 column name. E.g., the following prints the value in the 3rd row
536 for the 'First Name' column:
537
538 >>> print table[3, 'First Name']
539 John
540
541 You can configure the colors for individual rows, columns, or
542 cells using L{rowconfig()}, L{columnconfig()}, and L{itemconfig()}.
543 The color configuration for each row will be preserved if the
544 table is modified; however, when new rows are added, any color
545 configurations that have been made for I{columns} will not be
546 applied to the new row.
547
548 Note: Although C{Table} acts like a widget in some ways (e.g., it
549 defines L{grid()}, L{pack()}, and L{bind()}), it is not itself a
550 widget; it just contains one. This is because widgets need to
551 define C{__getitem__()}, C{__setitem__()}, and C{__nonzero__()} in
552 a way that's incompatible with the fact that C{Table} behaves as a
553 list-of-lists.
554
555 @ivar _mlb: The multi-column listbox used to display this table's data.
556 @ivar _rows: A list-of-lists used to hold the cell values of this
557 table. Each element of _rows is a row value, i.e., a list of
558 cell values, one for each column in the row.
559 """
560 - def __init__(self, master, column_names, rows=None,
561 column_weights=None,
562 scrollbar=True, click_to_sort=True,
563 reprfunc=None, cnf={}, **kw):
564 """
565 Construct a new Table widget.
566
567 @type master: C{Tkinter.Widget}
568 @param master: The widget that should contain the new table.
569
570 @type column_names: C{list} of C{str}
571 @param column_names: A list of names for the columns; these
572 names will be used to create labels for each column;
573 and can be used as an index when reading or writing
574 cell values from the table.
575
576 @type rows: C{list} of C{list}
577 @param rows: A list of row values used to initialze the table.
578 Each row value should be a tuple of cell values, one for
579 each column in the row.
580
581 @type scrollbar: C{bool}
582 @param scrollbar: If true, then create a scrollbar for the
583 new table widget.
584
585 @type click_to_sort: C{bool}
586 @param click_to_sort: If true, then create bindings that will
587 sort the table's rows by a given column's values if the
588 user clicks on that colum's label.
589
590 @type reprfunc: C{function}
591 @param reprfunc: If specified, then use this function to
592 convert each table cell value to a string suitable for
593 display. C{reprfunc} has the following signature:
594
595 >>> reprfunc(row_index, col_index, cell_value) -> str
596
597 (Note that the column is specified by index, not by name.)
598
599 @param cnf, kw: Configuration parameters for this widget's
600 contained C{MultiListbox}. See L{MultiListbox.__init__()}
601 for details.
602 """
603 self._num_columns = len(column_names)
604 self._reprfunc = reprfunc
605 self._frame = Frame(master)
606
607 self._column_name_to_index = dict((c,i) for (i,c) in
608 enumerate(column_names))
609
610
611 if rows is None: self._rows = []
612 else: self._rows = [[v for v in row] for row in rows]
613 for row in self._rows: self._checkrow(row)
614
615
616 self._mlb = MultiListbox(self._frame, column_names,
617 column_weights, cnf, **kw)
618 self._mlb.pack(side='left', expand=True, fill='both')
619
620
621 if scrollbar:
622 sb = Scrollbar(self._frame, orient='vertical',
623 command=self._mlb.yview)
624 sb.pack(side='right', fill='y')
625 for listbox in self._mlb.listboxes:
626 listbox['yscrollcommand'] = sb.set
627
628
629 self._sortkey = None
630 if click_to_sort:
631 for i, l in enumerate(self._mlb.column_labels):
632 l.bind('<Button-1>', self._sort)
633
634
635 self._fill_table()
636
637
638
639
640
641
642 - def pack(self, *args, **kwargs):
643 """Position this table's main frame widget in its parent
644 widget. See C{Tkinter.Frame.pack()} for more info."""
645 self._frame.pack(*args, **kwargs)
646
647 - def grid(self, *args, **kwargs):
648 """Position this table's main frame widget in its parent
649 widget. See C{Tkinter.Frame.grid()} for more info."""
650 self._frame.grid(*args, **kwargs)
651
653 """Direct (keyboard) input foxus to this widget."""
654 self._mlb.focus()
655
656 - def bind(self, sequence=None, func=None, add=None):
657 """Add a binding to this table's main frame that will call
658 C{func} in response to the event C{sequence}."""
659 self._mlb.bind(sequence, func, add)
660
664
669
674
676 """@see: L{MultiListbox.bind_to_labels()}"""
677 return self._mlb.bind_to_labels(sequence, func, add)
678
680 """@see: L{MultiListbox.bind_to_listboxes()}"""
681 return self._mlb.bind_to_listboxes(sequence, func, add)
682
684 """@see: L{MultiListbox.bind_to_columns()}"""
685 return self._mlb.bind_to_columns(sequence, func, add)
686
687 rowconfig = rowconfigure
688 columnconfig = columnconfigure
689 itemconfig = itemconfigure
690
691
692
693
694
695 - def insert(self, row_index, rowvalue):
696 """
697 Insert a new row into the table, so that its row index will be
698 C{row_index}. If the table contains any rows whose row index
699 is greater than or equal to C{row_index}, then they will be
700 shifted down.
701
702 @param rowvalue: A tuple of cell values, one for each column
703 in the new row.
704 """
705 self._checkrow(rowvalue)
706 self._rows.insert(row_index, rowvalue)
707 if self._reprfunc is not None:
708 rowvalue = [self._reprfunc(row_index,j,v)
709 for (j,v) in enumerate(rowvalue)]
710 self._mlb.insert(row_index, rowvalue)
711 if self._DEBUG: self._check_table_vs_mlb()
712
714 """
715 Add new rows at the end of the table.
716
717 @param rowvalues: A list of row values used to initialze the
718 table. Each row value should be a tuple of cell values,
719 one for each column in the row.
720 """
721 for rowvalue in rowvalues: self.append(rowvalue)
722 if self._DEBUG: self._check_table_vs_mlb()
723
725 """
726 Add a new row to the end of the table.
727
728 @param rowvalue: A tuple of cell values, one for each column
729 in the new row.
730 """
731 self.insert(len(self._rows), rowvalue)
732 if self._DEBUG: self._check_table_vs_mlb()
733
741
743 """
744 Return the value of a row or a cell in this table. If
745 C{index} is an integer, then the row value for the C{index}th
746 row. This row value consists of a tuple of cell values, one
747 for each column in the row. If C{index} is a tuple of two
748 integers, C{(i,j)}, then return the value of the cell in the
749 C{i}th row and the C{j}th column.
750 """
751 if isinstance(index, slice):
752 raise ValueError('Slicing not supported')
753 elif isinstance(index, tuple) and len(index)==2:
754 return self._rows[index[0]][self.column_index(index[1])]
755 else:
756 return tuple(self._rows[index])
757
759 """
760 Replace the value of a row or a cell in this table with
761 C{val}.
762
763 If C{index} is an integer, then C{val} should be a row value
764 (i.e., a tuple of cell values, one for each column). In this
765 case, the values of the C{index}th row of the table will be
766 replaced with the values in C{val}.
767
768 If C{index} is a tuple of integers, C{(i,j)}, then replace the
769 value of the cell in the C{i}th row and C{j}th column with
770 C{val}.
771 """
772 if isinstance(index, slice):
773 raise ValueError('Slicing not supported')
774
775
776
777 elif isinstance(index, tuple) and len(index)==2:
778 i, j = index[0], self.column_index(index[1])
779 config_cookie = self._save_config_info([i])
780 self._rows[i][j] = val
781 if self._reprfunc is not None:
782 val = self._reprfunc(i, j, val)
783 self._mlb.listboxes[j].insert(i, val)
784 self._mlb.listboxes[j].delete(i+1)
785 self._restore_config_info(config_cookie)
786
787
788 else:
789 config_cookie = self._save_config_info([index])
790 self._checkrow(val)
791 self._rows[index] = list(val)
792 if self._reprfunc is not None:
793 val = [self._reprfunc(index,j,v) for (j,v) in enumerate(val)]
794 self._mlb.insert(index, val)
795 self._mlb.delete(index+1)
796 self._restore_config_info(config_cookie)
797
799 """
800 Delete the C{row_index}th row from this table.
801 """
802 if isinstance(index, slice):
803 raise ValueError('Slicing not supported')
804 if isinstance(row_index, tuple) and len(row_index)==2:
805 raise ValueError('Cannot delete a single cell!')
806 del self._rows[row_index]
807 self._mlb.delete(row_index)
808 if self._DEBUG: self._check_table_vs_mlb()
809
811 """
812 @return: the number of rows in this table.
813 """
814 return len(self._rows)
815
817 """
818 Helper function: check that a given row value has the correct
819 number of elements; and if not, raise an exception.
820 """
821 if len(rowvalue) != self._num_columns:
822 raise ValueError('Row %r has %d columns; expected %d' %
823 (rowvalue, len(rowvalue), self._num_columns))
824
825
826
827
828
829 column_names = property(lambda self: self._mlb.column_names, doc="""
830 A list of the names of the columns in this table.""")
831
833 """
834 If C{i} is a valid column index integer, then return it as is.
835 Otherwise, check if C{i} is used as the name for any column;
836 if so, return that column's index. Otherwise, raise a
837 C{KeyError} exception.
838 """
839 if isinstance(i, int) and 0 <= i < self._num_columns:
840 return i
841 else:
842
843 return self._column_name_to_index[i]
844
848
852
853
854
855
856
858 """
859 Return the index of the currently selected row, or C{None} if
860 no row is selected. To get the row value itself, use
861 C{table[table.selected_row()]}.
862 """
863 sel = self._mlb.curselection()
864 if sel: return int(sel[0])
865 else: return None
866
867 - def select(self, index=None, delta=None, see=True):
868 """@see: L{MultiListbox.select()}"""
869 self._mlb.select(index, delta, see)
870
871
872
873
874
875 - def sort_by(self, column_index, order='toggle'):
876 """
877 Sort the rows in this table, using the specified column's
878 values as a sort key.
879
880 @param column_index: Specifies which column to sort, using
881 either a column index (C{int}) or a column's label name
882 (C{str}).
883
884 @param order: Specifies whether to sort the values in
885 ascending or descending order:
886
887 - C{'ascending'}: Sort from least to greatest.
888 - C{'descending'}: Sort from greatest to least.
889 - C{'toggle'}: If the most recent call to C{sort_by()}
890 sorted the table by the same column C({column_index}),
891 then reverse the rows; otherwise sort in ascending
892 order.
893 """
894 if order not in ('ascending', 'descending', 'toggle'):
895 raise ValueError('sort_by(): order should be "ascending", '
896 '"descending", or "toggle".')
897 column_index = self.column_index(column_index)
898 config_cookie = self._save_config_info(index_by_id=True)
899
900
901 if order == 'toggle' and column_index == self._sortkey:
902 self._rows.reverse()
903 else:
904 self._rows.sort(key=operator.itemgetter(column_index),
905 reverse=(order=='descending'))
906 self._sortkey = column_index
907
908
909 self._fill_table()
910 self._restore_config_info(config_cookie, index_by_id=True, see=True)
911 if self._DEBUG: self._check_table_vs_mlb()
912
927
928
929
930
931
933 """
934 Re-draw the table from scratch, by clearing out the table's
935 multi-column listbox; and then filling it in with values from
936 C{self._rows}. Note that any cell-, row-, or column-specific
937 color configuration that has been done will be lost. The
938 selection will also be lost -- i.e., no row will be selected
939 after this call completes.
940 """
941 self._mlb.delete(0, 'end')
942 for i, row in enumerate(self._rows):
943 if self._reprfunc is not None:
944 row = [self._reprfunc(i,j,v) for (j,v) in enumerate(row)]
945 self._mlb.insert('end', row)
946
948 return dict( (k, self._mlb.itemconfig(r, c, k)[-1])
949 for k in ('foreground', 'selectforeground',
950 'background', 'selectbackground') )
951
953 """
954 Return a 'cookie' containing information about which row is
955 selected, and what color configurations have been applied.
956 this information can the be re-applied to the table (after
957 making modifications) using L{_restore_config_info()}. Color
958 configuration information will be saved for any rows in
959 C{row_indices}, or in the entire table, if
960 C{row_indices=None}. If C{index_by_id=True}, the the cookie
961 will associate rows with their configuration information based
962 on the rows' python id. This is useful when performing
963 operations that re-arrange the rows (e.g. C{sort}). If
964 C{index_by_id=False}, then it is assumed that all rows will be
965 in the same order when C{_restore_config_info()} is called.
966 """
967
968 if row_indices is None:
969 row_indices = range(len(self._rows))
970
971
972 selection = self.selected_row()
973 if index_by_id and selection is not None:
974 selection = id(self._rows[selection])
975
976
977 if index_by_id:
978 config = dict((id(self._rows[r]), [self._get_itemconfig(r, c)
979 for c in range(self._num_columns)])
980 for r in row_indices)
981 else:
982 config = dict((r, [self._get_itemconfig(r, c)
983 for c in range(self._num_columns)])
984 for r in row_indices)
985
986
987 return selection, config
988
990 """
991 Restore selection & color configuration information that was
992 saved using L{_save_config_info}.
993 """
994 selection, config = cookie
995
996
997 if selection is None:
998 self._mlb.selection_clear(0, 'end')
999
1000
1001 if index_by_id:
1002 for r, row in enumerate(self._rows):
1003 if id(row) in config:
1004 for c in range(self._num_columns):
1005 self._mlb.itemconfigure(r, c, config[id(row)][c])
1006 if id(row) == selection:
1007 self._mlb.select(r, see=see)
1008 else:
1009 if selection is not None:
1010 self._mlb.select(selection, see=see)
1011 for r in config:
1012 for c in range(self._num_columns):
1013 self._mlb.itemconfigure(r, c, config[r][c])
1014
1015
1016
1017
1018
1019 _DEBUG = False
1020 """If true, then run L{_check_table_vs_mlb()} after any operation
1021 that modifies the table."""
1022
1024 """
1025 Verify that the contents of the table's L{_rows} variable match
1026 the contents of its multi-listbox (L{_mlb}). This is just
1027 included for debugging purposes, to make sure that the
1028 list-modifying operations are working correctly.
1029 """
1030 for col in self._mlb.listboxes:
1031 assert len(self) == col.size()
1032 for row in self:
1033 assert len(row) == self._num_columns
1034 assert self._num_columns == len(self._mlb.column_names)
1035
1036 for i, row in enumerate(self):
1037 for j, cell in enumerate(row):
1038 if self._reprfunc is not None:
1039 cell = self._reprfunc(i, j, cell)
1040 assert self._mlb.get(i)[j] == cell
1041
1042
1043
1044
1045
1047 root = Tk()
1048 root.bind('<Control-q>', lambda e: root.destroy())
1049
1050 table = Table(root, 'Word Synset Hypernym Hyponym'.split(),
1051 column_weights=[0, 1, 1, 1],
1052 reprfunc=(lambda i,j,s: ' %s' % s))
1053 table.pack(expand=True, fill='both')
1054
1055 from nltk import wordnet
1056 from nltk.corpus import brown
1057 for word, pos in sorted(set(brown.tagged_words()[:500])):
1058 if pos[0] != 'N': continue
1059 word = word.lower()
1060 if word not in wordnet.N: continue
1061 for synset in wordnet.N[word].synsets():
1062 hyper = synset.relations().get(wordnet.HYPERNYM, [''])[0]
1063 hypo = synset.relations().get(wordnet.HYPONYM, [''])[0]
1064 table.append([word,
1065 getattr(synset, 'gloss', '*none*'),
1066 getattr(hyper, 'gloss', '*none*'),
1067 getattr(hypo, 'gloss', '*none*')])
1068
1069 table.columnconfig('Word', background='#afa')
1070 table.columnconfig('Synset', background='#efe')
1071 table.columnconfig('Hypernym', background='#fee')
1072 table.columnconfig('Hyponym', background='#ffe')
1073 for row in range(len(table)):
1074 for column in ('Hypernym', 'Hyponym'):
1075 if table[row, column] == '*none*':
1076 table.itemconfig(row, column, foreground='#666',
1077 selectforeground='#666')
1078 root.mainloop()
1079
1080 if __name__ == '__main__':
1081 demo()
1082