4 class ScrolledCanvas(Pmw.MegaWidget):
5 def __init__(self, parent = None, **kw):
7 # Define the megawidget options.
10 ('borderframe', 0, INITOPT),
11 ('canvasmargin', 0, INITOPT),
12 ('hscrollmode', 'dynamic', self._hscrollMode),
13 ('labelmargin', 0, INITOPT),
14 ('labelpos', None, INITOPT),
15 ('scrollmargin', 2, INITOPT),
16 ('usehullsize', 0, INITOPT),
17 ('vscrollmode', 'dynamic', self._vscrollMode),
19 self.defineoptions(kw, optiondefs)
21 # Initialise the base class (after defining the options).
22 Pmw.MegaWidget.__init__(self, parent)
24 # Create the components.
25 self.origInterior = Pmw.MegaWidget.interior(self)
27 if self['usehullsize']:
28 self.origInterior.grid_propagate(0)
30 if self['borderframe']:
31 # Create a frame widget to act as the border of the canvas.
32 self._borderframe = self.createcomponent('borderframe',
34 Tkinter.Frame, (self.origInterior,),
38 self._borderframe.grid(row = 2, column = 2, sticky = 'news')
40 # Create the canvas widget.
41 self._canvas = self.createcomponent('canvas',
43 Tkinter.Canvas, (self._borderframe,),
44 highlightthickness = 0,
47 self._canvas.pack(fill = 'both', expand = 1)
49 # Create the canvas widget.
50 self._canvas = self.createcomponent('canvas',
52 Tkinter.Canvas, (self.origInterior,),
56 self._canvas.grid(row = 2, column = 2, sticky = 'news')
58 self.origInterior.grid_rowconfigure(2, weight = 1, minsize = 0)
59 self.origInterior.grid_columnconfigure(2, weight = 1, minsize = 0)
61 # Create the horizontal scrollbar
62 self._horizScrollbar = self.createcomponent('horizscrollbar',
64 Tkinter.Scrollbar, (self.origInterior,),
66 command=self._canvas.xview
69 # Create the vertical scrollbar
70 self._vertScrollbar = self.createcomponent('vertscrollbar',
72 Tkinter.Scrollbar, (self.origInterior,),
74 command=self._canvas.yview
77 self.createlabel(self.origInterior, childCols = 3, childRows = 3)
79 # Initialise instance variables.
80 self._horizScrollbarOn = 0
81 self._vertScrollbarOn = 0
82 self.scrollTimer = None
83 self._scrollRecurse = 0
84 self._horizScrollbarNeeded = 0
85 self._vertScrollbarNeeded = 0
86 self.setregionTimer = None
88 # Check keywords and initialise options.
89 self.initialiseoptions()
92 if self.scrollTimer is not None:
93 self.after_cancel(self.scrollTimer)
94 self.scrollTimer = None
95 if self.setregionTimer is not None:
96 self.after_cancel(self.setregionTimer)
97 self.setregionTimer = None
98 Pmw.MegaWidget.destroy(self)
100 # ======================================================================
107 def resizescrollregion(self):
108 if self.setregionTimer is None:
109 self.setregionTimer = self.after_idle(self._setRegion)
111 # ======================================================================
113 # Configuration methods.
115 def _hscrollMode(self):
116 # The horizontal scroll mode has been configured.
118 mode = self['hscrollmode']
121 if not self._horizScrollbarOn:
122 self._toggleHorizScrollbar()
123 elif mode == 'dynamic':
124 if self._horizScrollbarNeeded != self._horizScrollbarOn:
125 self._toggleHorizScrollbar()
127 if self._horizScrollbarOn:
128 self._toggleHorizScrollbar()
130 message = 'bad hscrollmode option "%s": should be static, dynamic, or none' % mode
131 raise ValueError, message
133 self._configureScrollCommands()
135 def _vscrollMode(self):
136 # The vertical scroll mode has been configured.
138 mode = self['vscrollmode']
141 if not self._vertScrollbarOn:
142 self._toggleVertScrollbar()
143 elif mode == 'dynamic':
144 if self._vertScrollbarNeeded != self._vertScrollbarOn:
145 self._toggleVertScrollbar()
147 if self._vertScrollbarOn:
148 self._toggleVertScrollbar()
150 message = 'bad vscrollmode option "%s": should be static, dynamic, or none' % mode
151 raise ValueError, message
153 self._configureScrollCommands()
155 # ======================================================================
159 def _configureScrollCommands(self):
160 # If both scrollmodes are not dynamic we can save a lot of
161 # time by not having to create an idle job to handle the
164 # Clean up previous scroll commands to prevent memory leak.
165 tclCommandName = str(self._canvas.cget('xscrollcommand'))
166 if tclCommandName != '':
167 self._canvas.deletecommand(tclCommandName)
168 tclCommandName = str(self._canvas.cget('yscrollcommand'))
169 if tclCommandName != '':
170 self._canvas.deletecommand(tclCommandName)
172 if self['hscrollmode'] == self['vscrollmode'] == 'dynamic':
173 self._canvas.configure(
174 xscrollcommand=self._scrollBothLater,
175 yscrollcommand=self._scrollBothLater
178 self._canvas.configure(
179 xscrollcommand=self._scrollXNow,
180 yscrollcommand=self._scrollYNow
183 def _scrollXNow(self, first, last):
184 self._horizScrollbar.set(first, last)
185 self._horizScrollbarNeeded = ((first, last) != ('0', '1'))
187 if self['hscrollmode'] == 'dynamic':
188 if self._horizScrollbarNeeded != self._horizScrollbarOn:
189 self._toggleHorizScrollbar()
191 def _scrollYNow(self, first, last):
192 self._vertScrollbar.set(first, last)
193 self._vertScrollbarNeeded = ((first, last) != ('0', '1'))
195 if self['vscrollmode'] == 'dynamic':
196 if self._vertScrollbarNeeded != self._vertScrollbarOn:
197 self._toggleVertScrollbar()
199 def _scrollBothLater(self, first, last):
200 # Called by the canvas to set the horizontal or vertical
201 # scrollbar when it has scrolled or changed scrollregion.
203 if self.scrollTimer is None:
204 self.scrollTimer = self.after_idle(self._scrollBothNow)
206 def _scrollBothNow(self):
207 # This performs the function of _scrollXNow and _scrollYNow.
208 # If one is changed, the other should be updated to match.
209 self.scrollTimer = None
211 # Call update_idletasks to make sure that the containing frame
212 # has been resized before we attempt to set the scrollbars.
213 # Otherwise the scrollbars may be mapped/unmapped continuously.
214 self._scrollRecurse = self._scrollRecurse + 1
215 self.update_idletasks()
216 self._scrollRecurse = self._scrollRecurse - 1
217 if self._scrollRecurse != 0:
220 xview = self._canvas.xview()
221 yview = self._canvas.yview()
222 self._horizScrollbar.set(xview[0], xview[1])
223 self._vertScrollbar.set(yview[0], yview[1])
225 self._horizScrollbarNeeded = (xview != (0.0, 1.0))
226 self._vertScrollbarNeeded = (yview != (0.0, 1.0))
228 # If both horizontal and vertical scrollmodes are dynamic and
229 # currently only one scrollbar is mapped and both should be
230 # toggled, then unmap the mapped scrollbar. This prevents a
231 # continuous mapping and unmapping of the scrollbars.
232 if (self['hscrollmode'] == self['vscrollmode'] == 'dynamic' and
233 self._horizScrollbarNeeded != self._horizScrollbarOn and
234 self._vertScrollbarNeeded != self._vertScrollbarOn and
235 self._vertScrollbarOn != self._horizScrollbarOn):
236 if self._horizScrollbarOn:
237 self._toggleHorizScrollbar()
239 self._toggleVertScrollbar()
242 if self['hscrollmode'] == 'dynamic':
243 if self._horizScrollbarNeeded != self._horizScrollbarOn:
244 self._toggleHorizScrollbar()
246 if self['vscrollmode'] == 'dynamic':
247 if self._vertScrollbarNeeded != self._vertScrollbarOn:
248 self._toggleVertScrollbar()
250 def _toggleHorizScrollbar(self):
252 self._horizScrollbarOn = not self._horizScrollbarOn
254 interior = self.origInterior
255 if self._horizScrollbarOn:
256 self._horizScrollbar.grid(row = 4, column = 2, sticky = 'news')
257 interior.grid_rowconfigure(3, minsize = self['scrollmargin'])
259 self._horizScrollbar.grid_forget()
260 interior.grid_rowconfigure(3, minsize = 0)
262 def _toggleVertScrollbar(self):
264 self._vertScrollbarOn = not self._vertScrollbarOn
266 interior = self.origInterior
267 if self._vertScrollbarOn:
268 self._vertScrollbar.grid(row = 2, column = 4, sticky = 'news')
269 interior.grid_columnconfigure(3, minsize = self['scrollmargin'])
271 self._vertScrollbar.grid_forget()
272 interior.grid_columnconfigure(3, minsize = 0)
274 def _setRegion(self):
275 self.setregionTimer = None
277 region = self._canvas.bbox('all')
278 if region is not None:
279 canvasmargin = self['canvasmargin']
280 region = (region[0] - canvasmargin, region[1] - canvasmargin,
281 region[2] + canvasmargin, region[3] + canvasmargin)
282 self._canvas.configure(scrollregion = region)
284 # Need to explicitly forward this to override the stupid
285 # (grid_)bbox method inherited from Tkinter.Frame.Grid.
286 def bbox(self, *args):
287 return apply(self._canvas.bbox, args)
289 Pmw.forwardmethods(ScrolledCanvas, Tkinter.Canvas, '_canvas')