Kano001's picture
Upload 498 files
d12bc25 verified
raw
history blame
23.6 kB
# DockingBar.py
# Ported directly (comments and all) from the samples at www.codeguru.com
# WARNING: Use at your own risk, as this interface is highly likely to change.
# Currently we support only one child per DockingBar. Later we need to add
# support for multiple children.
import struct
import win32api
import win32con
import win32ui
from pywin.mfc import afxres, window
clrBtnHilight = win32api.GetSysColor(win32con.COLOR_BTNHILIGHT)
clrBtnShadow = win32api.GetSysColor(win32con.COLOR_BTNSHADOW)
def CenterPoint(rect):
width = rect[2] - rect[0]
height = rect[3] - rect[1]
return rect[0] + width // 2, rect[1] + height // 2
def OffsetRect(rect, point):
(x, y) = point
return rect[0] + x, rect[1] + y, rect[2] + x, rect[3] + y
def DeflateRect(rect, point):
(x, y) = point
return rect[0] + x, rect[1] + y, rect[2] - x, rect[3] - y
def PtInRect(rect, pt):
return rect[0] <= pt[0] < rect[2] and rect[1] <= pt[1] < rect[3]
class DockingBar(window.Wnd):
def __init__(self, obj=None):
if obj is None:
obj = win32ui.CreateControlBar()
window.Wnd.__init__(self, obj)
self.dialog = None
self.nDockBarID = 0
self.sizeMin = 32, 32
self.sizeHorz = 200, 200
self.sizeVert = 200, 200
self.sizeFloat = 200, 200
self.bTracking = 0
self.bInRecalcNC = 0
self.cxEdge = 6
self.cxBorder = 3
self.cxGripper = 20
self.brushBkgd = win32ui.CreateBrush()
self.brushBkgd.CreateSolidBrush(win32api.GetSysColor(win32con.COLOR_BTNFACE))
# Support for diagonal resizing
self.cyBorder = 3
self.cCaptionSize = win32api.GetSystemMetrics(win32con.SM_CYSMCAPTION)
self.cMinWidth = win32api.GetSystemMetrics(win32con.SM_CXMIN)
self.cMinHeight = win32api.GetSystemMetrics(win32con.SM_CYMIN)
self.rectUndock = (0, 0, 0, 0)
def OnUpdateCmdUI(self, target, bDisableIfNoHndler):
return self.UpdateDialogControls(target, bDisableIfNoHndler)
def CreateWindow(
self,
parent,
childCreator,
title,
id,
style=win32con.WS_CHILD | win32con.WS_VISIBLE | afxres.CBRS_LEFT,
childCreatorArgs=(),
):
assert not (
(style & afxres.CBRS_SIZE_FIXED) and (style & afxres.CBRS_SIZE_DYNAMIC)
), "Invalid style"
self.rectClose = self.rectBorder = self.rectGripper = self.rectTracker = (
0,
0,
0,
0,
)
# save the style
self._obj_.dwStyle = style & afxres.CBRS_ALL
cursor = win32api.LoadCursor(0, win32con.IDC_ARROW)
wndClass = win32ui.RegisterWndClass(
win32con.CS_DBLCLKS, cursor, self.brushBkgd.GetSafeHandle(), 0
)
self._obj_.CreateWindow(wndClass, title, style, (0, 0, 0, 0), parent, id)
# Create the child dialog
self.dialog = childCreator(*(self,) + childCreatorArgs)
# use the dialog dimensions as default base dimensions
assert self.dialog.IsWindow(), (
"The childCreator function %s did not create a window!" % childCreator
)
rect = self.dialog.GetWindowRect()
self.sizeHorz = self.sizeVert = self.sizeFloat = (
rect[2] - rect[0],
rect[3] - rect[1],
)
self.sizeHorz = self.sizeHorz[0], self.sizeHorz[1] + self.cxEdge + self.cxBorder
self.sizeVert = self.sizeVert[0] + self.cxEdge + self.cxBorder, self.sizeVert[1]
self.HookMessages()
def CalcFixedLayout(self, bStretch, bHorz):
rectTop = self.dockSite.GetControlBar(
afxres.AFX_IDW_DOCKBAR_TOP
).GetWindowRect()
rectLeft = self.dockSite.GetControlBar(
afxres.AFX_IDW_DOCKBAR_LEFT
).GetWindowRect()
if bStretch:
nHorzDockBarWidth = 32767
nVertDockBarHeight = 32767
else:
nHorzDockBarWidth = rectTop[2] - rectTop[0] + 4
nVertDockBarHeight = rectLeft[3] - rectLeft[1] + 4
if self.IsFloating():
return self.sizeFloat
if bHorz:
return nHorzDockBarWidth, self.sizeHorz[1]
return self.sizeVert[0], nVertDockBarHeight
def CalcDynamicLayout(self, length, mode):
# Support for diagonal sizing.
if self.IsFloating():
self.GetParent().GetParent().ModifyStyle(win32ui.MFS_4THICKFRAME, 0)
if mode & (win32ui.LM_HORZDOCK | win32ui.LM_VERTDOCK):
flags = (
win32con.SWP_NOSIZE
| win32con.SWP_NOMOVE
| win32con.SWP_NOZORDER
| win32con.SWP_NOACTIVATE
| win32con.SWP_FRAMECHANGED
)
self.SetWindowPos(
0,
(
0,
0,
0,
0,
),
flags,
)
self.dockSite.RecalcLayout()
return self._obj_.CalcDynamicLayout(length, mode)
if mode & win32ui.LM_MRUWIDTH:
return self.sizeFloat
if mode & win32ui.LM_COMMIT:
self.sizeFloat = length, self.sizeFloat[1]
return self.sizeFloat
# More diagonal sizing.
if self.IsFloating():
dc = self.dockContext
pt = win32api.GetCursorPos()
windowRect = self.GetParent().GetParent().GetWindowRect()
hittest = dc.nHitTest
if hittest == win32con.HTTOPLEFT:
cx = max(windowRect[2] - pt[0], self.cMinWidth) - self.cxBorder
cy = max(windowRect[3] - self.cCaptionSize - pt[1], self.cMinHeight) - 1
self.sizeFloat = cx, cy
top = (
min(pt[1], windowRect[3] - self.cCaptionSize - self.cMinHeight)
- self.cyBorder
)
left = min(pt[0], windowRect[2] - self.cMinWidth) - 1
dc.rectFrameDragHorz = (
left,
top,
dc.rectFrameDragHorz[2],
dc.rectFrameDragHorz[3],
)
return self.sizeFloat
if hittest == win32con.HTTOPRIGHT:
cx = max(pt[0] - windowRect[0], self.cMinWidth)
cy = max(windowRect[3] - self.cCaptionSize - pt[1], self.cMinHeight) - 1
self.sizeFloat = cx, cy
top = (
min(pt[1], windowRect[3] - self.cCaptionSize - self.cMinHeight)
- self.cyBorder
)
dc.rectFrameDragHorz = (
dc.rectFrameDragHorz[0],
top,
dc.rectFrameDragHorz[2],
dc.rectFrameDragHorz[3],
)
return self.sizeFloat
if hittest == win32con.HTBOTTOMLEFT:
cx = max(windowRect[2] - pt[0], self.cMinWidth) - self.cxBorder
cy = max(pt[1] - windowRect[1] - self.cCaptionSize, self.cMinHeight)
self.sizeFloat = cx, cy
left = min(pt[0], windowRect[2] - self.cMinWidth) - 1
dc.rectFrameDragHorz = (
left,
dc.rectFrameDragHorz[1],
dc.rectFrameDragHorz[2],
dc.rectFrameDragHorz[3],
)
return self.sizeFloat
if hittest == win32con.HTBOTTOMRIGHT:
cx = max(pt[0] - windowRect[0], self.cMinWidth)
cy = max(pt[1] - windowRect[1] - self.cCaptionSize, self.cMinHeight)
self.sizeFloat = cx, cy
return self.sizeFloat
if mode & win32ui.LM_LENGTHY:
self.sizeFloat = self.sizeFloat[0], max(self.sizeMin[1], length)
return self.sizeFloat
else:
return max(self.sizeMin[0], length), self.sizeFloat[1]
def OnWindowPosChanged(self, msg):
if self.GetSafeHwnd() == 0 or self.dialog is None:
return 0
lparam = msg[3]
""" LPARAM used with WM_WINDOWPOSCHANGED:
typedef struct {
HWND hwnd;
HWND hwndInsertAfter;
int x;
int y;
int cx;
int cy;
UINT flags;} WINDOWPOS;
"""
format = "PPiiiii"
bytes = win32ui.GetBytes(lparam, struct.calcsize(format))
hwnd, hwndAfter, x, y, cx, cy, flags = struct.unpack(format, bytes)
if self.bInRecalcNC:
rc = self.GetClientRect()
self.dialog.MoveWindow(rc)
return 0
# Find on which side are we docked
nDockBarID = self.GetParent().GetDlgCtrlID()
# Return if dropped at same location
# no docking side change and no size change
if (
(nDockBarID == self.nDockBarID)
and (flags & win32con.SWP_NOSIZE)
and (
(self._obj_.dwStyle & afxres.CBRS_BORDER_ANY) != afxres.CBRS_BORDER_ANY
)
):
return
self.nDockBarID = nDockBarID
# Force recalc the non-client area
self.bInRecalcNC = 1
try:
swpflags = (
win32con.SWP_NOSIZE
| win32con.SWP_NOMOVE
| win32con.SWP_NOZORDER
| win32con.SWP_FRAMECHANGED
)
self.SetWindowPos(0, (0, 0, 0, 0), swpflags)
finally:
self.bInRecalcNC = 0
return 0
# This is a virtual and not a message hook.
def OnSetCursor(self, window, nHitTest, wMouseMsg):
if nHitTest != win32con.HTSIZE or self.bTracking:
return self._obj_.OnSetCursor(window, nHitTest, wMouseMsg)
if self.IsHorz():
win32api.SetCursor(win32api.LoadCursor(0, win32con.IDC_SIZENS))
else:
win32api.SetCursor(win32api.LoadCursor(0, win32con.IDC_SIZEWE))
return 1
# Mouse Handling
def OnLButtonUp(self, msg):
if not self.bTracking:
return 1 # pass it on.
self.StopTracking(1)
return 0 # Dont pass on
def OnLButtonDown(self, msg):
# UINT nFlags, CPoint point)
# only start dragging if clicked in "void" space
if self.dockBar is not None:
# start the drag
pt = msg[5]
pt = self.ClientToScreen(pt)
self.dockContext.StartDrag(pt)
return 0
return 1
def OnNcLButtonDown(self, msg):
if self.bTracking:
return 0
nHitTest = wparam = msg[2]
pt = msg[5]
if nHitTest == win32con.HTSYSMENU and not self.IsFloating():
self.GetDockingFrame().ShowControlBar(self, 0, 0)
elif nHitTest == win32con.HTMINBUTTON and not self.IsFloating():
self.dockContext.ToggleDocking()
elif (
nHitTest == win32con.HTCAPTION
and not self.IsFloating()
and self.dockBar is not None
):
self.dockContext.StartDrag(pt)
elif nHitTest == win32con.HTSIZE and not self.IsFloating():
self.StartTracking()
else:
return 1
return 0
def OnLButtonDblClk(self, msg):
# only toggle docking if clicked in "void" space
if self.dockBar is not None:
# toggle docking
self.dockContext.ToggleDocking()
return 0
return 1
def OnNcLButtonDblClk(self, msg):
nHitTest = wparam = msg[2]
# UINT nHitTest, CPoint point)
if self.dockBar is not None and nHitTest == win32con.HTCAPTION:
# toggle docking
self.dockContext.ToggleDocking()
return 0
return 1
def OnMouseMove(self, msg):
flags = wparam = msg[2]
lparam = msg[3]
if self.IsFloating() or not self.bTracking:
return 1
# Convert unsigned 16 bit to signed 32 bit.
x = win32api.LOWORD(lparam)
if x & 32768:
x = x | -65536
y = win32api.HIWORD(lparam)
if y & 32768:
y = y | -65536
pt = x, y
cpt = CenterPoint(self.rectTracker)
pt = self.ClientToWnd(pt)
if self.IsHorz():
if cpt[1] != pt[1]:
self.OnInvertTracker(self.rectTracker)
self.rectTracker = OffsetRect(self.rectTracker, (0, pt[1] - cpt[1]))
self.OnInvertTracker(self.rectTracker)
else:
if cpt[0] != pt[0]:
self.OnInvertTracker(self.rectTracker)
self.rectTracker = OffsetRect(self.rectTracker, (pt[0] - cpt[0], 0))
self.OnInvertTracker(self.rectTracker)
return 0 # Dont pass it on.
# def OnBarStyleChange(self, old, new):
def OnNcCalcSize(self, bCalcValid, size_info):
(rc0, rc1, rc2, pos) = size_info
self.rectBorder = self.GetWindowRect()
self.rectBorder = OffsetRect(
self.rectBorder, (-self.rectBorder[0], -self.rectBorder[1])
)
dwBorderStyle = self._obj_.dwStyle | afxres.CBRS_BORDER_ANY
if self.nDockBarID == afxres.AFX_IDW_DOCKBAR_TOP:
dwBorderStyle = dwBorderStyle & ~afxres.CBRS_BORDER_BOTTOM
rc0.left = rc0.left + self.cxGripper
rc0.bottom = rc0.bottom - self.cxEdge
rc0.top = rc0.top + self.cxBorder
rc0.right = rc0.right - self.cxBorder
self.rectBorder = (
self.rectBorder[0],
self.rectBorder[3] - self.cxEdge,
self.rectBorder[2],
self.rectBorder[3],
)
elif self.nDockBarID == afxres.AFX_IDW_DOCKBAR_BOTTOM:
dwBorderStyle = dwBorderStyle & ~afxres.CBRS_BORDER_TOP
rc0.left = rc0.left + self.cxGripper
rc0.top = rc0.top + self.cxEdge
rc0.bottom = rc0.bottom - self.cxBorder
rc0.right = rc0.right - self.cxBorder
self.rectBorder = (
self.rectBorder[0],
self.rectBorder[1],
self.rectBorder[2],
self.rectBorder[1] + self.cxEdge,
)
elif self.nDockBarID == afxres.AFX_IDW_DOCKBAR_LEFT:
dwBorderStyle = dwBorderStyle & ~afxres.CBRS_BORDER_RIGHT
rc0.right = rc0.right - self.cxEdge
rc0.left = rc0.left + self.cxBorder
rc0.bottom = rc0.bottom - self.cxBorder
rc0.top = rc0.top + self.cxGripper
self.rectBorder = (
self.rectBorder[2] - self.cxEdge,
self.rectBorder[1],
self.rectBorder[2],
self.rectBorder[3],
)
elif self.nDockBarID == afxres.AFX_IDW_DOCKBAR_RIGHT:
dwBorderStyle = dwBorderStyle & ~afxres.CBRS_BORDER_LEFT
rc0.left = rc0.left + self.cxEdge
rc0.right = rc0.right - self.cxBorder
rc0.bottom = rc0.bottom - self.cxBorder
rc0.top = rc0.top + self.cxGripper
self.rectBorder = (
self.rectBorder[0],
self.rectBorder[1],
self.rectBorder[0] + self.cxEdge,
self.rectBorder[3],
)
else:
self.rectBorder = 0, 0, 0, 0
self.SetBarStyle(dwBorderStyle)
return 0
def OnNcPaint(self, msg):
self.EraseNonClient()
dc = self.GetWindowDC()
ctl = win32api.GetSysColor(win32con.COLOR_BTNHIGHLIGHT)
cbr = win32api.GetSysColor(win32con.COLOR_BTNSHADOW)
dc.Draw3dRect(self.rectBorder, ctl, cbr)
self.DrawGripper(dc)
rect = self.GetClientRect()
self.InvalidateRect(rect, 1)
return 0
def OnNcHitTest(self, pt): # A virtual, not a hooked message.
if self.IsFloating():
return 1
ptOrig = pt
rect = self.GetWindowRect()
pt = pt[0] - rect[0], pt[1] - rect[1]
if PtInRect(self.rectClose, pt):
return win32con.HTSYSMENU
elif PtInRect(self.rectUndock, pt):
return win32con.HTMINBUTTON
elif PtInRect(self.rectGripper, pt):
return win32con.HTCAPTION
elif PtInRect(self.rectBorder, pt):
return win32con.HTSIZE
else:
return self._obj_.OnNcHitTest(ptOrig)
def StartTracking(self):
self.SetCapture()
# make sure no updates are pending
self.RedrawWindow(None, None, win32con.RDW_ALLCHILDREN | win32con.RDW_UPDATENOW)
self.dockSite.LockWindowUpdate()
self.ptOld = CenterPoint(self.rectBorder)
self.bTracking = 1
self.rectTracker = self.rectBorder
if not self.IsHorz():
l, t, r, b = self.rectTracker
b = b - 4
self.rectTracker = l, t, r, b
self.OnInvertTracker(self.rectTracker)
def OnCaptureChanged(self, msg):
hwnd = lparam = msg[3]
if self.bTracking and hwnd != self.GetSafeHwnd():
self.StopTracking(0) # cancel tracking
return 1
def StopTracking(self, bAccept):
self.OnInvertTracker(self.rectTracker)
self.dockSite.UnlockWindowUpdate()
self.bTracking = 0
self.ReleaseCapture()
if not bAccept:
return
rcc = self.dockSite.GetWindowRect()
if self.IsHorz():
newsize = self.sizeHorz[1]
maxsize = newsize + (rcc[3] - rcc[1])
minsize = self.sizeMin[1]
else:
newsize = self.sizeVert[0]
maxsize = newsize + (rcc[2] - rcc[0])
minsize = self.sizeMin[0]
pt = CenterPoint(self.rectTracker)
if self.nDockBarID == afxres.AFX_IDW_DOCKBAR_TOP:
newsize = newsize + (pt[1] - self.ptOld[1])
elif self.nDockBarID == afxres.AFX_IDW_DOCKBAR_BOTTOM:
newsize = newsize + (-pt[1] + self.ptOld[1])
elif self.nDockBarID == afxres.AFX_IDW_DOCKBAR_LEFT:
newsize = newsize + (pt[0] - self.ptOld[0])
elif self.nDockBarID == afxres.AFX_IDW_DOCKBAR_RIGHT:
newsize = newsize + (-pt[0] + self.ptOld[0])
newsize = max(minsize, min(maxsize, newsize))
if self.IsHorz():
self.sizeHorz = self.sizeHorz[0], newsize
else:
self.sizeVert = newsize, self.sizeVert[1]
self.dockSite.RecalcLayout()
return 0
def OnInvertTracker(self, rect):
assert rect[2] - rect[0] > 0 and rect[3] - rect[1] > 0, "rect is empty"
assert self.bTracking
rcc = self.GetWindowRect()
rcf = self.dockSite.GetWindowRect()
rect = OffsetRect(rect, (rcc[0] - rcf[0], rcc[1] - rcf[1]))
rect = DeflateRect(rect, (1, 1))
flags = win32con.DCX_WINDOW | win32con.DCX_CACHE | win32con.DCX_LOCKWINDOWUPDATE
dc = self.dockSite.GetDCEx(None, flags)
try:
brush = win32ui.GetHalftoneBrush()
oldBrush = dc.SelectObject(brush)
dc.PatBlt(
(rect[0], rect[1]),
(rect[2] - rect[0], rect[3] - rect[1]),
win32con.PATINVERT,
)
dc.SelectObject(oldBrush)
finally:
self.dockSite.ReleaseDC(dc)
def IsHorz(self):
return (
self.nDockBarID == afxres.AFX_IDW_DOCKBAR_TOP
or self.nDockBarID == afxres.AFX_IDW_DOCKBAR_BOTTOM
)
def ClientToWnd(self, pt):
x, y = pt
if self.nDockBarID == afxres.AFX_IDW_DOCKBAR_BOTTOM:
y = y + self.cxEdge
elif self.nDockBarID == afxres.AFX_IDW_DOCKBAR_RIGHT:
x = x + self.cxEdge
return x, y
def DrawGripper(self, dc):
# no gripper if floating
if self._obj_.dwStyle & afxres.CBRS_FLOATING:
return
# -==HACK==-
# in order to calculate the client area properly after docking,
# the client area must be recalculated twice (I have no idea why)
self.dockSite.RecalcLayout()
# -==END HACK==-
gripper = self.GetWindowRect()
gripper = self.ScreenToClient(gripper)
gripper = OffsetRect(gripper, (-gripper[0], -gripper[1]))
gl, gt, gr, gb = gripper
if self._obj_.dwStyle & afxres.CBRS_ORIENT_HORZ:
# gripper at left
self.rectGripper = gl, gt + 40, gl + 20, gb
# draw close box
self.rectClose = gl + 7, gt + 10, gl + 19, gt + 22
dc.DrawFrameControl(
self.rectClose, win32con.DFC_CAPTION, win32con.DFCS_CAPTIONCLOSE
)
# draw docking toggle box
self.rectUndock = OffsetRect(self.rectClose, (0, 13))
dc.DrawFrameControl(
self.rectUndock, win32con.DFC_CAPTION, win32con.DFCS_CAPTIONMAX
)
gt = gt + 38
gb = gb - 10
gl = gl + 10
gr = gl + 3
gripper = gl, gt, gr, gb
dc.Draw3dRect(gripper, clrBtnHilight, clrBtnShadow)
dc.Draw3dRect(OffsetRect(gripper, (4, 0)), clrBtnHilight, clrBtnShadow)
else:
# gripper at top
self.rectGripper = gl, gt, gr - 40, gt + 20
# draw close box
self.rectClose = gr - 21, gt + 7, gr - 10, gt + 18
dc.DrawFrameControl(
self.rectClose, win32con.DFC_CAPTION, win32con.DFCS_CAPTIONCLOSE
)
# draw docking toggle box
self.rectUndock = OffsetRect(self.rectClose, (-13, 0))
dc.DrawFrameControl(
self.rectUndock, win32con.DFC_CAPTION, win32con.DFCS_CAPTIONMAX
)
gr = gr - 38
gl = gl + 10
gt = gt + 10
gb = gt + 3
gripper = gl, gt, gr, gb
dc.Draw3dRect(gripper, clrBtnHilight, clrBtnShadow)
dc.Draw3dRect(OffsetRect(gripper, (0, 4)), clrBtnHilight, clrBtnShadow)
def HookMessages(self):
self.HookMessage(self.OnLButtonUp, win32con.WM_LBUTTONUP)
self.HookMessage(self.OnLButtonDown, win32con.WM_LBUTTONDOWN)
self.HookMessage(self.OnLButtonDblClk, win32con.WM_LBUTTONDBLCLK)
self.HookMessage(self.OnNcLButtonDown, win32con.WM_NCLBUTTONDOWN)
self.HookMessage(self.OnNcLButtonDblClk, win32con.WM_NCLBUTTONDBLCLK)
self.HookMessage(self.OnMouseMove, win32con.WM_MOUSEMOVE)
self.HookMessage(self.OnNcPaint, win32con.WM_NCPAINT)
self.HookMessage(self.OnCaptureChanged, win32con.WM_CAPTURECHANGED)
self.HookMessage(self.OnWindowPosChanged, win32con.WM_WINDOWPOSCHANGED)
# self.HookMessage(self.OnSize, win32con.WM_SIZE)
def EditCreator(parent):
d = win32ui.CreateEdit()
es = (
win32con.WS_CHILD
| win32con.WS_VISIBLE
| win32con.WS_BORDER
| win32con.ES_MULTILINE
| win32con.ES_WANTRETURN
)
d.CreateWindow(es, (0, 0, 150, 150), parent, 1000)
return d
def test():
import pywin.mfc.dialog
global bar
bar = DockingBar()
creator = EditCreator
bar.CreateWindow(win32ui.GetMainFrame(), creator, "Coolbar Demo", 0xFFFFF)
# win32ui.GetMainFrame().ShowControlBar(bar, 1, 0)
bar.SetBarStyle(
bar.GetBarStyle()
| afxres.CBRS_TOOLTIPS
| afxres.CBRS_FLYBY
| afxres.CBRS_SIZE_DYNAMIC
)
bar.EnableDocking(afxres.CBRS_ALIGN_ANY)
win32ui.GetMainFrame().DockControlBar(bar, afxres.AFX_IDW_DOCKBAR_BOTTOM)
if __name__ == "__main__":
test()