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