Initial commit of OpenSPARC T2 architecture model.
[OpenSPARC-T2-SAM] / sam-t2 / devtools / v9 / lib / python2.4 / site-packages / Pmw / Pmw_1_2 / contrib / MCListbox.py
CommitLineData
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
29import string
30import Tkinter
31import Pmw
32
33class 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
660if __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()