]>
Commit | Line | Data |
---|---|---|
1 | #---------------------------------------------------------------------- | |
2 | # Name: wx.lib.splitter | |
3 | # Purpose: A class similar to wx.SplitterWindow but that allows more | |
4 | # than a single split | |
5 | # | |
6 | # Author: Robin Dunn | |
7 | # | |
8 | # Created: 9-June-2005 | |
9 | # RCS-ID: $Id$ | |
10 | # Copyright: (c) 2005 by Total Control Software | |
11 | # Licence: wxWindows license | |
12 | #---------------------------------------------------------------------- | |
13 | """ | |
14 | This module provides the `MultiSplitterWindow` class, which is very | |
15 | similar to the standard `wx.SplitterWindow` except it can be split | |
16 | more than once. | |
17 | """ | |
18 | ||
19 | import wx | |
20 | ||
21 | _RENDER_VER = (2,6,1,1) | |
22 | ||
23 | #---------------------------------------------------------------------- | |
24 | ||
25 | class MultiSplitterWindow(wx.PyPanel): | |
26 | """ | |
27 | This class is very similar to `wx.SplitterWindow` except that it | |
28 | allows for more than two windows and more than one sash. Many of | |
29 | the same styles, constants, and methods behave the same as in | |
30 | wx.SplitterWindow. The key differences are seen in the methods | |
31 | that deal with the child windows managed by the splitter, and also | |
32 | those that deal with the sash positions. In most cases you will | |
33 | need to pass an index value to tell the class which window or sash | |
34 | you are refering to. | |
35 | ||
36 | The concept of the sash position is also different than in | |
37 | wx.SplitterWindow. Since the wx.Splitterwindow has only one sash | |
38 | you can think of it's position as either relative to the whole | |
39 | splitter window, or as relative to the first window pane managed | |
40 | by the splitter. Once there is more than one sash then the | |
41 | distinciton between the two concepts needs to be clairified. I've | |
42 | chosen to use the second definition, and sash positions are the | |
43 | distance (either horizontally or vertically) from the origin of | |
44 | the window just before the sash in the splitter stack. | |
45 | ||
46 | NOTE: These things are not yet supported: | |
47 | ||
48 | * Using negative sash positions to indicate a position offset | |
49 | from the end. | |
50 | ||
51 | * User controlled unsplitting (with double clicks on the sash | |
52 | or dragging a sash until the pane size is zero.) | |
53 | ||
54 | * Sash gravity | |
55 | ||
56 | """ | |
57 | def __init__(self, parent, id=-1, | |
58 | pos = wx.DefaultPosition, size = wx.DefaultSize, | |
59 | style = 0, name="multiSplitter"): | |
60 | ||
61 | # always turn on tab traversal | |
62 | style |= wx.TAB_TRAVERSAL | |
63 | ||
64 | # and turn off any border styles | |
65 | style &= ~wx.BORDER_MASK | |
66 | style |= wx.BORDER_NONE | |
67 | ||
68 | # initialize the base class | |
69 | wx.PyPanel.__init__(self, parent, id, pos, size, style, name) | |
70 | self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) | |
71 | ||
72 | # initialize data members | |
73 | self._windows = [] | |
74 | self._sashes = [] | |
75 | self._pending = {} | |
76 | self._permitUnsplitAlways = self.HasFlag(wx.SP_PERMIT_UNSPLIT) | |
77 | self._orient = wx.HORIZONTAL | |
78 | self._dragMode = wx.SPLIT_DRAG_NONE | |
79 | self._activeSash = -1 | |
80 | self._oldX = 0 | |
81 | self._oldY = 0 | |
82 | self._checkRequestedSashPosition = False | |
83 | self._minimumPaneSize = 0 | |
84 | self._sashCursorWE = wx.StockCursor(wx.CURSOR_SIZEWE) | |
85 | self._sashCursorNS = wx.StockCursor(wx.CURSOR_SIZENS) | |
86 | self._sashTrackerPen = wx.Pen(wx.BLACK, 2, wx.SOLID) | |
87 | self._needUpdating = False | |
88 | self._isHot = False | |
89 | ||
90 | # Bind event handlers | |
91 | self.Bind(wx.EVT_PAINT, self._OnPaint) | |
92 | self.Bind(wx.EVT_IDLE, self._OnIdle) | |
93 | self.Bind(wx.EVT_SIZE, self._OnSize) | |
94 | self.Bind(wx.EVT_MOUSE_EVENTS, self._OnMouse) | |
95 | ||
96 | ||
97 | ||
98 | def SetOrientation(self, orient): | |
99 | """ | |
100 | Set whether the windows managed by the splitter will be | |
101 | stacked vertically or horizontally. The default is | |
102 | horizontal. | |
103 | """ | |
104 | assert orient in [ wx.VERTICAL, wx.HORIZONTAL ] | |
105 | self._orient = orient | |
106 | ||
107 | def GetOrientation(self): | |
108 | """ | |
109 | Returns the current orientation of the splitter, either | |
110 | wx.VERTICAL or wx.HORIZONTAL. | |
111 | """ | |
112 | return self._orient | |
113 | ||
114 | ||
115 | def SetMinimumPaneSize(self, minSize): | |
116 | """ | |
117 | Set the smallest size that any pane will be allowed to be | |
118 | resized to. | |
119 | """ | |
120 | self._minimumPaneSize = minSize | |
121 | ||
122 | def GetMinimumPaneSize(self): | |
123 | """ | |
124 | Returns the smallest allowed size for a window pane. | |
125 | """ | |
126 | return self._minimumPaneSize | |
127 | ||
128 | ||
129 | ||
130 | def AppendWindow(self, window, sashPos=-1): | |
131 | """ | |
132 | Add a new window to the splitter at the right side or bottom | |
133 | of the window stack. If sashPos is given then it is used to | |
134 | size the new window. | |
135 | """ | |
136 | self.InsertWindow(len(self._windows), window, sashPos) | |
137 | ||
138 | ||
139 | def InsertWindow(self, idx, window, sashPos=-1): | |
140 | """ | |
141 | Insert a new window into the splitter at the position given in | |
142 | ``idx``. | |
143 | """ | |
144 | assert window not in self._windows, "A window can only be in the splitter once!" | |
145 | self._windows.insert(idx, window) | |
146 | self._sashes.insert(idx, -1) | |
147 | if not window.IsShown(): | |
148 | window.Show() | |
149 | if sashPos != -1: | |
150 | self._pending[window] = sashPos | |
151 | self._checkRequestedSashPosition = False | |
152 | self._SizeWindows() | |
153 | ||
154 | ||
155 | def DetachWindow(self, window): | |
156 | """ | |
157 | Removes the window from the stack of windows managed by the | |
158 | splitter. The window will still exist so you should `Hide` or | |
159 | `Destroy` it as needed. | |
160 | """ | |
161 | assert window in self._windows, "Unknown window!" | |
162 | idx = self._windows.index(window) | |
163 | del self._windows[idx] | |
164 | del self._sashes[idx] | |
165 | self._SizeWindows() | |
166 | ||
167 | ||
168 | def ReplaceWindow(self, oldWindow, newWindow): | |
169 | """ | |
170 | Replaces oldWindow (which is currently being managed by the | |
171 | splitter) with newWindow. The oldWindow window will still | |
172 | exist so you should `Hide` or `Destroy` it as needed. | |
173 | """ | |
174 | assert oldWindow in self._windows, "Unknown window!" | |
175 | idx = self._windows.index(oldWindow) | |
176 | self._windows[idx] = newWindow | |
177 | if not newWindow.IsShown(): | |
178 | newWindow.Show() | |
179 | self._SizeWindows() | |
180 | ||
181 | ||
182 | def ExchangeWindows(self, window1, window2): | |
183 | """ | |
184 | Trade the positions in the splitter of the two windows. | |
185 | """ | |
186 | assert window1 in self._windows, "Unknown window!" | |
187 | assert window2 in self._windows, "Unknown window!" | |
188 | idx1 = self._windows.index(window1) | |
189 | idx2 = self._windows.index(window2) | |
190 | self._windows[idx1] = window2 | |
191 | self._windows[idx2] = window1 | |
192 | self._SizeWindows() | |
193 | ||
194 | ||
195 | def GetWindow(self, idx): | |
196 | """ | |
197 | Returns the idx'th window being managed by the splitter. | |
198 | """ | |
199 | assert idx < len(self._windows) | |
200 | return self._windows[idx] | |
201 | ||
202 | ||
203 | def GetSashPosition(self, idx): | |
204 | """ | |
205 | Returns the position of the idx'th sash, measured from the | |
206 | left/top of the window preceding the sash. | |
207 | """ | |
208 | assert idx < len(self._sashes) | |
209 | return self._sashes[idx] | |
210 | ||
211 | ||
212 | def SetSashPosition(self, idx, pos): | |
213 | """ | |
214 | Set the psition of the idx'th sash, measured from the left/top | |
215 | of the window preceding the sash. | |
216 | """ | |
217 | assert idx < len(self._sashes) | |
218 | self._sashes[idx] = pos | |
219 | self._SizeWindows() | |
220 | ||
221 | ||
222 | def SizeWindows(self): | |
223 | """ | |
224 | Reposition and size the windows managed by the splitter. | |
225 | Useful when windows have been added/removed or when styles | |
226 | have been changed. | |
227 | """ | |
228 | self._SizeWindows() | |
229 | ||
230 | ||
231 | def DoGetBestSize(self): | |
232 | """ | |
233 | Overridden base class virtual. Determines the best size of | |
234 | the control based on the best sizes of the child windows. | |
235 | """ | |
236 | best = wx.Size(0,0) | |
237 | if not self._windows: | |
238 | best = wx.Size(10,10) | |
239 | ||
240 | sashsize = self._GetSashSize() | |
241 | if self._orient == wx.HORIZONTAL: | |
242 | for win in self._windows: | |
243 | winbest = win.GetEffectiveMinSize() | |
244 | best.width += max(self._minimumPaneSize, winbest.width) | |
245 | best.height = max(best.height, winbest.height) | |
246 | best.width += sashsize * (len(self._windows)-1) | |
247 | ||
248 | else: | |
249 | for win in self._windows: | |
250 | winbest = win.GetEffectiveMinSize() | |
251 | best.height += max(self._minimumPaneSize, winbest.height) | |
252 | best.width = max(best.width, winbest.width) | |
253 | best.height += sashsize * (len(self._windows)-1) | |
254 | ||
255 | border = 2 * self._GetBorderSize() | |
256 | best.width += border | |
257 | best.height += border | |
258 | return best | |
259 | ||
260 | # ------------------------------------- | |
261 | # Event handlers | |
262 | ||
263 | def _OnPaint(self, evt): | |
264 | dc = wx.PaintDC(self) | |
265 | self._DrawSash(dc) | |
266 | ||
267 | ||
268 | def _OnSize(self, evt): | |
269 | parent = wx.GetTopLevelParent(self) | |
270 | if parent.IsIconized(): | |
271 | evt.Skip() | |
272 | return | |
273 | self._SizeWindows() | |
274 | ||
275 | ||
276 | def _OnIdle(self, evt): | |
277 | evt.Skip() | |
278 | # if this is the first idle time after a sash position has | |
279 | # potentially been set, allow _SizeWindows to check for a | |
280 | # requested size. | |
281 | if not self._checkRequestedSashPosition: | |
282 | self._checkRequestedSashPosition = True | |
283 | self._SizeWindows() | |
284 | ||
285 | if self._needUpdating: | |
286 | self._SizeWindows() | |
287 | ||
288 | ||
289 | ||
290 | def _OnMouse(self, evt): | |
291 | if self.HasFlag(wx.SP_NOSASH): | |
292 | return | |
293 | ||
294 | x, y = evt.GetPosition() | |
295 | isLive = self.HasFlag(wx.SP_LIVE_UPDATE) | |
296 | adjustNeighbor = evt.ShiftDown() | |
297 | ||
298 | # LeftDown: set things up for dragging the sash | |
299 | if evt.LeftDown() and self._SashHitTest(x, y) != -1: | |
300 | self._activeSash = self._SashHitTest(x, y) | |
301 | self._dragMode = wx.SPLIT_DRAG_DRAGGING | |
302 | ||
303 | self.CaptureMouse() | |
304 | self._SetResizeCursor() | |
305 | ||
306 | if not isLive: | |
307 | self._pendingPos = (self._sashes[self._activeSash], | |
308 | self._sashes[self._activeSash+1]) | |
309 | self._DrawSashTracker(x, y) | |
310 | ||
311 | self._oldX = x | |
312 | self._oldY = y | |
313 | return | |
314 | ||
315 | # LeftUp: Finsish the drag | |
316 | elif evt.LeftUp() and self._dragMode == wx.SPLIT_DRAG_DRAGGING: | |
317 | self._dragMode = wx.SPLIT_DRAG_NONE | |
318 | self.ReleaseMouse() | |
319 | self.SetCursor(wx.STANDARD_CURSOR) | |
320 | ||
321 | if not isLive: | |
322 | # erase the old tracker | |
323 | self._DrawSashTracker(self._oldX, self._oldY) | |
324 | ||
325 | diff = self._GetMotionDiff(x, y) | |
326 | ||
327 | # determine if we can change the position | |
328 | if isLive: | |
329 | oldPos1, oldPos2 = (self._sashes[self._activeSash], | |
330 | self._sashes[self._activeSash+1]) | |
331 | else: | |
332 | oldPos1, oldPos2 = self._pendingPos | |
333 | newPos1, newPos2 = self._OnSashPositionChanging(self._activeSash, | |
334 | oldPos1 + diff, | |
335 | oldPos2 - diff, | |
336 | adjustNeighbor) | |
337 | if newPos1 == -1: | |
338 | # the change was not allowed | |
339 | return | |
340 | ||
341 | # TODO: check for unsplit? | |
342 | ||
343 | self._SetSashPositionAndNotify(self._activeSash, newPos1, newPos2, adjustNeighbor) | |
344 | self._activeSash = -1 | |
345 | self._pendingPos = (-1, -1) | |
346 | self._SizeWindows() | |
347 | ||
348 | # Entering or Leaving a sash: Change the cursor | |
349 | elif (evt.Moving() or evt.Leaving() or evt.Entering()) and self._dragMode == wx.SPLIT_DRAG_NONE: | |
350 | if evt.Leaving() or self._SashHitTest(x, y) == -1: | |
351 | self._OnLeaveSash() | |
352 | else: | |
353 | self._OnEnterSash() | |
354 | ||
355 | # Dragging the sash | |
356 | elif evt.Dragging() and self._dragMode == wx.SPLIT_DRAG_DRAGGING: | |
357 | diff = self._GetMotionDiff(x, y) | |
358 | if not diff: | |
359 | return # mouse didn't move far enough | |
360 | ||
361 | # determine if we can change the position | |
362 | if isLive: | |
363 | oldPos1, oldPos2 = (self._sashes[self._activeSash], | |
364 | self._sashes[self._activeSash+1]) | |
365 | else: | |
366 | oldPos1, oldPos2 = self._pendingPos | |
367 | newPos1, newPos2 = self._OnSashPositionChanging(self._activeSash, | |
368 | oldPos1 + diff, | |
369 | oldPos2 - diff, | |
370 | adjustNeighbor) | |
371 | if newPos1 == -1: | |
372 | # the change was not allowed | |
373 | return | |
374 | ||
375 | if newPos1 == self._sashes[self._activeSash]: | |
376 | return # nothing was changed | |
377 | ||
378 | if not isLive: | |
379 | # erase the old tracker | |
380 | self._DrawSashTracker(self._oldX, self._oldY) | |
381 | ||
382 | if self._orient == wx.HORIZONTAL: | |
383 | x = self._SashToCoord(self._activeSash, newPos1) | |
384 | else: | |
385 | y = self._SashToCoord(self._activeSash, newPos1) | |
386 | ||
387 | # Remember old positions | |
388 | self._oldX = x | |
389 | self._oldY = y | |
390 | ||
391 | if not isLive: | |
392 | # draw a new tracker | |
393 | self._pendingPos = (newPos1, newPos2) | |
394 | self._DrawSashTracker(self._oldX, self._oldY) | |
395 | else: | |
396 | self._DoSetSashPosition(self._activeSash, newPos1, newPos2, adjustNeighbor) | |
397 | self._needUpdating = True | |
398 | ||
399 | ||
400 | # ------------------------------------- | |
401 | # Internal helpers | |
402 | ||
403 | def _RedrawIfHotSensitive(self, isHot): | |
404 | if not wx.VERSION >= _RENDER_VER: | |
405 | return | |
406 | if wx.RendererNative.Get().GetSplitterParams(self).isHotSensitive: | |
407 | self._isHot = isHot | |
408 | dc = wx.ClientDC(self) | |
409 | self._DrawSash(dc) | |
410 | ||
411 | ||
412 | def _OnEnterSash(self): | |
413 | self._SetResizeCursor() | |
414 | self._RedrawIfHotSensitive(True) | |
415 | ||
416 | ||
417 | def _OnLeaveSash(self): | |
418 | self.SetCursor(wx.STANDARD_CURSOR) | |
419 | self._RedrawIfHotSensitive(False) | |
420 | ||
421 | ||
422 | def _SetResizeCursor(self): | |
423 | if self._orient == wx.HORIZONTAL: | |
424 | self.SetCursor(self._sashCursorWE) | |
425 | else: | |
426 | self.SetCursor(self._sashCursorNS) | |
427 | ||
428 | ||
429 | def _OnSashPositionChanging(self, idx, newPos1, newPos2, adjustNeighbor): | |
430 | # TODO: check for possibility of unsplit (pane size becomes zero) | |
431 | ||
432 | # make sure that minsizes are honored | |
433 | newPos1, newPos2 = self._AdjustSashPosition(idx, newPos1, newPos2, adjustNeighbor) | |
434 | ||
435 | # sanity check | |
436 | if newPos1 <= 0: | |
437 | newPos2 += newPos1 | |
438 | newPos1 = 0 | |
439 | ||
440 | # send the events | |
441 | evt = MultiSplitterEvent( | |
442 | wx.wxEVT_COMMAND_SPLITTER_SASH_POS_CHANGING, self) | |
443 | evt.SetSashIdx(idx) | |
444 | evt.SetSashPosition(newPos1) | |
445 | if not self._DoSendEvent(evt): | |
446 | # the event handler vetoed the change | |
447 | newPos1 = -1 | |
448 | else: | |
449 | # or it might have changed the value | |
450 | newPos1 = evt.GetSashPosition() | |
451 | ||
452 | if adjustNeighbor and newPos1 != -1: | |
453 | evt.SetSashIdx(idx+1) | |
454 | evt.SetSashPosition(newPos2) | |
455 | if not self._DoSendEvent(evt): | |
456 | # the event handler vetoed the change | |
457 | newPos2 = -1 | |
458 | else: | |
459 | # or it might have changed the value | |
460 | newPos2 = evt.GetSashPosition() | |
461 | if newPos2 == -1: | |
462 | newPos1 = -1 | |
463 | ||
464 | return (newPos1, newPos2) | |
465 | ||
466 | ||
467 | def _AdjustSashPosition(self, idx, newPos1, newPos2=-1, adjustNeighbor=False): | |
468 | total = newPos1 + newPos2 | |
469 | ||
470 | # these are the windows on either side of the sash | |
471 | win1 = self._windows[idx] | |
472 | win2 = self._windows[idx+1] | |
473 | ||
474 | # make adjustments for window min sizes | |
475 | minSize = self._GetWindowMin(win1) | |
476 | if minSize == -1 or self._minimumPaneSize > minSize: | |
477 | minSize = self._minimumPaneSize | |
478 | minSize += self._GetBorderSize() | |
479 | if newPos1 < minSize: | |
480 | newPos1 = minSize | |
481 | newPos2 = total - newPos1 | |
482 | ||
483 | if adjustNeighbor: | |
484 | minSize = self._GetWindowMin(win2) | |
485 | if minSize == -1 or self._minimumPaneSize > minSize: | |
486 | minSize = self._minimumPaneSize | |
487 | minSize += self._GetBorderSize() | |
488 | if newPos2 < minSize: | |
489 | newPos2 = minSize | |
490 | newPos1 = total - newPos2 | |
491 | ||
492 | return (newPos1, newPos2) | |
493 | ||
494 | ||
495 | def _DoSetSashPosition(self, idx, newPos1, newPos2=-1, adjustNeighbor=False): | |
496 | newPos1, newPos2 = self._AdjustSashPosition(idx, newPos1, newPos2, adjustNeighbor) | |
497 | if newPos1 == self._sashes[idx]: | |
498 | return False | |
499 | self._sashes[idx] = newPos1 | |
500 | if adjustNeighbor: | |
501 | self._sashes[idx+1] = newPos2 | |
502 | return True | |
503 | ||
504 | ||
505 | def _SetSashPositionAndNotify(self, idx, newPos1, newPos2=-1, adjustNeighbor=False): | |
506 | # TODO: what is the thing about _requestedSashPosition for? | |
507 | ||
508 | self._DoSetSashPosition(idx, newPos1, newPos2, adjustNeighbor) | |
509 | ||
510 | evt = MultiSplitterEvent( | |
511 | wx.wxEVT_COMMAND_SPLITTER_SASH_POS_CHANGED, self) | |
512 | evt.SetSashIdx(idx) | |
513 | evt.SetSashPosition(newPos1) | |
514 | self._DoSendEvent(evt) | |
515 | ||
516 | if adjustNeighbor: | |
517 | evt.SetSashIdx(idx+1) | |
518 | evt.SetSashPosition(newPos2) | |
519 | self._DoSendEvent(evt) | |
520 | ||
521 | ||
522 | def _GetMotionDiff(self, x, y): | |
523 | # find the diff from the old pos | |
524 | if self._orient == wx.HORIZONTAL: | |
525 | diff = x - self._oldX | |
526 | else: | |
527 | diff = y - self._oldY | |
528 | return diff | |
529 | ||
530 | ||
531 | def _SashToCoord(self, idx, sashPos): | |
532 | coord = 0 | |
533 | for i in range(idx): | |
534 | coord += self._sashes[i] | |
535 | coord += self._GetSashSize() | |
536 | coord += sashPos | |
537 | return coord | |
538 | ||
539 | ||
540 | def _GetWindowMin(self, window): | |
541 | if self._orient == wx.HORIZONTAL: | |
542 | return window.GetMinWidth() | |
543 | else: | |
544 | return window.GetMinHeight() | |
545 | ||
546 | ||
547 | def _GetSashSize(self): | |
548 | if self.HasFlag(wx.SP_NOSASH): | |
549 | return 0 | |
550 | if wx.VERSION >= _RENDER_VER: | |
551 | return wx.RendererNative.Get().GetSplitterParams(self).widthSash | |
552 | else: | |
553 | return 5 | |
554 | ||
555 | ||
556 | def _GetBorderSize(self): | |
557 | if wx.VERSION >= _RENDER_VER: | |
558 | return wx.RendererNative.Get().GetSplitterParams(self).border | |
559 | else: | |
560 | return 0 | |
561 | ||
562 | ||
563 | def _DrawSash(self, dc): | |
564 | if wx.VERSION >= _RENDER_VER: | |
565 | if self.HasFlag(wx.SP_3DBORDER): | |
566 | wx.RendererNative.Get().DrawSplitterBorder( | |
567 | self, dc, self.GetClientRect()) | |
568 | ||
569 | # if there are no splits then we're done. | |
570 | if len(self._windows) < 2: | |
571 | return | |
572 | ||
573 | # if we are not supposed to use a sash then we're done. | |
574 | if self.HasFlag(wx.SP_NOSASH): | |
575 | return | |
576 | ||
577 | # Reverse the sense of the orientation, in this case it refers | |
578 | # to the direction to draw the sash not the direction that | |
579 | # windows are stacked. | |
580 | orient = { wx.HORIZONTAL : wx.VERTICAL, | |
581 | wx.VERTICAL : wx.HORIZONTAL }[self._orient] | |
582 | ||
583 | flag = 0 | |
584 | if self._isHot: | |
585 | flag = wx.CONTROL_CURRENT | |
586 | ||
587 | pos = 0 | |
588 | for sash in self._sashes[:-1]: | |
589 | pos += sash | |
590 | if wx.VERSION >= _RENDER_VER: | |
591 | wx.RendererNative.Get().DrawSplitterSash(self, dc, | |
592 | self.GetClientSize(), | |
593 | pos, orient, flag) | |
594 | else: | |
595 | dc.SetPen(wx.TRANSPARENT_PEN) | |
596 | dc.SetBrush(wx.Brush(self.GetBackgroundColour())) | |
597 | sashsize = self._GetSashSize() | |
598 | if orient == wx.VERTICAL: | |
599 | x = pos | |
600 | y = 0 | |
601 | w = sashsize | |
602 | h = self.GetClientSize().height | |
603 | else: | |
604 | x = 0 | |
605 | y = pos | |
606 | w = self.GetClientSize().width | |
607 | h = sashsize | |
608 | dc.DrawRectangle(x, y, w, h) | |
609 | ||
610 | pos += self._GetSashSize() | |
611 | ||
612 | ||
613 | def _DrawSashTracker(self, x, y): | |
614 | # Draw a line to represent the dragging sash, for when not | |
615 | # doing live updates | |
616 | w, h = self.GetClientSize() | |
617 | dc = wx.ScreenDC() | |
618 | ||
619 | if self._orient == wx.HORIZONTAL: | |
620 | x1 = x | |
621 | y1 = 2 | |
622 | x2 = x | |
623 | y2 = h-2 | |
624 | if x1 > w: | |
625 | x1 = w | |
626 | x2 = w | |
627 | elif x1 < 0: | |
628 | x1 = 0 | |
629 | x2 = 0 | |
630 | else: | |
631 | x1 = 2 | |
632 | y1 = y | |
633 | x2 = w-2 | |
634 | y2 = y | |
635 | if y1 > h: | |
636 | y1 = h | |
637 | y2 = h | |
638 | elif y1 < 0: | |
639 | y1 = 0 | |
640 | y2 = 0 | |
641 | ||
642 | x1, y1 = self.ClientToScreenXY(x1, y1) | |
643 | x2, y2 = self.ClientToScreenXY(x2, y2) | |
644 | ||
645 | dc.SetLogicalFunction(wx.INVERT) | |
646 | dc.SetPen(self._sashTrackerPen) | |
647 | dc.SetBrush(wx.TRANSPARENT_BRUSH) | |
648 | dc.DrawLine(x1, y1, x2, y2) | |
649 | dc.SetLogicalFunction(wx.COPY) | |
650 | ||
651 | ||
652 | def _SashHitTest(self, x, y, tolerance=5): | |
653 | # if there are no splits then we're done. | |
654 | if len(self._windows) < 2: | |
655 | return -1 | |
656 | ||
657 | if self._orient == wx.HORIZONTAL: | |
658 | z = x | |
659 | else: | |
660 | z = y | |
661 | ||
662 | pos = 0 | |
663 | for idx, sash in enumerate(self._sashes[:-1]): | |
664 | pos += sash | |
665 | hitMin = pos - tolerance | |
666 | hitMax = pos + self._GetSashSize() + tolerance | |
667 | ||
668 | if z >= hitMin and z <= hitMax: | |
669 | return idx | |
670 | ||
671 | pos += self._GetSashSize() | |
672 | ||
673 | return -1 | |
674 | ||
675 | ||
676 | def _SizeWindows(self): | |
677 | # no windows yet? | |
678 | if not self._windows: | |
679 | return | |
680 | ||
681 | # are there any pending size settings? | |
682 | for window, spos in self._pending.items(): | |
683 | idx = self._windows.index(window) | |
684 | # TODO: this may need adjusted to make sure they all fit | |
685 | # in the current client size | |
686 | self._sashes[idx] = spos | |
687 | del self._pending[window] | |
688 | ||
689 | # are there any that still have a -1? | |
690 | for idx, spos in enumerate(self._sashes[:-1]): | |
691 | if spos == -1: | |
692 | # TODO: this should also be adjusted | |
693 | self._sashes[idx] = 100 | |
694 | ||
695 | cw, ch = self.GetClientSize() | |
696 | border = self._GetBorderSize() | |
697 | sash = self._GetSashSize() | |
698 | ||
699 | if len(self._windows) == 1: | |
700 | # there's only one, it's an easy layout | |
701 | self._windows[0].SetDimensions(border, border, | |
702 | cw - 2*border, ch - 2*border) | |
703 | else: | |
704 | if 'wxMSW' in wx.PlatformInfo: | |
705 | self.Freeze() | |
706 | if self._orient == wx.HORIZONTAL: | |
707 | x = y = border | |
708 | h = ch - 2*border | |
709 | for idx, spos in enumerate(self._sashes[:-1]): | |
710 | self._windows[idx].SetDimensions(x, y, spos, h) | |
711 | x += spos + sash | |
712 | # last one takes the rest of the space. TODO make this configurable | |
713 | last = cw - 2*border - x | |
714 | self._windows[idx+1].SetDimensions(x, y, last, h) | |
715 | if last > 0: | |
716 | self._sashes[idx+1] = last | |
717 | else: | |
718 | x = y = border | |
719 | w = cw - 2*border | |
720 | for idx, spos in enumerate(self._sashes[:-1]): | |
721 | self._windows[idx].SetDimensions(x, y, w, spos) | |
722 | y += spos + sash | |
723 | # last one takes the rest of the space. TODO make this configurable | |
724 | last = ch - 2*border - y | |
725 | self._windows[idx+1].SetDimensions(x, y, w, last) | |
726 | if last > 0: | |
727 | self._sashes[idx+1] = last | |
728 | if 'wxMSW' in wx.PlatformInfo: | |
729 | self.Thaw() | |
730 | ||
731 | self._DrawSash(wx.ClientDC(self)) | |
732 | self._needUpdating = False | |
733 | ||
734 | ||
735 | def _DoSendEvent(self, evt): | |
736 | return not self.GetEventHandler().ProcessEvent(evt) or evt.IsAllowed() | |
737 | ||
738 | #---------------------------------------------------------------------- | |
739 | ||
740 | class MultiSplitterEvent(wx.PyCommandEvent): | |
741 | """ | |
742 | This event class is almost the same as `wx.SplitterEvent` except | |
743 | it adds an accessor for the sash index that is being changed. The | |
744 | same event type IDs and event binders are used as with | |
745 | `wx.SplitterEvent`. | |
746 | """ | |
747 | def __init__(self, type=wx.wxEVT_NULL, splitter=None): | |
748 | wx.PyCommandEvent.__init__(self, type) | |
749 | if splitter: | |
750 | self.SetEventObject(splitter) | |
751 | self.SetId(splitter.GetId()) | |
752 | self.sashIdx = -1 | |
753 | self.sashPos = -1 | |
754 | self.isAllowed = True | |
755 | ||
756 | def SetSashIdx(self, idx): | |
757 | self.sashIdx = idx | |
758 | ||
759 | def SetSashPosition(self, pos): | |
760 | self.sashPos = pos | |
761 | ||
762 | def GetSashIdx(self): | |
763 | return self.sashIdx | |
764 | ||
765 | def GetSashPosition(self): | |
766 | return self.sashPos | |
767 | ||
768 | # methods from wx.NotifyEvent | |
769 | def Veto(self): | |
770 | self.isAllowed = False | |
771 | def Allow(self): | |
772 | self.isAllowed = True | |
773 | def IsAllowed(self): | |
774 | return self.isAllowed | |
775 | ||
776 | ||
777 | ||
778 | #---------------------------------------------------------------------- | |
779 | ||
780 | ||
781 |