Commit | Line | Data |
---|---|---|
920dae64 AT |
1 | # |
2 | # FILE: MCListbox.py | |
3 | # | |
4 | # DESCRIPTION: | |
5 | # This file provides a generic Multi-Column Listbox widget. It is derived | |
6 | # from a heavily hacked version of Pmw.ScrolledFrame | |
7 | # | |
8 | # This program is free software; you can redistribute it and/or modify it | |
9 | # under the terms of the GNU General Public License as published by the | |
10 | # Free Software Foundation; either version 2 of the License, or (at your | |
11 | # option) any later version. | |
12 | # | |
13 | # THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED | |
14 | # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
15 | # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN | |
16 | # NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | |
17 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |
18 | # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |
19 | # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | |
20 | # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
21 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
22 | # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
23 | # | |
24 | # You should have received a copy of the GNU General Public License along | |
25 | # with this program; if not, write to the Free Software Foundation, Inc., | |
26 | # 675 Mass Ave, Cambridge, MA 02139, USA. | |
27 | # | |
28 | ||
29 | import string | |
30 | import Tkinter | |
31 | import Pmw | |
32 | ||
33 | class MultiColumnListbox(Pmw.MegaWidget): | |
34 | def __init__(self, parent = None, **kw): | |
35 | colors = Pmw.Color.getdefaultpalette(parent) | |
36 | ||
37 | # Define the megawidget options. | |
38 | INITOPT = Pmw.INITOPT | |
39 | optiondefs = ( | |
40 | #('borderframe', 1, INITOPT), | |
41 | ('horizflex', 'fixed', self._horizflex), | |
42 | ('horizfraction', 0.05, INITOPT), | |
43 | ('hscrollmode', 'dynamic', self._hscrollMode), | |
44 | ('labelmargin', 0, INITOPT), | |
45 | ('labelpos', None, INITOPT), | |
46 | ('scrollmargin', 2, INITOPT), | |
47 | ('usehullsize', 0, INITOPT), | |
48 | ('vertflex', 'fixed', self._vertflex), | |
49 | ('vertfraction', 0.05, INITOPT), | |
50 | ('vscrollmode', 'dynamic', self._vscrollMode), | |
51 | ('labellist', None, INITOPT), | |
52 | ('selectbackground', colors['selectBackground'], INITOPT), | |
53 | ('selectforeground', colors['selectForeground'], INITOPT), | |
54 | ('background', colors['background'], INITOPT), | |
55 | ('foreground', colors['foreground'], INITOPT), | |
56 | ('command', None, None), | |
57 | ('dblclickcommand', None, None), | |
58 | ) | |
59 | self.defineoptions(kw, optiondefs) | |
60 | ||
61 | # Initialise the base class (after defining the options). | |
62 | Pmw.MegaWidget.__init__(self, parent) | |
63 | ||
64 | self._numcolumns = len(self['labellist']) | |
65 | self._columnlabels = self['labellist'] | |
66 | self._lineid = 0 | |
67 | self._numrows = 0 | |
68 | self._lineitemframes = [] | |
69 | self._lineitems = [] | |
70 | self._lineitemdata = {} | |
71 | self._labelframe = {} | |
72 | self._cursel = [] | |
73 | ||
74 | # Create the components. | |
75 | self.origInterior = Pmw.MegaWidget.interior(self) | |
76 | ||
77 | if self['usehullsize']: | |
78 | self.origInterior.grid_propagate(0) | |
79 | ||
80 | # Create a frame widget to act as the border of the clipper. | |
81 | self._borderframe = self.createcomponent('borderframe', | |
82 | (), None, | |
83 | Tkinter.Frame, | |
84 | (self.origInterior,), | |
85 | relief = 'sunken', | |
86 | borderwidth = 2, | |
87 | ) | |
88 | self._borderframe.grid(row = 2, column = 2, | |
89 | rowspan = 2, sticky = 'news') | |
90 | ||
91 | # Create the clipping windows. | |
92 | self._hclipper = self.createcomponent('hclipper', | |
93 | (), None, | |
94 | Tkinter.Frame, | |
95 | (self._borderframe,), | |
96 | width = 400, | |
97 | height = 300, | |
98 | ) | |
99 | self._hclipper.pack(fill = 'both', expand = 1) | |
100 | ||
101 | self._hsframe = self.createcomponent('hsframe', (), None, | |
102 | Tkinter.Frame, | |
103 | (self._hclipper,), | |
104 | ) | |
105 | ||
106 | ||
107 | self._vclipper = self.createcomponent('vclipper', | |
108 | (), None, | |
109 | Tkinter.Frame, | |
110 | (self._hsframe,), | |
111 | #width = 400, | |
112 | #height = 300, | |
113 | highlightthickness = 0, | |
114 | borderwidth = 0, | |
115 | ) | |
116 | ||
117 | self._vclipper.grid(row = 1, column = 0, | |
118 | columnspan = self._numcolumns, | |
119 | sticky = 'news')#, expand = 1) | |
120 | self._hsframe.grid_rowconfigure(1, weight = 1)#, minsize = 300) | |
121 | ||
122 | ||
123 | gridcolumn = 0 | |
124 | for labeltext in self._columnlabels: | |
125 | lframe = self.createcomponent(labeltext+'frame', (), None, | |
126 | Tkinter.Frame, | |
127 | (self._hsframe,), | |
128 | borderwidth = 1, | |
129 | relief = 'raised', | |
130 | ) | |
131 | label = self.createcomponent(labeltext, (), None, | |
132 | Tkinter.Label, | |
133 | (lframe,), | |
134 | text = labeltext, | |
135 | ) | |
136 | label.pack(expand = 0, fill = 'y', side = 'left') | |
137 | lframe.grid(row = 0, column = gridcolumn, sticky = 'ews') | |
138 | self._labelframe[labeltext] = lframe | |
139 | #lframe.update() | |
140 | #print lframe.winfo_reqwidth() | |
141 | self._hsframe.grid_columnconfigure(gridcolumn, weight = 1) | |
142 | gridcolumn = gridcolumn + 1 | |
143 | ||
144 | lframe.update() | |
145 | self._labelheight = lframe.winfo_reqheight() | |
146 | self.origInterior.grid_rowconfigure(2, minsize = self._labelheight + 2) | |
147 | ||
148 | self.origInterior.grid_rowconfigure(3, weight = 1, minsize = 0) | |
149 | self.origInterior.grid_columnconfigure(2, weight = 1, minsize = 0) | |
150 | ||
151 | # Create the horizontal scrollbar | |
152 | self._horizScrollbar = self.createcomponent('horizscrollbar', | |
153 | (), 'Scrollbar', | |
154 | Tkinter.Scrollbar, | |
155 | (self.origInterior,), | |
156 | orient='horizontal', | |
157 | command=self._xview | |
158 | ) | |
159 | ||
160 | # Create the vertical scrollbar | |
161 | self._vertScrollbar = self.createcomponent('vertscrollbar', | |
162 | (), 'Scrollbar', | |
163 | Tkinter.Scrollbar, | |
164 | (self.origInterior,), | |
165 | #(self._hclipper,), | |
166 | orient='vertical', | |
167 | command=self._yview | |
168 | ) | |
169 | ||
170 | self.createlabel(self.origInterior, childCols = 3, childRows = 4) | |
171 | ||
172 | # Initialise instance variables. | |
173 | self._horizScrollbarOn = 0 | |
174 | self._vertScrollbarOn = 0 | |
175 | self.scrollTimer = None | |
176 | self._scrollRecurse = 0 | |
177 | self._horizScrollbarNeeded = 0 | |
178 | self._vertScrollbarNeeded = 0 | |
179 | self.startX = 0 | |
180 | self.startY = 0 | |
181 | self._flexoptions = ('fixed', 'expand', 'shrink', 'elastic') | |
182 | ||
183 | # Create a frame in the clipper to contain the widgets to be | |
184 | # scrolled. | |
185 | self._vsframe = self.createcomponent('vsframe', | |
186 | (), None, | |
187 | Tkinter.Frame, | |
188 | (self._vclipper,), | |
189 | #height = 300, | |
190 | #borderwidth = 4, | |
191 | #relief = 'groove', | |
192 | ) | |
193 | ||
194 | # Whenever the clipping window or scrolled frame change size, | |
195 | # update the scrollbars. | |
196 | self._hsframe.bind('<Configure>', self._reposition) | |
197 | self._vsframe.bind('<Configure>', self._reposition) | |
198 | self._hclipper.bind('<Configure>', self._reposition) | |
199 | self._vclipper.bind('<Configure>', self._reposition) | |
200 | ||
201 | #elf._vsframe.bind('<Button-1>', self._vsframeselect) | |
202 | ||
203 | # Check keywords and initialise options. | |
204 | self.initialiseoptions() | |
205 | ||
206 | def destroy(self): | |
207 | if self.scrollTimer is not None: | |
208 | self.after_cancel(self.scrollTimer) | |
209 | self.scrollTimer = None | |
210 | Pmw.MegaWidget.destroy(self) | |
211 | ||
212 | # ====================================================================== | |
213 | ||
214 | # Public methods. | |
215 | ||
216 | def interior(self): | |
217 | return self._vsframe | |
218 | ||
219 | # Set timer to call real reposition method, so that it is not | |
220 | # called multiple times when many things are reconfigured at the | |
221 | # same time. | |
222 | def reposition(self): | |
223 | if self.scrollTimer is None: | |
224 | self.scrollTimer = self.after_idle(self._scrollBothNow) | |
225 | ||
226 | ||
227 | ||
228 | def insertrow(self, index, rowdata): | |
229 | #if len(rowdata) != self._numcolumns: | |
230 | # raise ValueError, 'Number of items in rowdata does not match number of columns.' | |
231 | if index > self._numrows: | |
232 | index = self._numrows | |
233 | ||
234 | rowframes = {} | |
235 | for columnlabel in self._columnlabels: | |
236 | celldata = rowdata.get(columnlabel) | |
237 | cellframe = self.createcomponent(('cellframeid.%d.%s'%(self._lineid, | |
238 | columnlabel)), | |
239 | (), ('Cellframerowid.%d'%self._lineid), | |
240 | Tkinter.Frame, | |
241 | (self._vsframe,), | |
242 | background = self['background'], | |
243 | #borderwidth = 1, | |
244 | #relief = 'flat' | |
245 | ) | |
246 | ||
247 | cellframe.bind('<Double-Button-1>', self._cellframedblclick) | |
248 | cellframe.bind('<Button-1>', self._cellframeselect) | |
249 | ||
250 | if celldata: | |
251 | cell = self.createcomponent(('cellid.%d.%s'%(self._lineid, | |
252 | columnlabel)), | |
253 | (), ('Cellrowid.%d'%self._lineid), | |
254 | Tkinter.Label, | |
255 | (cellframe,), | |
256 | background = self['background'], | |
257 | foreground = self['foreground'], | |
258 | text = celldata, | |
259 | ) | |
260 | ||
261 | cell.bind('<Double-Button-1>', self._celldblclick) | |
262 | cell.bind('<Button-1>', self._cellselect) | |
263 | ||
264 | cell.pack(expand = 0, fill = 'y', side = 'left', padx = 1, pady = 1) | |
265 | rowframes[columnlabel] = cellframe | |
266 | ||
267 | self._lineitemdata[self._lineid] = rowdata | |
268 | self._lineitems.insert(index, self._lineid) | |
269 | self._lineitemframes.insert(index, rowframes) | |
270 | self._numrows = self._numrows + 1 | |
271 | self._lineid = self._lineid + 1 | |
272 | ||
273 | self._placedata(index) | |
274 | ||
275 | def _placedata(self, index = 0): | |
276 | gridy = index | |
277 | for rowframes in self._lineitemframes[index:]: | |
278 | gridx = 0 | |
279 | for columnlabel in self._columnlabels: | |
280 | rowframes[columnlabel].grid(row = gridy, | |
281 | column = gridx, | |
282 | sticky = 'news') | |
283 | gridx = gridx + 1 | |
284 | gridy = gridy + 1 | |
285 | ||
286 | ||
287 | ||
288 | def addrow(self, rowdata): | |
289 | self.insertrow(self._numrows, rowdata) | |
290 | ||
291 | def delrow(self, index): | |
292 | rowframes = self._lineitemframes.pop(index) | |
293 | for columnlabel in self._columnlabels: | |
294 | rowframes[columnlabel].destroy() | |
295 | self._placedata(index) | |
296 | self._numrows = self._numrows - 1 | |
297 | del self._lineitems[index] | |
298 | if index in self._cursel: | |
299 | self._cursel.remove(index) | |
300 | ||
301 | ||
302 | def curselection(self): | |
303 | # Return a tuple of just one element as this will probably be the | |
304 | # interface used in a future implementation when multiple rows can | |
305 | # be selected at once. | |
306 | return tuple(self._cursel) | |
307 | ||
308 | def getcurselection(self): | |
309 | # Return a tuple of just one row as this will probably be the | |
310 | # interface used in a future implementation when multiple rows can | |
311 | # be selected at once. | |
312 | sellist = [] | |
313 | for sel in self._cursel: | |
314 | sellist.append(self._lineitemdata[self._lineitems[sel]]) | |
315 | return tuple(sellist) | |
316 | ||
317 | # ====================================================================== | |
318 | ||
319 | # Configuration methods. | |
320 | ||
321 | def _hscrollMode(self): | |
322 | # The horizontal scroll mode has been configured. | |
323 | ||
324 | mode = self['hscrollmode'] | |
325 | ||
326 | ||
327 | if mode == 'static': | |
328 | if not self._horizScrollbarOn: | |
329 | self._toggleHorizScrollbar() | |
330 | elif mode == 'dynamic': | |
331 | if self._horizScrollbarNeeded != self._horizScrollbarOn: | |
332 | self._toggleHorizScrollbar() | |
333 | elif mode == 'none': | |
334 | if self._horizScrollbarOn: | |
335 | self._toggleHorizScrollbar() | |
336 | else: | |
337 | message = 'bad hscrollmode option "%s": should be static, dynamic, or none' % mode | |
338 | raise ValueError, message | |
339 | ||
340 | def _vscrollMode(self): | |
341 | # The vertical scroll mode has been configured. | |
342 | ||
343 | mode = self['vscrollmode'] | |
344 | ||
345 | if mode == 'static': | |
346 | if not self._vertScrollbarOn: | |
347 | self._toggleVertScrollbar() | |
348 | elif mode == 'dynamic': | |
349 | if self._vertScrollbarNeeded != self._vertScrollbarOn: | |
350 | self._toggleVertScrollbar() | |
351 | elif mode == 'none': | |
352 | if self._vertScrollbarOn: | |
353 | self._toggleVertScrollbar() | |
354 | else: | |
355 | message = 'bad vscrollmode option "%s": should be static, dynamic, or none' % mode | |
356 | raise ValueError, message | |
357 | ||
358 | def _horizflex(self): | |
359 | # The horizontal flex mode has been configured. | |
360 | ||
361 | flex = self['horizflex'] | |
362 | ||
363 | if flex not in self._flexoptions: | |
364 | message = 'bad horizflex option "%s": should be one of %s' % \ | |
365 | mode, str(self._flexoptions) | |
366 | raise ValueError, message | |
367 | ||
368 | self.reposition() | |
369 | ||
370 | def _vertflex(self): | |
371 | # The vertical flex mode has been configured. | |
372 | ||
373 | flex = self['vertflex'] | |
374 | ||
375 | if flex not in self._flexoptions: | |
376 | message = 'bad vertflex option "%s": should be one of %s' % \ | |
377 | mode, str(self._flexoptions) | |
378 | raise ValueError, message | |
379 | ||
380 | self.reposition() | |
381 | ||
382 | ||
383 | ||
384 | # ====================================================================== | |
385 | ||
386 | # Private methods. | |
387 | ||
388 | def _reposition(self, event): | |
389 | gridx = 0 | |
390 | for col in self._columnlabels: | |
391 | maxwidth = self._labelframe[col].winfo_reqwidth() | |
392 | for row in self._lineitemframes: | |
393 | cellwidth = row[col].winfo_reqwidth() | |
394 | if cellwidth > maxwidth: | |
395 | maxwidth = cellwidth | |
396 | self._hsframe.grid_columnconfigure(gridx, minsize = maxwidth) | |
397 | gridwidth = self._hsframe.grid_bbox(column = gridx, row = 0)[2] | |
398 | if self['horizflex'] in ('expand', 'elastic') and gridwidth > maxwidth: | |
399 | maxwidth = gridwidth | |
400 | self._vsframe.grid_columnconfigure(gridx, minsize = maxwidth) | |
401 | gridx = gridx + 1 | |
402 | ||
403 | ||
404 | ||
405 | self._vclipper.configure(height = self._hclipper.winfo_height() - self._labelheight) | |
406 | ||
407 | self.reposition() | |
408 | ||
409 | # Called when the user clicks in the horizontal scrollbar. | |
410 | # Calculates new position of frame then calls reposition() to | |
411 | # update the frame and the scrollbar. | |
412 | def _xview(self, mode, value, units = None): | |
413 | ||
414 | if mode == 'moveto': | |
415 | frameWidth = self._hsframe.winfo_reqwidth() | |
416 | self.startX = string.atof(value) * float(frameWidth) | |
417 | else: | |
418 | clipperWidth = self._hclipper.winfo_width() | |
419 | if units == 'units': | |
420 | jump = int(clipperWidth * self['horizfraction']) | |
421 | else: | |
422 | jump = clipperWidth | |
423 | ||
424 | if value == '1': | |
425 | self.startX = self.startX + jump | |
426 | else: | |
427 | self.startX = self.startX - jump | |
428 | ||
429 | self.reposition() | |
430 | ||
431 | # Called when the user clicks in the vertical scrollbar. | |
432 | # Calculates new position of frame then calls reposition() to | |
433 | # update the frame and the scrollbar. | |
434 | def _yview(self, mode, value, units = None): | |
435 | ||
436 | if mode == 'moveto': | |
437 | frameHeight = self._vsframe.winfo_reqheight() | |
438 | self.startY = string.atof(value) * float(frameHeight) | |
439 | else: | |
440 | clipperHeight = self._vclipper.winfo_height() | |
441 | if units == 'units': | |
442 | jump = int(clipperHeight * self['vertfraction']) | |
443 | else: | |
444 | jump = clipperHeight | |
445 | ||
446 | if value == '1': | |
447 | self.startY = self.startY + jump | |
448 | else: | |
449 | self.startY = self.startY - jump | |
450 | ||
451 | self.reposition() | |
452 | ||
453 | def _getxview(self): | |
454 | ||
455 | # Horizontal dimension. | |
456 | clipperWidth = self._hclipper.winfo_width() | |
457 | frameWidth = self._hsframe.winfo_reqwidth() | |
458 | if frameWidth <= clipperWidth: | |
459 | # The scrolled frame is smaller than the clipping window. | |
460 | ||
461 | self.startX = 0 | |
462 | endScrollX = 1.0 | |
463 | ||
464 | if self['horizflex'] in ('expand', 'elastic'): | |
465 | relwidth = 1 | |
466 | else: | |
467 | relwidth = '' | |
468 | else: | |
469 | # The scrolled frame is larger than the clipping window. | |
470 | ||
471 | if self['horizflex'] in ('shrink', 'elastic'): | |
472 | self.startX = 0 | |
473 | endScrollX = 1.0 | |
474 | relwidth = 1 | |
475 | else: | |
476 | if self.startX + clipperWidth > frameWidth: | |
477 | self.startX = frameWidth - clipperWidth | |
478 | endScrollX = 1.0 | |
479 | else: | |
480 | if self.startX < 0: | |
481 | self.startX = 0 | |
482 | endScrollX = (self.startX + clipperWidth) / float(frameWidth) | |
483 | relwidth = '' | |
484 | ||
485 | # Position frame relative to clipper. | |
486 | self._hsframe.place(x = -self.startX, relwidth = relwidth) | |
487 | return (self.startX / float(frameWidth), endScrollX) | |
488 | ||
489 | def _getyview(self): | |
490 | ||
491 | # Vertical dimension. | |
492 | clipperHeight = self._vclipper.winfo_height() | |
493 | frameHeight = self._vsframe.winfo_reqheight() | |
494 | if frameHeight <= clipperHeight: | |
495 | # The scrolled frame is smaller than the clipping window. | |
496 | ||
497 | self.startY = 0 | |
498 | endScrollY = 1.0 | |
499 | ||
500 | if self['vertflex'] in ('expand', 'elastic'): | |
501 | relheight = 1 | |
502 | else: | |
503 | relheight = '' | |
504 | else: | |
505 | # The scrolled frame is larger than the clipping window. | |
506 | ||
507 | if self['vertflex'] in ('shrink', 'elastic'): | |
508 | self.startY = 0 | |
509 | endScrollY = 1.0 | |
510 | relheight = 1 | |
511 | else: | |
512 | if self.startY + clipperHeight > frameHeight: | |
513 | self.startY = frameHeight - clipperHeight | |
514 | endScrollY = 1.0 | |
515 | else: | |
516 | if self.startY < 0: | |
517 | self.startY = 0 | |
518 | endScrollY = (self.startY + clipperHeight) / float(frameHeight) | |
519 | relheight = '' | |
520 | ||
521 | # Position frame relative to clipper. | |
522 | self._vsframe.place(y = -self.startY, relheight = relheight) | |
523 | return (self.startY / float(frameHeight), endScrollY) | |
524 | ||
525 | # According to the relative geometries of the frame and the | |
526 | # clipper, reposition the frame within the clipper and reset the | |
527 | # scrollbars. | |
528 | def _scrollBothNow(self): | |
529 | self.scrollTimer = None | |
530 | ||
531 | # Call update_idletasks to make sure that the containing frame | |
532 | # has been resized before we attempt to set the scrollbars. | |
533 | # Otherwise the scrollbars may be mapped/unmapped continuously. | |
534 | self._scrollRecurse = self._scrollRecurse + 1 | |
535 | self.update_idletasks() | |
536 | self._scrollRecurse = self._scrollRecurse - 1 | |
537 | if self._scrollRecurse != 0: | |
538 | return | |
539 | ||
540 | xview = self._getxview() | |
541 | yview = self._getyview() | |
542 | self._horizScrollbar.set(xview[0], xview[1]) | |
543 | self._vertScrollbar.set(yview[0], yview[1]) | |
544 | ||
545 | self._horizScrollbarNeeded = (xview != (0.0, 1.0)) | |
546 | self._vertScrollbarNeeded = (yview != (0.0, 1.0)) | |
547 | ||
548 | # If both horizontal and vertical scrollmodes are dynamic and | |
549 | # currently only one scrollbar is mapped and both should be | |
550 | # toggled, then unmap the mapped scrollbar. This prevents a | |
551 | # continuous mapping and unmapping of the scrollbars. | |
552 | if (self['hscrollmode'] == self['vscrollmode'] == 'dynamic' and | |
553 | self._horizScrollbarNeeded != self._horizScrollbarOn and | |
554 | self._vertScrollbarNeeded != self._vertScrollbarOn and | |
555 | self._vertScrollbarOn != self._horizScrollbarOn): | |
556 | if self._horizScrollbarOn: | |
557 | self._toggleHorizScrollbar() | |
558 | else: | |
559 | self._toggleVertScrollbar() | |
560 | return | |
561 | ||
562 | if self['hscrollmode'] == 'dynamic': | |
563 | if self._horizScrollbarNeeded != self._horizScrollbarOn: | |
564 | self._toggleHorizScrollbar() | |
565 | ||
566 | if self['vscrollmode'] == 'dynamic': | |
567 | if self._vertScrollbarNeeded != self._vertScrollbarOn: | |
568 | self._toggleVertScrollbar() | |
569 | ||
570 | def _toggleHorizScrollbar(self): | |
571 | ||
572 | self._horizScrollbarOn = not self._horizScrollbarOn | |
573 | ||
574 | interior = self.origInterior | |
575 | if self._horizScrollbarOn: | |
576 | self._horizScrollbar.grid(row = 5, column = 2, sticky = 'news') | |
577 | interior.grid_rowconfigure(4, minsize = self['scrollmargin']) | |
578 | else: | |
579 | self._horizScrollbar.grid_forget() | |
580 | interior.grid_rowconfigure(4, minsize = 0) | |
581 | ||
582 | def _toggleVertScrollbar(self): | |
583 | ||
584 | self._vertScrollbarOn = not self._vertScrollbarOn | |
585 | ||
586 | interior = self.origInterior | |
587 | if self._vertScrollbarOn: | |
588 | self._vertScrollbar.grid(row = 3, column = 4, sticky = 'news') | |
589 | interior.grid_columnconfigure(3, minsize = self['scrollmargin']) | |
590 | else: | |
591 | self._vertScrollbar.grid_forget() | |
592 | interior.grid_columnconfigure(3, minsize = 0) | |
593 | ||
594 | # ====================================================================== | |
595 | ||
596 | # Selection methods. | |
597 | ||
598 | #def _vsframeselect(self, event): | |
599 | # print 'vsframe event x: %d y: %d'%(event.x, event.y) | |
600 | # col, row = self._vsframe.grid_location(event.x, event.y) | |
601 | # self._select(col, row) | |
602 | ||
603 | def _cellframeselect(self, event): | |
604 | #print 'cellframe event x: %d y: %d'%(event.x, event.y) | |
605 | x = event.widget.winfo_x() | |
606 | y = event.widget.winfo_y() | |
607 | #col, row = self._vsframe.grid_location(x + event.x, y + event.y) | |
608 | self._select(x + event.x, y + event.y)#(col, row) | |
609 | ||
610 | def _cellselect(self, event): | |
611 | #print 'cell event x: %d y: %d'%(event.x, event.y) | |
612 | lx = event.widget.winfo_x() | |
613 | ly = event.widget.winfo_y() | |
614 | parent = event.widget.pack_info()['in'] | |
615 | fx = parent.winfo_x() | |
616 | fy = parent.winfo_y() | |
617 | #col, row = self._vsframe.grid_location(fx + lx + event.x, fy + ly + event.y) | |
618 | self._select(fx + lx + event.x, fy + ly + event.y)#(col, row) | |
619 | ||
620 | def _select(self, x, y): | |
621 | col, row = self._vsframe.grid_location(x, y) | |
622 | #print 'Clicked on col: %d row: %d'%(col,row) | |
623 | cfg = {} | |
624 | lineid = self._lineitems[row] | |
625 | cfg['Cellrowid.%d_foreground'%lineid] = self['selectforeground'] | |
626 | cfg['Cellrowid.%d_background'%lineid] = self['selectbackground'] | |
627 | cfg['Cellframerowid.%d_background'%lineid] = self['selectbackground'] | |
628 | #cfg['Cellframerowid%d_relief'%row] = 'raised' | |
629 | ||
630 | if self._cursel != []: | |
631 | cursel = self._cursel[0] | |
632 | lineid = self._lineitems[cursel] | |
633 | if cursel != None and cursel != row: | |
634 | cfg['Cellrowid.%d_foreground'%lineid] = self['foreground'] | |
635 | cfg['Cellrowid.%d_background'%lineid] = self['background'] | |
636 | cfg['Cellframerowid.%d_background'%lineid] = self['background'] | |
637 | #cfg['Cellframerowid%d_relief'%cursel] = 'flat' | |
638 | ||
639 | apply(self.configure, (), cfg) | |
640 | self._cursel = [row] | |
641 | ||
642 | cmd = self['command'] | |
643 | if callable(cmd): | |
644 | cmd() | |
645 | ||
646 | ||
647 | ||
648 | def _cellframedblclick(self, event): | |
649 | #print 'double click cell frame' | |
650 | cmd = self['dblclickcommand'] | |
651 | if callable(cmd): | |
652 | cmd() | |
653 | ||
654 | def _celldblclick(self, event): | |
655 | #print 'double click cell' | |
656 | cmd = self['dblclickcommand'] | |
657 | if callable(cmd): | |
658 | cmd() | |
659 | ||
660 | if __name__ == '__main__': | |
661 | ||
662 | rootWin = Tkinter.Tk() | |
663 | ||
664 | Pmw.initialise() | |
665 | ||
666 | rootWin.title('MultiColumnListbox Demo') | |
667 | rootWin.configure(width = 500, height = 300) | |
668 | rootWin.update() | |
669 | ||
670 | def dbl(): | |
671 | print listbox.getcurselection() | |
672 | ||
673 | listbox = MultiColumnListbox(rootWin, | |
674 | #usehullsize = 1, | |
675 | labellist = ('Column 0', | |
676 | 'Column 1', | |
677 | 'Column 2', | |
678 | 'Column 3', | |
679 | 'Column 4', | |
680 | #'Column 5', | |
681 | #'Column 6', | |
682 | #'Column 7', | |
683 | #'Column 8', | |
684 | #'Column 9', | |
685 | ), | |
686 | horizflex = 'expand', | |
687 | #vertflex = 'elastic', | |
688 | dblclickcommand = dbl, | |
689 | ) | |
690 | ||
691 | ||
692 | #print 'start adding item' | |
693 | for i in range(20): | |
694 | r = {} | |
695 | for j in range(5): | |
696 | r[('Column %d'%j)] = 'Really long item name %d'%i | |
697 | listbox.addrow(r) | |
698 | #print 'items added' | |
699 | ||
700 | listbox.pack(expand = 1, fill = 'both', padx = 10, pady = 10) | |
701 | ||
702 | ||
703 | exitButton = Tkinter.Button(rootWin, text="Quit", command=rootWin.quit) | |
704 | exitButton.pack(side = 'left', padx = 10, pady = 10) | |
705 | ||
706 | rootWin.mainloop() |