# a frame which may contain several resizable sub-frames
class PanedWidget(Pmw
.MegaWidget
):
def __init__(self
, parent
= None, **kw
):
# Define the megawidget options.
('orient', 'vertical', INITOPT
),
('separatorrelief', 'sunken', INITOPT
),
('separatorthickness', 2, INITOPT
),
('handlesize', 8, INITOPT
),
('hull_width', 400, None),
('hull_height', 400, None),
self
.defineoptions(kw
, optiondefs
,
dynamicGroups
= ('Frame', 'Separator', 'Handle'))
# Initialise the base class (after defining the options).
Pmw
.MegaWidget
.__init
__(self
, parent
)
self
.bind('<Configure>', self
._handleConfigure
)
if self
['orient'] not in ('horizontal', 'vertical'):
raise ValueError, 'bad orient option ' + repr(self
['orient']) + \
': must be either \'horizontal\' or \'vertical\''
self
._separatorThickness
= self
['separatorthickness']
self
._handleSize
= self
['handlesize']
self
._paneNames
= [] # List of pane names
self
._paneAttrs
= {} # Map from pane name to pane info
# Check keywords and initialise options.
def insert(self
, name
, before
= 0, **kw
):
# Parse <kw> for options.
self
._initPaneOptions
(name
)
self
._parsePaneOptions
(name
, kw
)
insertPos
= self
._nameToIndex
(before
)
atEnd
= (insertPos
== len(self
._paneNames
))
self
._paneNames
[insertPos
:insertPos
] = [name
]
self
._frame
[name
] = self
.createcomponent(name
,
Tkinter
.Frame
, (self
.interior(),))
# Add separator, if necessary.
if len(self
._paneNames
) > 1:
self
._separator
.append(None)
self
._button
.append(None)
# Add the new frame and adjust the PanedWidget
if size
> 0 or self
._relsize
[name
] is not None:
if self
['orient'] == 'vertical':
self
._frame
[name
].place(x
=0, relwidth
=1,
height
=size
, y
=self
._totalSize
)
self
._frame
[name
].place(y
=0, relheight
=1,
width
=size
, x
=self
._totalSize
)
if self
['orient'] == 'vertical':
self
._frame
[name
].place(x
=0, relwidth
=1,
self
._frame
[name
].place(y
=0, relheight
=1,
self
._totalSize
= self
._totalSize
+ self
._size
[name
]
def add(self
, name
, **kw
):
return apply(self
.insert
, (name
, len(self
._paneNames
)), kw
)
deletePos
= self
._nameToIndex
(name
)
name
= self
._paneNames
[deletePos
]
self
.destroycomponent(name
)
del self
._paneNames
[deletePos
]
last
= len(self
._paneNames
)
del self
._separator
[last
]
self
.destroycomponent(self
._sepName
(last
))
self
.destroycomponent(self
._buttonName
(last
))
def setnaturalsize(self
):
for name
in self
._paneNames
:
frame
= self
._frame
[name
]
w
= frame
.winfo_reqwidth()
h
= frame
.winfo_reqheight()
totalWidth
= totalWidth
+ w
totalHeight
= totalHeight
+ h
# Note that, since the hull is a frame, the width and height
# options specify the geometry *outside* the borderwidth and
bw
= string
.atoi(str(self
.cget('hull_borderwidth')))
hl
= string
.atoi(str(self
.cget('hull_highlightthickness')))
if str(self
.cget('orient')) == 'horizontal':
totalWidth
= totalWidth
+ extra
maxHeight
= maxHeight
+ extra
self
.configure(hull_width
= totalWidth
, hull_height
= maxHeight
)
totalHeight
= (totalHeight
+ extra
+
(len(self
._paneNames
) - 1) * self
._separatorThickness
)
maxWidth
= maxWidth
+ extra
self
.configure(hull_width
= maxWidth
, hull_height
= totalHeight
)
def move(self
, name
, newPos
, newPosOffset
= 0):
# see if we can spare ourselves some work
numPanes
= len(self
._paneNames
)
newPos
= self
._nameToIndex
(newPos
) + newPosOffset
if newPos
< 0 or newPos
>=numPanes
:
deletePos
= self
._nameToIndex
(name
)
# inserting over ourself is a no-op
# delete name from old position in list
name
= self
._paneNames
[deletePos
]
del self
._paneNames
[deletePos
]
self
._paneNames
[newPos
:newPos
] = [name
]
# force everything to redraw
def _nameToIndex(self
, nameOrIndex
):
pos
= self
._paneNames
.index(nameOrIndex
)
def _initPaneOptions(self
, name
):
self
._relsize
[name
] = None
self
._relmin
[name
] = None
self
._relmax
[name
] = None
def _parsePaneOptions(self
, name
, args
):
# Parse <args> for options.
for arg
, value
in args
.items():
if type(value
) == types
.FloatType
:
value
= self
._absSize
(relvalue
)
self
._size
[name
], self
._relsize
[name
] = value
, relvalue
self
._min
[name
], self
._relmin
[name
] = value
, relvalue
self
._max
[name
], self
._relmax
[name
] = value
, relvalue
raise ValueError, 'keyword must be "size", "min", or "max"'
def _absSize(self
, relvalue
):
return int(round(relvalue
* self
._majorSize
))
return 'separator-%d' % n
def _buttonName(self
, n
):
n
= len(self
._paneNames
) - 1
downFunc
= lambda event
, s
= self
, num
=n
: s
._btnDown
(event
, num
)
upFunc
= lambda event
, s
= self
, num
=n
: s
._btnUp
(event
, num
)
moveFunc
= lambda event
, s
= self
, num
=n
: s
._btnMove
(event
, num
)
# Create the line dividing the panes.
sep
= self
.createcomponent(self
._sepName
(n
),
Tkinter
.Frame
, (self
.interior(),),
relief
= self
['separatorrelief'])
self
._separator
.append(sep
)
sep
.bind('<ButtonPress-1>', downFunc
)
sep
.bind('<Any-ButtonRelease-1>', upFunc
)
sep
.bind('<B1-Motion>', moveFunc
)
if self
['orient'] == 'vertical':
cursor
= 'sb_v_double_arrow'
sep
.configure(height
= self
._separatorThickness
,
width
= 10000, cursor
= cursor
)
cursor
= 'sb_h_double_arrow'
sep
.configure(width
= self
._separatorThickness
,
height
= 10000, cursor
= cursor
)
self
._totalSize
= self
._totalSize
+ self
._separatorThickness
# Create the handle on the dividing line.
handle
= self
.createcomponent(self
._buttonName
(n
),
Tkinter
.Frame
, (self
.interior(),),
width
= self
._handleSize
,
height
= self
._handleSize
,
self
._button
.append(handle
)
handle
.bind('<ButtonPress-1>', downFunc
)
handle
.bind('<Any-ButtonRelease-1>', upFunc
)
handle
.bind('<B1-Motion>', moveFunc
)
for i
in range(1, len(self
._paneNames
)):
self
._separator
[i
].tkraise()
for i
in range(1, len(self
._paneNames
)):
self
._button
[i
].tkraise()
def _btnUp(self
, event
, item
):
self
._button
[item
].configure(relief
='raised')
def _btnDown(self
, event
, item
):
self
._button
[item
].configure(relief
='sunken')
self
._getMotionLimit
(item
)
def _handleConfigure(self
, event
= None):
iterRange
= list(self
._paneNames
)
if self
._majorSize
> self
._totalSize
:
n
= self
._majorSize
- self
._totalSize
self
._iterate
(iterRange
, self
._grow
, n
)
elif self
._majorSize
< self
._totalSize
:
n
= self
._totalSize
- self
._majorSize
self
._iterate
(iterRange
, self
._shrink
, n
)
def _getNaturalSizes(self
):
# Must call this in order to get correct winfo_width, winfo_height
if self
['orient'] == 'vertical':
self
._majorSize
= self
.winfo_height()
self
._minorSize
= self
.winfo_width()
majorspec
= Tkinter
.Frame
.winfo_reqheight
self
._majorSize
= self
.winfo_width()
self
._minorSize
= self
.winfo_height()
majorspec
= Tkinter
.Frame
.winfo_reqwidth
bw
= string
.atoi(str(self
.cget('hull_borderwidth')))
hl
= string
.atoi(str(self
.cget('hull_highlightthickness')))
self
._majorSize
= self
._majorSize
- extra
self
._minorSize
= self
._minorSize
- extra
for name
in self
._paneNames
:
# adjust the absolute sizes first...
if self
._relsize
[name
] is None:
if self
._size
[name
] == 0:
self
._size
[name
] = apply(majorspec
, (self
._frame
[name
],))
self
._size
[name
] = self
._absSize
(self
._relsize
[name
])
if self
._relmin
[name
] is not None:
self
._min
[name
] = self
._absSize
(self
._relmin
[name
])
if self
._relmax
[name
] is not None:
self
._max
[name
] = self
._absSize
(self
._relmax
[name
])
if self
._size
[name
] < self
._min
[name
]:
self
._size
[name
] = self
._min
[name
]
if self
._size
[name
] > self
._max
[name
]:
self
._size
[name
] = self
._max
[name
]
self
._totalSize
= self
._totalSize
+ self
._size
[name
]
self
._totalSize
= (self
._totalSize
+
(len(self
._paneNames
) - 1) * self
._separatorThickness
)
if self
._relsize
[name
] is not None:
self
._relsize
[name
] = round(self
._size
[name
]) / self
._majorSize
def _iterate(self
, names
, proc
, n
):
def _grow(self
, name
, n
):
canGrow
= self
._max
[name
] - self
._size
[name
]
self
._size
[name
] = self
._size
[name
] + n
self
._size
[name
] = self
._max
[name
]
def _shrink(self
, name
, n
):
canShrink
= self
._size
[name
] - self
._min
[name
]
self
._size
[name
] = self
._size
[name
] - n
self
._size
[name
] = self
._min
[name
]
for name
in self
._paneNames
:
if self
['orient'] == 'vertical':
self
._frame
[name
].place(x
= 0, relwidth
= 1,
self
._frame
[name
].place(y
= 0, relheight
= 1,
totalSize
= totalSize
+ size
+ self
._separatorThickness
# Invoke the callback command
cmd(map(lambda x
, s
= self
: s
._size
[x
], self
._paneNames
))
if len(self
._paneNames
) == 0:
if self
['orient'] == 'vertical':
btnp
= self
._minorSize
- 13
firstPane
= self
._paneNames
[0]
totalSize
= self
._size
[firstPane
]
last
= len(self
._paneNames
) - 1
# loop from first to last, inclusive
for i
in range(1, last
+ 1):
handlepos
= totalSize
- 3
prevSize
= self
._size
[self
._paneNames
[i
- 1]]
nextSize
= self
._size
[self
._paneNames
[i
]]
offset1
= (8 - prevSize
) / 2
offset2
= (nextSize
- 8) / 2
handlepos
= handlepos
+ offset1
if self
['orient'] == 'vertical':
height
= 8 - offset1
+ offset2
self
._button
[i
].configure(height
= height
)
self
._button
[i
].place(x
= btnp
, y
= handlepos
)
self
._button
[i
].place_forget()
self
._separator
[i
].place(x
= 0, y
= totalSize
,
width
= 8 - offset1
+ offset2
self
._button
[i
].configure(width
= width
)
self
._button
[i
].place(y
= btnp
, x
= handlepos
)
self
._button
[i
].place_forget()
self
._separator
[i
].place(y
= 0, x
= totalSize
,
totalSize
= totalSize
+ nextSize
+ self
._separatorThickness
return self
._frame
[self
._paneNames
[self
._nameToIndex
(name
)]]
# Return the name of all panes
return list(self
._paneNames
)
def configurepane(self
, name
, **kw
):
name
= self
._paneNames
[self
._nameToIndex
(name
)]
self
._parsePaneOptions
(name
, kw
)
def _getMotionLimit(self
, item
):
curBefore
= (item
- 1) * self
._separatorThickness
minBefore
, maxBefore
= curBefore
, curBefore
for name
in self
._paneNames
[:item
]:
curBefore
= curBefore
+ self
._size
[name
]
minBefore
= minBefore
+ self
._min
[name
]
maxBefore
= maxBefore
+ self
._max
[name
]
curAfter
= (len(self
._paneNames
) - item
) * self
._separatorThickness
minAfter
, maxAfter
= curAfter
, curAfter
for name
in self
._paneNames
[item
:]:
curAfter
= curAfter
+ self
._size
[name
]
minAfter
= minAfter
+ self
._min
[name
]
maxAfter
= maxAfter
+ self
._max
[name
]
beforeToGo
= min(curBefore
- minBefore
, maxAfter
- curAfter
)
afterToGo
= min(curAfter
- minAfter
, maxBefore
- curBefore
)
self
._beforeLimit
= curBefore
- beforeToGo
self
._afterLimit
= curBefore
+ afterToGo
self
._curSize
= curBefore
# Compress the motion so that update is quick even on slow machines
# theRootp = root position (either rootx or rooty)
def _btnMove(self
, event
, item
):
if self
._movePending
== 0:
self
._timerId
= self
.after_idle(
lambda s
= self
, i
= item
: s
._btnMoveCompressed
(i
))
if self
._timerId
is not None:
self
.after_cancel(self
._timerId
)
Pmw
.MegaWidget
.destroy(self
)
def _btnMoveCompressed(self
, item
):
if not self
._buttonIsDown
:
if self
['orient'] == 'vertical':
p
= self
._rootp
.y_root
- self
.winfo_rooty()
p
= self
._rootp
.x_root
- self
.winfo_rootx()
if p
< self
._beforeLimit
:
if p
>= self
._afterLimit
:
self
._calculateChange
(item
, p
)
# Calculate the change in response to mouse motions
def _calculateChange(self
, item
, p
):
self
._moveBefore
(item
, p
)
def _moveBefore(self
, item
, p
):
# Shrink the frames before
iterRange
= list(self
._paneNames
[:item
])
self
._iterate
(iterRange
, self
._shrink
, n
)
# Adjust the frames after
iterRange
= self
._paneNames
[item
:]
self
._iterate
(iterRange
, self
._grow
, n
)
def _moveAfter(self
, item
, p
):
# Shrink the frames after
iterRange
= self
._paneNames
[item
:]
self
._iterate
(iterRange
, self
._shrink
, n
)
# Adjust the frames before
iterRange
= list(self
._paneNames
[:item
])
self
._iterate
(iterRange
, self
._grow
, n
)