Initial commit of OpenSPARC T2 architecture model.
[OpenSPARC-T2-SAM] / sam-t2 / devtools / amd64 / lib / python2.4 / site-packages / Pmw / Pmw_1_2 / lib / PmwPanedWidget.py
CommitLineData
920dae64
AT
1# PanedWidget
2# a frame which may contain several resizable sub-frames
3
4import string
5import sys
6import types
7import Tkinter
8import Pmw
9
10class PanedWidget(Pmw.MegaWidget):
11
12 def __init__(self, parent = None, **kw):
13
14 # Define the megawidget options.
15 INITOPT = Pmw.INITOPT
16 optiondefs = (
17 ('command', None, None),
18 ('orient', 'vertical', INITOPT),
19 ('separatorrelief', 'sunken', INITOPT),
20 ('separatorthickness', 2, INITOPT),
21 ('handlesize', 8, INITOPT),
22 ('hull_width', 400, None),
23 ('hull_height', 400, None),
24 )
25 self.defineoptions(kw, optiondefs,
26 dynamicGroups = ('Frame', 'Separator', 'Handle'))
27
28 # Initialise the base class (after defining the options).
29 Pmw.MegaWidget.__init__(self, parent)
30
31 self.bind('<Configure>', self._handleConfigure)
32
33 if self['orient'] not in ('horizontal', 'vertical'):
34 raise ValueError, 'bad orient option ' + repr(self['orient']) + \
35 ': must be either \'horizontal\' or \'vertical\''
36
37 self._separatorThickness = self['separatorthickness']
38 self._handleSize = self['handlesize']
39 self._paneNames = [] # List of pane names
40 self._paneAttrs = {} # Map from pane name to pane info
41
42 self._timerId = None
43 self._frame = {}
44 self._separator = []
45 self._button = []
46 self._totalSize = 0
47 self._movePending = 0
48 self._relsize = {}
49 self._relmin = {}
50 self._relmax = {}
51 self._size = {}
52 self._min = {}
53 self._max = {}
54 self._rootp = None
55 self._curSize = None
56 self._beforeLimit = None
57 self._afterLimit = None
58 self._buttonIsDown = 0
59 self._majorSize = 100
60 self._minorSize = 100
61
62 # Check keywords and initialise options.
63 self.initialiseoptions()
64
65 def insert(self, name, before = 0, **kw):
66 # Parse <kw> for options.
67 self._initPaneOptions(name)
68 self._parsePaneOptions(name, kw)
69
70 insertPos = self._nameToIndex(before)
71 atEnd = (insertPos == len(self._paneNames))
72
73 # Add the frame.
74 self._paneNames[insertPos:insertPos] = [name]
75 self._frame[name] = self.createcomponent(name,
76 (), 'Frame',
77 Tkinter.Frame, (self.interior(),))
78
79 # Add separator, if necessary.
80 if len(self._paneNames) > 1:
81 self._addSeparator()
82 else:
83 self._separator.append(None)
84 self._button.append(None)
85
86 # Add the new frame and adjust the PanedWidget
87 if atEnd:
88 size = self._size[name]
89 if size > 0 or self._relsize[name] is not None:
90 if self['orient'] == 'vertical':
91 self._frame[name].place(x=0, relwidth=1,
92 height=size, y=self._totalSize)
93 else:
94 self._frame[name].place(y=0, relheight=1,
95 width=size, x=self._totalSize)
96 else:
97 if self['orient'] == 'vertical':
98 self._frame[name].place(x=0, relwidth=1,
99 y=self._totalSize)
100 else:
101 self._frame[name].place(y=0, relheight=1,
102 x=self._totalSize)
103 else:
104 self._updateSizes()
105
106 self._totalSize = self._totalSize + self._size[name]
107 return self._frame[name]
108
109 def add(self, name, **kw):
110 return apply(self.insert, (name, len(self._paneNames)), kw)
111
112 def delete(self, name):
113 deletePos = self._nameToIndex(name)
114 name = self._paneNames[deletePos]
115 self.destroycomponent(name)
116 del self._paneNames[deletePos]
117 del self._frame[name]
118 del self._size[name]
119 del self._min[name]
120 del self._max[name]
121 del self._relsize[name]
122 del self._relmin[name]
123 del self._relmax[name]
124
125 last = len(self._paneNames)
126 del self._separator[last]
127 del self._button[last]
128 if last > 0:
129 self.destroycomponent(self._sepName(last))
130 self.destroycomponent(self._buttonName(last))
131
132 self._plotHandles()
133
134 def setnaturalsize(self):
135 self.update_idletasks()
136 totalWidth = 0
137 totalHeight = 0
138 maxWidth = 0
139 maxHeight = 0
140 for name in self._paneNames:
141 frame = self._frame[name]
142 w = frame.winfo_reqwidth()
143 h = frame.winfo_reqheight()
144 totalWidth = totalWidth + w
145 totalHeight = totalHeight + h
146 if maxWidth < w:
147 maxWidth = w
148 if maxHeight < h:
149 maxHeight = h
150
151 # Note that, since the hull is a frame, the width and height
152 # options specify the geometry *outside* the borderwidth and
153 # highlightthickness.
154 bw = string.atoi(str(self.cget('hull_borderwidth')))
155 hl = string.atoi(str(self.cget('hull_highlightthickness')))
156 extra = (bw + hl) * 2
157 if str(self.cget('orient')) == 'horizontal':
158 totalWidth = totalWidth + extra
159 maxHeight = maxHeight + extra
160 self.configure(hull_width = totalWidth, hull_height = maxHeight)
161 else:
162 totalHeight = (totalHeight + extra +
163 (len(self._paneNames) - 1) * self._separatorThickness)
164 maxWidth = maxWidth + extra
165 self.configure(hull_width = maxWidth, hull_height = totalHeight)
166
167 def move(self, name, newPos, newPosOffset = 0):
168
169 # see if we can spare ourselves some work
170 numPanes = len(self._paneNames)
171 if numPanes < 2:
172 return
173
174 newPos = self._nameToIndex(newPos) + newPosOffset
175 if newPos < 0 or newPos >=numPanes:
176 return
177
178 deletePos = self._nameToIndex(name)
179
180 if deletePos == newPos:
181 # inserting over ourself is a no-op
182 return
183
184 # delete name from old position in list
185 name = self._paneNames[deletePos]
186 del self._paneNames[deletePos]
187
188 # place in new position
189 self._paneNames[newPos:newPos] = [name]
190
191 # force everything to redraw
192 self._plotHandles()
193 self._updateSizes()
194
195 def _nameToIndex(self, nameOrIndex):
196 try:
197 pos = self._paneNames.index(nameOrIndex)
198 except ValueError:
199 pos = nameOrIndex
200
201 return pos
202
203 def _initPaneOptions(self, name):
204 # Set defaults.
205 self._size[name] = 0
206 self._relsize[name] = None
207 self._min[name] = 0
208 self._relmin[name] = None
209 self._max[name] = 100000
210 self._relmax[name] = None
211
212 def _parsePaneOptions(self, name, args):
213 # Parse <args> for options.
214 for arg, value in args.items():
215 if type(value) == types.FloatType:
216 relvalue = value
217 value = self._absSize(relvalue)
218 else:
219 relvalue = None
220
221 if arg == 'size':
222 self._size[name], self._relsize[name] = value, relvalue
223 elif arg == 'min':
224 self._min[name], self._relmin[name] = value, relvalue
225 elif arg == 'max':
226 self._max[name], self._relmax[name] = value, relvalue
227 else:
228 raise ValueError, 'keyword must be "size", "min", or "max"'
229
230 def _absSize(self, relvalue):
231 return int(round(relvalue * self._majorSize))
232
233 def _sepName(self, n):
234 return 'separator-%d' % n
235
236 def _buttonName(self, n):
237 return 'handle-%d' % n
238
239 def _addSeparator(self):
240 n = len(self._paneNames) - 1
241
242 downFunc = lambda event, s = self, num=n: s._btnDown(event, num)
243 upFunc = lambda event, s = self, num=n: s._btnUp(event, num)
244 moveFunc = lambda event, s = self, num=n: s._btnMove(event, num)
245
246 # Create the line dividing the panes.
247 sep = self.createcomponent(self._sepName(n),
248 (), 'Separator',
249 Tkinter.Frame, (self.interior(),),
250 borderwidth = 1,
251 relief = self['separatorrelief'])
252 self._separator.append(sep)
253
254 sep.bind('<ButtonPress-1>', downFunc)
255 sep.bind('<Any-ButtonRelease-1>', upFunc)
256 sep.bind('<B1-Motion>', moveFunc)
257
258 if self['orient'] == 'vertical':
259 cursor = 'sb_v_double_arrow'
260 sep.configure(height = self._separatorThickness,
261 width = 10000, cursor = cursor)
262 else:
263 cursor = 'sb_h_double_arrow'
264 sep.configure(width = self._separatorThickness,
265 height = 10000, cursor = cursor)
266
267 self._totalSize = self._totalSize + self._separatorThickness
268
269 # Create the handle on the dividing line.
270 handle = self.createcomponent(self._buttonName(n),
271 (), 'Handle',
272 Tkinter.Frame, (self.interior(),),
273 relief = 'raised',
274 borderwidth = 1,
275 width = self._handleSize,
276 height = self._handleSize,
277 cursor = cursor,
278 )
279 self._button.append(handle)
280
281 handle.bind('<ButtonPress-1>', downFunc)
282 handle.bind('<Any-ButtonRelease-1>', upFunc)
283 handle.bind('<B1-Motion>', moveFunc)
284
285 self._plotHandles()
286
287 for i in range(1, len(self._paneNames)):
288 self._separator[i].tkraise()
289 for i in range(1, len(self._paneNames)):
290 self._button[i].tkraise()
291
292 def _btnUp(self, event, item):
293 self._buttonIsDown = 0
294 self._updateSizes()
295 try:
296 self._button[item].configure(relief='raised')
297 except:
298 pass
299
300 def _btnDown(self, event, item):
301 self._button[item].configure(relief='sunken')
302 self._getMotionLimit(item)
303 self._buttonIsDown = 1
304 self._movePending = 0
305
306 def _handleConfigure(self, event = None):
307 self._getNaturalSizes()
308 if self._totalSize == 0:
309 return
310
311 iterRange = list(self._paneNames)
312 iterRange.reverse()
313 if self._majorSize > self._totalSize:
314 n = self._majorSize - self._totalSize
315 self._iterate(iterRange, self._grow, n)
316 elif self._majorSize < self._totalSize:
317 n = self._totalSize - self._majorSize
318 self._iterate(iterRange, self._shrink, n)
319
320 self._plotHandles()
321 self._updateSizes()
322
323 def _getNaturalSizes(self):
324 # Must call this in order to get correct winfo_width, winfo_height
325 self.update_idletasks()
326
327 self._totalSize = 0
328
329 if self['orient'] == 'vertical':
330 self._majorSize = self.winfo_height()
331 self._minorSize = self.winfo_width()
332 majorspec = Tkinter.Frame.winfo_reqheight
333 else:
334 self._majorSize = self.winfo_width()
335 self._minorSize = self.winfo_height()
336 majorspec = Tkinter.Frame.winfo_reqwidth
337
338 bw = string.atoi(str(self.cget('hull_borderwidth')))
339 hl = string.atoi(str(self.cget('hull_highlightthickness')))
340 extra = (bw + hl) * 2
341 self._majorSize = self._majorSize - extra
342 self._minorSize = self._minorSize - extra
343
344 if self._majorSize < 0:
345 self._majorSize = 0
346 if self._minorSize < 0:
347 self._minorSize = 0
348
349 for name in self._paneNames:
350 # adjust the absolute sizes first...
351 if self._relsize[name] is None:
352 #special case
353 if self._size[name] == 0:
354 self._size[name] = apply(majorspec, (self._frame[name],))
355 self._setrel(name)
356 else:
357 self._size[name] = self._absSize(self._relsize[name])
358
359 if self._relmin[name] is not None:
360 self._min[name] = self._absSize(self._relmin[name])
361 if self._relmax[name] is not None:
362 self._max[name] = self._absSize(self._relmax[name])
363
364 # now adjust sizes
365 if self._size[name] < self._min[name]:
366 self._size[name] = self._min[name]
367 self._setrel(name)
368
369 if self._size[name] > self._max[name]:
370 self._size[name] = self._max[name]
371 self._setrel(name)
372
373 self._totalSize = self._totalSize + self._size[name]
374
375 # adjust for separators
376 self._totalSize = (self._totalSize +
377 (len(self._paneNames) - 1) * self._separatorThickness)
378
379 def _setrel(self, name):
380 if self._relsize[name] is not None:
381 if self._majorSize != 0:
382 self._relsize[name] = round(self._size[name]) / self._majorSize
383
384 def _iterate(self, names, proc, n):
385 for i in names:
386 n = apply(proc, (i, n))
387 if n == 0:
388 break
389
390 def _grow(self, name, n):
391 canGrow = self._max[name] - self._size[name]
392
393 if canGrow > n:
394 self._size[name] = self._size[name] + n
395 self._setrel(name)
396 return 0
397 elif canGrow > 0:
398 self._size[name] = self._max[name]
399 self._setrel(name)
400 n = n - canGrow
401
402 return n
403
404 def _shrink(self, name, n):
405 canShrink = self._size[name] - self._min[name]
406
407 if canShrink > n:
408 self._size[name] = self._size[name] - n
409 self._setrel(name)
410 return 0
411 elif canShrink > 0:
412 self._size[name] = self._min[name]
413 self._setrel(name)
414 n = n - canShrink
415
416 return n
417
418 def _updateSizes(self):
419 totalSize = 0
420
421 for name in self._paneNames:
422 size = self._size[name]
423 if self['orient'] == 'vertical':
424 self._frame[name].place(x = 0, relwidth = 1,
425 y = totalSize,
426 height = size)
427 else:
428 self._frame[name].place(y = 0, relheight = 1,
429 x = totalSize,
430 width = size)
431
432 totalSize = totalSize + size + self._separatorThickness
433
434 # Invoke the callback command
435 cmd = self['command']
436 if callable(cmd):
437 cmd(map(lambda x, s = self: s._size[x], self._paneNames))
438
439 def _plotHandles(self):
440 if len(self._paneNames) == 0:
441 return
442
443 if self['orient'] == 'vertical':
444 btnp = self._minorSize - 13
445 else:
446 h = self._minorSize
447
448 if h > 18:
449 btnp = 9
450 else:
451 btnp = h - 9
452
453 firstPane = self._paneNames[0]
454 totalSize = self._size[firstPane]
455
456 first = 1
457 last = len(self._paneNames) - 1
458
459 # loop from first to last, inclusive
460 for i in range(1, last + 1):
461
462 handlepos = totalSize - 3
463 prevSize = self._size[self._paneNames[i - 1]]
464 nextSize = self._size[self._paneNames[i]]
465
466 offset1 = 0
467
468 if i == first:
469 if prevSize < 4:
470 offset1 = 4 - prevSize
471 else:
472 if prevSize < 8:
473 offset1 = (8 - prevSize) / 2
474
475 offset2 = 0
476
477 if i == last:
478 if nextSize < 4:
479 offset2 = nextSize - 4
480 else:
481 if nextSize < 8:
482 offset2 = (nextSize - 8) / 2
483
484 handlepos = handlepos + offset1
485
486 if self['orient'] == 'vertical':
487 height = 8 - offset1 + offset2
488
489 if height > 1:
490 self._button[i].configure(height = height)
491 self._button[i].place(x = btnp, y = handlepos)
492 else:
493 self._button[i].place_forget()
494
495 self._separator[i].place(x = 0, y = totalSize,
496 relwidth = 1)
497 else:
498 width = 8 - offset1 + offset2
499
500 if width > 1:
501 self._button[i].configure(width = width)
502 self._button[i].place(y = btnp, x = handlepos)
503 else:
504 self._button[i].place_forget()
505
506 self._separator[i].place(y = 0, x = totalSize,
507 relheight = 1)
508
509 totalSize = totalSize + nextSize + self._separatorThickness
510
511 def pane(self, name):
512 return self._frame[self._paneNames[self._nameToIndex(name)]]
513
514 # Return the name of all panes
515 def panes(self):
516 return list(self._paneNames)
517
518 def configurepane(self, name, **kw):
519 name = self._paneNames[self._nameToIndex(name)]
520 self._parsePaneOptions(name, kw)
521 self._handleConfigure()
522
523 def updatelayout(self):
524 self._handleConfigure()
525
526 def _getMotionLimit(self, item):
527 curBefore = (item - 1) * self._separatorThickness
528 minBefore, maxBefore = curBefore, curBefore
529
530 for name in self._paneNames[:item]:
531 curBefore = curBefore + self._size[name]
532 minBefore = minBefore + self._min[name]
533 maxBefore = maxBefore + self._max[name]
534
535 curAfter = (len(self._paneNames) - item) * self._separatorThickness
536 minAfter, maxAfter = curAfter, curAfter
537 for name in self._paneNames[item:]:
538 curAfter = curAfter + self._size[name]
539 minAfter = minAfter + self._min[name]
540 maxAfter = maxAfter + self._max[name]
541
542 beforeToGo = min(curBefore - minBefore, maxAfter - curAfter)
543 afterToGo = min(curAfter - minAfter, maxBefore - curBefore)
544
545 self._beforeLimit = curBefore - beforeToGo
546 self._afterLimit = curBefore + afterToGo
547 self._curSize = curBefore
548
549 self._plotHandles()
550
551 # Compress the motion so that update is quick even on slow machines
552 #
553 # theRootp = root position (either rootx or rooty)
554 def _btnMove(self, event, item):
555 self._rootp = event
556
557 if self._movePending == 0:
558 self._timerId = self.after_idle(
559 lambda s = self, i = item: s._btnMoveCompressed(i))
560 self._movePending = 1
561
562 def destroy(self):
563 if self._timerId is not None:
564 self.after_cancel(self._timerId)
565 self._timerId = None
566 Pmw.MegaWidget.destroy(self)
567
568 def _btnMoveCompressed(self, item):
569 if not self._buttonIsDown:
570 return
571
572 if self['orient'] == 'vertical':
573 p = self._rootp.y_root - self.winfo_rooty()
574 else:
575 p = self._rootp.x_root - self.winfo_rootx()
576
577 if p == self._curSize:
578 self._movePending = 0
579 return
580
581 if p < self._beforeLimit:
582 p = self._beforeLimit
583
584 if p >= self._afterLimit:
585 p = self._afterLimit
586
587 self._calculateChange(item, p)
588 self.update_idletasks()
589 self._movePending = 0
590
591 # Calculate the change in response to mouse motions
592 def _calculateChange(self, item, p):
593
594 if p < self._curSize:
595 self._moveBefore(item, p)
596 elif p > self._curSize:
597 self._moveAfter(item, p)
598
599 self._plotHandles()
600
601 def _moveBefore(self, item, p):
602 n = self._curSize - p
603
604 # Shrink the frames before
605 iterRange = list(self._paneNames[:item])
606 iterRange.reverse()
607 self._iterate(iterRange, self._shrink, n)
608
609 # Adjust the frames after
610 iterRange = self._paneNames[item:]
611 self._iterate(iterRange, self._grow, n)
612
613 self._curSize = p
614
615 def _moveAfter(self, item, p):
616 n = p - self._curSize
617
618 # Shrink the frames after
619 iterRange = self._paneNames[item:]
620 self._iterate(iterRange, self._shrink, n)
621
622 # Adjust the frames before
623 iterRange = list(self._paneNames[:item])
624 iterRange.reverse()
625 self._iterate(iterRange, self._grow, n)
626
627 self._curSize = p