root/trunk/RBSkinning/RBSkinning/wxTools/wxLockingFrame.py

Revision 739, 13.2 kB (checked in by sholloway, 5 years ago)

*** empty log message ***

Line 
1 #!/usr/bin/env python
2 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3 ##~ License
4 ##~
5 ##- The RuneBlade Foundation library is intended to ease some
6 ##- aspects of writing intricate Jabber, XML, and User Interface (wxPython, etc.)
7 ##- applications, while providing the flexibility to modularly change the
8 ##- architecture. Enjoy.
9 ##~
10 ##~ Copyright (C) 2002  TechGame Networks, LLC.
11 ##~
12 ##~ This library is free software; you can redistribute it and/or
13 ##~ modify it under the terms of the BSD style License as found in the
14 ##~ LICENSE file included with this distribution.
15 ##~
16 ##~ TechGame Networks, LLC can be reached at:
17 ##~ 3578 E. Hartsel Drive #211
18 ##~ Colorado Springs, Colorado, USA, 80920
19 ##~
20 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
21
22 """Frame locking (docking) code.
23
24 Frame locking (docking) is most useful when there are a LOT of windows about
25 and our human instincts plead for us to align them."""
26
27 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
28 #~ Imports
29 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
30
31 import weakref
32 from wxPython import wx
33 from RBFoundation.BindCallable import BindCallable
34
35 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
36 #~ Definitions
37 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
38
39 class wxLockSide:
40     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
41     #~ Constants / Variables / Etc.
42     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
43
44     _DeltaWing = 10
45     _DeltaAttract = 10
46     _DeltaResist = 10
47
48     _LockingSides = [
49         ({}, 2), # Left - outer
50         ({}, 3), # Top - outer
51         ({}, 0), # Right - outer
52         ({}, 1), # Bottom - outer
53
54         ({}, 0), # Left - inner
55         ({}, 1), # Top - inner
56         ({}, 2), # Right - inner
57         ({}, 3), # Bottom - inner
58         ]
59
60     SizeConstants = {
61         'Left': 0, 'Top': 1, 'Right': 2, 'Bottom': 3,
62         'LeftInner': 4, 'TopInner': 5, 'RightInner': 6, 'BottomInner': 7,
63     }
64     locals().update(SizeConstants)
65
66     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
67     #~ Class Methods
68     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
69
70     def RemoveSidePositions(self, key):
71         for each in self._LockingSides:
72             del each[0][key]
73
74     def SaveSidePositions(self, key, rect):
75         return self.SaveSidePositionsEx(key, rect, outsides=True, insides=True, deltas=(self._DeltaAttract, self._DeltaResist, self._DeltaWing))
76
77     def SaveSidePositionsEx(Class, key, rect, outsides=True, insides=True, deltas=None):
78         l,t,w,h = rect
79         dAttract, dResist, dWing = deltas or (Class._DeltaAttract, Class._DeltaResist, Class._DeltaWing)
80         if outsides:
81             Class._LockingSides[Class.Left][0][key] = [(l-dAttract, t, l+dResist, t+h, l)]
82             Class._LockingSides[Class.Right][0][key] = [(l+w-dResist, t, l+w+dAttract, t+h, l+w)]
83             Class._LockingSides[Class.Bottom][0][key] = [(l, t+h-dResist, l+w, t+h+dAttract, t+h)]
84             Class._LockingSides[Class.Top][0][key] = [(l, t-dAttract, l+w, t+dResist, t)]
85
86         if insides:
87             Class._LockingSides[Class.LeftInner][0][key] = [(l-dResist, t-dWing, l+dAttract, t, l), (l-dResist, t+h, l+dAttract, t+h+dWing, l)]
88             Class._LockingSides[Class.RightInner][0][key] = [(l+w-dAttract, t-dWing, l+w+dResist, t, l+w), (l+w-dAttract, t+h, l+w+dResist, t+h+dWing, l+w)]
89             Class._LockingSides[Class.BottomInner][0][key] = [(l-dWing, t+h-dAttract, l, t+h+dResist, t+h), (l+w, t+h-dAttract, l+w+dWing, t+h+dResist, t+h)]
90             Class._LockingSides[Class.TopInner][0][key] = [(l-dWing, t-dResist, l, t+dAttract, t), (l+w, t-dResist, l+w+dWing, t+dAttract, t)]
91     SaveSidePositionsEx = classmethod(SaveSidePositionsEx)
92
93     def SetupInternalLockingSides(Class, key, rect, deltas=None):
94         l,t,w,h = rect
95         dAttract, dResist, dWing = deltas or (Class._DeltaAttract, Class._DeltaResist, Class._DeltaWing)
96         Class._LockingSides[Class.LeftInner][0][key] = [(l-dResist, t, l+dAttract, t+h, l)]
97         Class._LockingSides[Class.RightInner][0][key] = [(l+w-dAttract, t, l+w+dResist, t+h, l+w)]
98         Class._LockingSides[Class.BottomInner][0][key] = [(l, t+h-dAttract, l+w, t+h+dResist, t+h)]
99         Class._LockingSides[Class.TopInner][0][key] = [(l, t-dResist, l+w, t+dAttract, t)]
100     SetupInternalLockingSides = classmethod(SetupInternalLockingSides)
101
102 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
103
104 def LockWindowsTogether(HostWin, AttachWin, LockSideIndicies=(wxLockSide.Bottom,wxLockSide.LeftInner), LockSizeIndicies=tuple()):
105     # Lookup the constant aliases
106     LockSideIndicies = [wxLockSide.SizeConstants.get(i, i) for i in LockSideIndicies]
107     LockSizeIndicies = [wxLockSide.SizeConstants.get(i, i) for i in LockSizeIndicies]
108
109     if HostWin.IsTopLevel() ^ AttachWin.IsTopLevel():
110         # If both HostWin xor AttachWin are top level windows (Frame or Dialog), then figure out screen coordinates
111         hostrect = HostWin.ClientToScreen((0,0)).asTuple() + HostWin.GetSize().asTuple()
112     else:
113         # Otherwise, assume that they are in the same frame of reference.  (Until we get a bug for shortsightedness ;)
114         hostrect = HostWin.GetRect().asTuple()
115     Positions = _GetIntersectionPositions(*(hostrect + AttachWin.GetSizeTuple()))
116     return _DoLockWindowToPositon(AttachWin, Positions, LockSideIndicies, LockSizeIndicies)
117
118 def LockToDesktop(AttachWin, LockSideIndicies=(wxLockSide.TopInner,wxLockSide.LeftInner), LockSizeIndicies=tuple()):
119     LockSideIndicies = [wxLockSide.SizeConstants.get(i, i) for i in LockSideIndicies]
120     LockSizeIndicies = [wxLockSide.SizeConstants.get(i, i) for i in LockSizeIndicies]
121     Positions = _GetIntersectionPositions(*(wx.wxGetClientDisplayRect().asTuple() + AttachWin.GetSizeTuple()))
122     return _DoLockWindowToPositon(AttachWin, Positions, LockSideIndicies, LockSizeIndicies)
123
124 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
125
126 class wxLockingFrameBase(wx.wxFrame, wxLockSide):
127     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
128     #~ Constants / Variables / Etc.
129     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
130
131     _weakself = None
132     __bLocking = 0
133
134     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
135     #~ Special
136     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
137
138     def __init__(self, *args, **kw):
139         wx.wxFrame.__init__(self, *args, **kw)
140
141         wx.EVT_SIZE(self, BindCallable(self._OnSizeFrame))
142         wx.EVT_MOVE(self, BindCallable(self._OnMoveFrame))
143         self.PushEventHandler(wx.wxEvtHandler())
144
145     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
146     #~ Public Methods
147     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
148
149     def Show(self, bShow=1):
150         result = wx.wxFrame.Show(self, bShow)
151         self._SaveSidePositions()
152         return result
153
154     def Move(self, pos):
155         self.__bLocking = 1
156         if isinstance(pos, (tuple, list)): newpos = pos
157         else: newpos = pos.x, pos.y
158
159         newpos, newsize = self._OnLockingMove(newpos, self.GetSizeTuple()) or newpos
160         result = wx.wxFrame.Move(self, newpos)
161
162         self.__bLocking = 0
163         return result
164
165     def SetDimensions(self, x, y, w, h, *args, **kw):
166         self.__bLocking= 1
167         newpos, newsize = self._OnLockingMove((x,y), (w,h))
168         newpos, newsize = self._OnLockingSize(newpos, newsize)
169         result = self._DoSetDimensions(*(newpos + newsize + args), **kw)
170         self.__bLocking= 0
171         return result
172
173     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
174    
175     def LockFrameToSelf(self, frame, LockSideIndicies=(wxLockSide.Bottom,wxLockSide.LeftInner), LockSizeIndicies=tuple()):
176         return LockWindowsTogether(self, frame, LockSideIndicies, LockSizeIndicies)
177
178     def LockToFrame(self, frame, LockSideIndicies=(wxLockSide.Bottom,wxLockSide.LeftInner), LockSizeIndicies=tuple()):
179         return LockWindowsTogether(frame, self, LockSideIndicies, LockSizeIndicies)
180
181     def LockToDesktop(self, LockSideIndicies=(wxLockSide.TopInner,wxLockSide.LeftInner), LockSizeIndicies=tuple()):
182         return LockToDesktop(self, LockSideIndicies, LockSizeIndicies)
183
184     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
185     #~ Event Handlers
186     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
187
188     def _OnSizeFrame(self, evt):
189         if not self.__bLocking:
190             self.__bLocking = 1
191             pos, size = self.GetPositionTuple(), self.GetSizeTuple()
192             newpos, newsize = self._OnLockingSize(pos, size)
193             self._DoSetDimensions(*(newpos + newsize))
194             self.__bLocking = 0
195         self._SaveSidePositions()
196         evt.Skip()
197
198     def _OnMoveFrame(self, evt):
199         if not self.__bLocking:
200             self.__bLocking= 1
201             pos, size = self.GetPositionTuple(), self.GetSizeTuple()
202             newpos, newsize = self._OnLockingMove(pos, size)
203             self._DoSetDimensions(*(newpos + newsize))
204             self.__bLocking= 0
205         self._SaveSidePositions()
206         evt.Skip()
207
208     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
209     #~ Protected Methods
210     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
211
212     def _DoSetDimensions(self, *args, **kw):
213         wx.wxFrame.SetDimensions(self, *args, **kw)
214
215     def _RemoveSidePositions(self):
216         if self._weakself:
217             self.RemoveSidePositions(self._weakself)
218             self._weakself = None
219
220     def _SaveSidePositions(self):
221         if not self.IsShown() or self.IsIconized():
222             return self._RemoveSidePositions()
223         self._weakself = weakref.ref(self)
224         l,t,w,h = self.GetRect().asTuple()
225         self.SaveSidePositions(self._weakself, (l,t,w,h))
226
227     def _OnLockingMove(self, pos, size):
228         (l,t),(w,h) = pos, size
229         IntersectionSides = _GetIntersectionSides(l,t,w,h)
230         for (LockingSides, WidthIdx), FrameSide in zip(self._LockingSides, IntersectionSides):
231             for weakframe, SideList in LockingSides.iteritems():
232                 if weakframe is self._weakself: continue
233                 for Side in SideList:
234                     if _Intersects(FrameSide, Side):
235                         if WidthIdx == 0: pos = Side[-1], pos[1],
236                         elif WidthIdx == 1: pos = pos[0], Side[-1],
237                         elif WidthIdx == 2: pos = Side[-1]-w, pos[1],
238                         elif WidthIdx == 3: pos = pos[0], Side[-1]-h,
239                         break
240         return pos, size
241
242     def _OnLockingSize(self, pos, size):
243         (l,t),(w,h) = pos, size
244         IntersectionSides = _GetIntersectionSides(l,t,w,h)
245         for (LockingSides, WidthIdx), FrameSide in zip(self._LockingSides, IntersectionSides):
246             for weakframe, SideList in LockingSides.iteritems():
247                 if weakframe is self._weakself: continue
248                 for Side in SideList:
249                     if _Intersects(FrameSide, Side):
250                         if WidthIdx == 0: pos = Side[-1], pos[1],
251                         elif WidthIdx == 1: pos = pos[0], Side[-1],
252                         elif WidthIdx == 2: size = Side[-1]-pos[0], size[1],
253                         elif WidthIdx == 3: size = size[0], Side[-1]-pos[1],
254                         break
255         return pos, size
256
257 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
258 #~ Utilities
259 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
260
261 def _DoLockWindowToPositon(window, Positions, LockSideIndicies, LockSizeIndicies, pos=None, size=None):
262     pos, size = pos or window.GetPositionTuple(), size or window.GetSizeTuple()
263     for LockIndex in LockSideIndicies:
264         if LockIndex & 1: pos = pos[0], Positions[LockIndex]
265         else: pos = Positions[LockIndex], pos[1]
266
267     newpos, pos = pos, (pos[0] + size[0], pos[1] + size[1])
268     for LockIndex in LockSizeIndicies:
269         if LockIndex & 1: pos = pos[0], Positions[LockIndex]
270         else: pos = Positions[LockIndex], pos[1]
271
272     newsize = abs(pos[0] - newpos[0]), abs(pos[1] - newpos[1])
273     newpos = tuple(map(min, zip(pos, newpos)))
274
275     return window.SetDimensions(*(newpos + newsize))
276
277 def _GetIntersectionPositions(l, t, w, h, wP=0, hP=0):
278     return [
279         l-wP, #LeftSide
280         t-hP, #TopSide
281         l+w, #RightSide
282         t+h, #BottomSide
283
284         l, #LeftInnerSide
285         t, #TopInnerSide
286         l+w-wP, #RightInnerSide
287         t+h-hP, #BottomInnerSide
288         ]
289
290 def _GetIntersectionSides(l, t, w, h):
291     return [
292         (l+w, t, l+w, t+h), # Right - Outter
293         (l, t+h, l+w, t+h), # Bottom - Outter
294         (l, t, l, t+h), # Left - Outter
295         (l, t, l+w, t), # Top - Outter
296
297         (l, t, l, t+h), # Left - Inner
298         (l, t, l+w, t), # Top - Inner
299         (l+w, t, l+w, t+h), # Right - Inner
300         (l, t+h, l+w, t+h), # Bottom - Inner
301         ]
302
303 def _Intersects(A, B):
304     if A[0] > B[2]: return 0
305     if B[0] > A[2]: return 0
306     if A[1] > B[3]: return 0
307     if B[1] > A[3]: return 0
308     return 1
309
310
311 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
312
313 wxLockingFrameBase.SetupInternalLockingSides(None, wx.wxGetClientDisplayRect().asTuple())
314
315 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
316
317 class wxAttractiveLockingFrame(wxLockingFrameBase):
318     _DeltaAttract = 20
319     _DeltaResist = 0
320
321 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
322
323 class wxResistiveLockingFrame(wxLockingFrameBase):
324     _DeltaAttract = 0
325     _DeltaResist = 20
326
327 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
328
329 class wxLockingFrame(wxLockingFrameBase):
330     _DeltaAttract = 10
331     _DeltaResist = 10
332
Note: See TracBrowser for help on using the browser.