]>
Commit | Line | Data |
---|---|---|
0263d0bb RD |
1 | #---------------------------------------------------------------------- |
2 | # Name: wx.lib.splitter | |
3 | # Purpose: A class similar to wx.SplitterWindow but that allows more | |
a6cb8040 | 4 | # than a single split |
0263d0bb RD |
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 | |
0263d0bb RD |
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 | |
095315e2 | 31 | that deal with the child windows managed by the splitter, and also |
0263d0bb RD |
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 | |
095315e2 | 40 | by the splitter. Once there is more than one sash then the |
0263d0bb RD |
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 | """ | |
095315e2 RD |
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. | |
0263d0bb | 135 | """ |
8948306f | 136 | self.InsertWindow(len(self._windows), window, sashPos) |
0263d0bb RD |
137 | |
138 | ||
139 | def InsertWindow(self, idx, window, sashPos=-1): | |
140 | """ | |
095315e2 RD |
141 | Insert a new window into the splitter at the position given in |
142 | ``idx``. | |
0263d0bb RD |
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 SizeWindows(self): | |
213 | """ | |
214 | Reposition and size the windows managed by the splitter. | |
215 | Useful when windows have been added/removed or when styles | |
216 | have been changed. | |
217 | """ | |
218 | self._SizeWindows() | |
219 | ||
220 | ||
221 | def DoGetBestSize(self): | |
222 | """ | |
223 | Overridden base class virtual. Determines the best size of | |
224 | the control based on the best sizes of the child windows. | |
225 | """ | |
226 | best = wx.Size(0,0) | |
227 | if not self._windows: | |
228 | best = wx.Size(10,10) | |
229 | ||
230 | sashsize = self._GetSashSize() | |
231 | if self._orient == wx.HORIZONTAL: | |
232 | for win in self._windows: | |
233 | winbest = win.GetAdjustedBestSize() | |
234 | best.width += max(self._minimumPaneSize, winbest.width) | |
235 | best.height = max(best.height, winbest.height) | |
236 | best.width += sashsize * (len(self._windows)-1) | |
237 | ||
238 | else: | |
239 | for win in self._windows: | |
240 | winbest = win.GetAdjustedBestSize() | |
241 | best.height += max(self._minimumPaneSize, winbest.height) | |
242 | best.width = max(best.width, winbest.width) | |
243 | best.height += sashsize * (len(self._windows)-1) | |
244 | ||
245 | border = 2 * self._GetBorderSize() | |
246 | best.width += border | |
247 | best.height += border | |
248 | return best | |
249 | ||
250 | # ------------------------------------- | |
251 | # Event handlers | |
252 | ||
253 | def _OnPaint(self, evt): | |
254 | dc = wx.PaintDC(self) | |
255 | self._DrawSash(dc) | |
256 | ||
257 | ||
258 | def _OnSize(self, evt): | |
259 | parent = wx.GetTopLevelParent(self) | |
260 | if parent.IsIconized(): | |
261 | evt.Skip() | |
262 | return | |
263 | self._SizeWindows() | |
264 | ||
265 | ||
266 | def _OnIdle(self, evt): | |
267 | evt.Skip() | |
268 | # if this is the first idle time after a sash position has | |
269 | # potentially been set, allow _SizeWindows to check for a | |
270 | # requested size. | |
271 | if not self._checkRequestedSashPosition: | |
272 | self._checkRequestedSashPosition = True | |
273 | self._SizeWindows() | |
274 | ||
275 | if self._needUpdating: | |
276 | self._SizeWindows() | |
277 | ||
278 | ||
279 | ||
280 | def _OnMouse(self, evt): | |
281 | if self.HasFlag(wx.SP_NOSASH): | |
282 | return | |
283 | ||
284 | x, y = evt.GetPosition() | |
285 | isLive = self.HasFlag(wx.SP_LIVE_UPDATE) | |
286 | adjustNeighbor = evt.ShiftDown() | |
287 | ||
288 | # LeftDown: set things up for dragging the sash | |
289 | if evt.LeftDown() and self._SashHitTest(x, y) != -1: | |
290 | self._activeSash = self._SashHitTest(x, y) | |
291 | self._dragMode = wx.SPLIT_DRAG_DRAGGING | |
292 | ||
293 | self.CaptureMouse() | |
294 | self._SetResizeCursor() | |
295 | ||
296 | if not isLive: | |
297 | self._pendingPos = (self._sashes[self._activeSash], | |
298 | self._sashes[self._activeSash+1]) | |
299 | self._DrawSashTracker(x, y) | |
300 | ||
301 | self._oldX = x | |
302 | self._oldY = y | |
303 | return | |
304 | ||
305 | # LeftUp: Finsish the drag | |
306 | elif evt.LeftUp() and self._dragMode == wx.SPLIT_DRAG_DRAGGING: | |
307 | self._dragMode = wx.SPLIT_DRAG_NONE | |
308 | self.ReleaseMouse() | |
309 | self.SetCursor(wx.STANDARD_CURSOR) | |
310 | ||
311 | if not isLive: | |
312 | # erase the old tracker | |
313 | self._DrawSashTracker(self._oldX, self._oldY) | |
314 | ||
315 | diff = self._GetMotionDiff(x, y) | |
316 | ||
317 | # determine if we can change the position | |
318 | if isLive: | |
319 | oldPos1, oldPos2 = (self._sashes[self._activeSash], | |
320 | self._sashes[self._activeSash+1]) | |
321 | else: | |
322 | oldPos1, oldPos2 = self._pendingPos | |
323 | newPos1, newPos2 = self._OnSashPositionChanging(self._activeSash, | |
324 | oldPos1 + diff, | |
325 | oldPos2 - diff, | |
326 | adjustNeighbor) | |
327 | if newPos1 == -1: | |
328 | # the change was not allowed | |
329 | return | |
330 | ||
331 | # TODO: check for unsplit? | |
332 | ||
333 | self._SetSashPositionAndNotify(self._activeSash, newPos1, newPos2, adjustNeighbor) | |
334 | self._activeSash = -1 | |
335 | self._pendingPos = (-1, -1) | |
336 | self._SizeWindows() | |
337 | ||
338 | # Entering or Leaving a sash: Change the cursor | |
339 | elif (evt.Moving() or evt.Leaving() or evt.Entering()) and self._dragMode == wx.SPLIT_DRAG_NONE: | |
340 | if evt.Leaving() or self._SashHitTest(x, y) == -1: | |
341 | self._OnLeaveSash() | |
342 | else: | |
343 | self._OnEnterSash() | |
344 | ||
345 | # Dragging the sash | |
346 | elif evt.Dragging() and self._dragMode == wx.SPLIT_DRAG_DRAGGING: | |
347 | diff = self._GetMotionDiff(x, y) | |
348 | if not diff: | |
349 | return # mouse didn't move far enough | |
350 | ||
351 | # determine if we can change the position | |
352 | if isLive: | |
353 | oldPos1, oldPos2 = (self._sashes[self._activeSash], | |
354 | self._sashes[self._activeSash+1]) | |
355 | else: | |
356 | oldPos1, oldPos2 = self._pendingPos | |
357 | newPos1, newPos2 = self._OnSashPositionChanging(self._activeSash, | |
358 | oldPos1 + diff, | |
359 | oldPos2 - diff, | |
360 | adjustNeighbor) | |
361 | if newPos1 == -1: | |
362 | # the change was not allowed | |
363 | return | |
364 | ||
365 | if newPos1 == self._sashes[self._activeSash]: | |
366 | return # nothing was changed | |
367 | ||
368 | if not isLive: | |
369 | # erase the old tracker | |
370 | self._DrawSashTracker(self._oldX, self._oldY) | |
371 | ||
372 | if self._orient == wx.HORIZONTAL: | |
373 | x = self._SashToCoord(self._activeSash, newPos1) | |
374 | else: | |
375 | y = self._SashToCoord(self._activeSash, newPos1) | |
376 | ||
377 | # Remember old positions | |
378 | self._oldX = x | |
379 | self._oldY = y | |
380 | ||
381 | if not isLive: | |
382 | # draw a new tracker | |
383 | self._pendingPos = (newPos1, newPos2) | |
384 | self._DrawSashTracker(self._oldX, self._oldY) | |
385 | else: | |
386 | self._DoSetSashPosition(self._activeSash, newPos1, newPos2, adjustNeighbor) | |
387 | self._needUpdating = True | |
388 | ||
389 | ||
390 | # ------------------------------------- | |
391 | # Internal helpers | |
392 | ||
393 | def _RedrawIfHotSensitive(self, isHot): | |
394 | if not wx.VERSION >= _RENDER_VER: | |
395 | return | |
396 | if wx.RendererNative.Get().GetSplitterParams(self).isHotSensitive: | |
397 | self._isHot = isHot | |
398 | dc = wx.ClientDC(self) | |
399 | self._DrawSash(dc) | |
400 | ||
401 | ||
402 | def _OnEnterSash(self): | |
403 | self._SetResizeCursor() | |
404 | self._RedrawIfHotSensitive(True) | |
405 | ||
406 | ||
407 | def _OnLeaveSash(self): | |
408 | self.SetCursor(wx.STANDARD_CURSOR) | |
409 | self._RedrawIfHotSensitive(False) | |
410 | ||
411 | ||
412 | def _SetResizeCursor(self): | |
413 | if self._orient == wx.HORIZONTAL: | |
414 | self.SetCursor(self._sashCursorWE) | |
415 | else: | |
416 | self.SetCursor(self._sashCursorNS) | |
417 | ||
418 | ||
419 | def _OnSashPositionChanging(self, idx, newPos1, newPos2, adjustNeighbor): | |
420 | # TODO: check for possibility of unsplit (pane size becomes zero) | |
421 | ||
422 | # make sure that minsizes are honored | |
423 | newPos1, newPos2 = self._AdjustSashPosition(idx, newPos1, newPos2, adjustNeighbor) | |
424 | ||
425 | # sanity check | |
426 | if newPos1 <= 0: | |
427 | newPos2 += newPos1 | |
428 | newPos1 = 0 | |
429 | ||
430 | # send the events | |
431 | evt = MultiSplitterEvent( | |
432 | wx.wxEVT_COMMAND_SPLITTER_SASH_POS_CHANGING, self) | |
433 | evt.SetSashIdx(idx) | |
434 | evt.SetSashPosition(newPos1) | |
435 | if not self._DoSendEvent(evt): | |
436 | # the event handler vetoed the change | |
437 | newPos1 = -1 | |
438 | else: | |
439 | # or it might have changed the value | |
440 | newPos1 = evt.GetSashPosition() | |
441 | ||
442 | if adjustNeighbor and newPos1 != -1: | |
443 | evt.SetSashIdx(idx+1) | |
444 | evt.SetSashPosition(newPos2) | |
445 | if not self._DoSendEvent(evt): | |
446 | # the event handler vetoed the change | |
447 | newPos2 = -1 | |
448 | else: | |
449 | # or it might have changed the value | |
450 | newPos2 = evt.GetSashPosition() | |
451 | if newPos2 == -1: | |
452 | newPos1 = -1 | |
453 | ||
454 | return (newPos1, newPos2) | |
455 | ||
456 | ||
457 | def _AdjustSashPosition(self, idx, newPos1, newPos2=-1, adjustNeighbor=False): | |
458 | total = newPos1 + newPos2 | |
459 | ||
460 | # these are the windows on either side of the sash | |
461 | win1 = self._windows[idx] | |
462 | win2 = self._windows[idx+1] | |
463 | ||
464 | # make adjustments for window min sizes | |
465 | minSize = self._GetWindowMin(win1) | |
466 | if minSize == -1 or self._minimumPaneSize > minSize: | |
467 | minSize = self._minimumPaneSize | |
468 | minSize += self._GetBorderSize() | |
469 | if newPos1 < minSize: | |
470 | newPos1 = minSize | |
471 | newPos2 = total - newPos1 | |
472 | ||
473 | if adjustNeighbor: | |
474 | minSize = self._GetWindowMin(win2) | |
475 | if minSize == -1 or self._minimumPaneSize > minSize: | |
476 | minSize = self._minimumPaneSize | |
477 | minSize += self._GetBorderSize() | |
478 | if newPos2 < minSize: | |
479 | newPos2 = minSize | |
480 | newPos1 = total - newPos2 | |
481 | ||
482 | return (newPos1, newPos2) | |
483 | ||
484 | ||
485 | def _DoSetSashPosition(self, idx, newPos1, newPos2=-1, adjustNeighbor=False): | |
486 | newPos1, newPos2 = self._AdjustSashPosition(idx, newPos1, newPos2, adjustNeighbor) | |
487 | if newPos1 == self._sashes[idx]: | |
488 | return False | |
489 | self._sashes[idx] = newPos1 | |
490 | if adjustNeighbor: | |
491 | self._sashes[idx+1] = newPos2 | |
492 | return True | |
493 | ||
494 | ||
495 | def _SetSashPositionAndNotify(self, idx, newPos1, newPos2=-1, adjustNeighbor=False): | |
496 | # TODO: what is the thing about _requestedSashPosition for? | |
497 | ||
498 | self._DoSetSashPosition(idx, newPos1, newPos2, adjustNeighbor) | |
499 | ||
500 | evt = MultiSplitterEvent( | |
501 | wx.wxEVT_COMMAND_SPLITTER_SASH_POS_CHANGED, self) | |
502 | evt.SetSashIdx(idx) | |
503 | evt.SetSashPosition(newPos1) | |
504 | self._DoSendEvent(evt) | |
505 | ||
506 | if adjustNeighbor: | |
507 | evt.SetSashIdx(idx+1) | |
508 | evt.SetSashPosition(newPos2) | |
509 | self._DoSendEvent(evt) | |
510 | ||
511 | ||
512 | def _GetMotionDiff(self, x, y): | |
513 | # find the diff from the old pos | |
514 | if self._orient == wx.HORIZONTAL: | |
515 | diff = x - self._oldX | |
516 | else: | |
517 | diff = y - self._oldY | |
518 | return diff | |
519 | ||
520 | ||
521 | def _SashToCoord(self, idx, sashPos): | |
522 | coord = 0 | |
523 | for i in range(idx): | |
524 | coord += self._sashes[i] | |
525 | coord += self._GetSashSize() | |
526 | coord += sashPos | |
527 | return coord | |
528 | ||
529 | ||
530 | def _GetWindowMin(self, window): | |
531 | if self._orient == wx.HORIZONTAL: | |
532 | return window.GetMinWidth() | |
533 | else: | |
534 | return window.GetMinHeight() | |
535 | ||
536 | ||
537 | def _GetSashSize(self): | |
538 | if self.HasFlag(wx.SP_NOSASH): | |
539 | return 0 | |
540 | if wx.VERSION >= _RENDER_VER: | |
541 | return wx.RendererNative.Get().GetSplitterParams(self).widthSash | |
542 | else: | |
543 | return 5 | |
544 | ||
545 | ||
546 | def _GetBorderSize(self): | |
547 | if wx.VERSION >= _RENDER_VER: | |
548 | return wx.RendererNative.Get().GetSplitterParams(self).border | |
549 | else: | |
550 | return 0 | |
551 | ||
552 | ||
553 | def _DrawSash(self, dc): | |
554 | if wx.VERSION >= _RENDER_VER: | |
555 | if self.HasFlag(wx.SP_3DBORDER): | |
556 | wx.RendererNative.Get().DrawSplitterBorder( | |
557 | self, dc, self.GetClientRect()) | |
558 | ||
559 | # if there are no splits then we're done. | |
560 | if len(self._windows) < 2: | |
561 | return | |
562 | ||
563 | # if we are not supposed to use a sash then we're done. | |
564 | if self.HasFlag(wx.SP_NOSASH): | |
565 | return | |
566 | ||
567 | # Reverse the sense of the orientation, in this case it refers | |
568 | # to the direction to draw the sash not the direction that | |
569 | # windows are stacked. | |
570 | orient = { wx.HORIZONTAL : wx.VERTICAL, | |
571 | wx.VERTICAL : wx.HORIZONTAL }[self._orient] | |
572 | ||
573 | flag = 0 | |
574 | if self._isHot: | |
575 | flag = wx.CONTROL_CURRENT | |
576 | ||
577 | pos = 0 | |
578 | for sash in self._sashes[:-1]: | |
579 | pos += sash | |
580 | if wx.VERSION >= _RENDER_VER: | |
581 | wx.RendererNative.Get().DrawSplitterSash(self, dc, | |
582 | self.GetClientSize(), | |
583 | pos, orient, flag) | |
584 | else: | |
585 | dc.SetPen(wx.TRANSPARENT_PEN) | |
586 | dc.SetBrush(wx.Brush(self.GetBackgroundColour())) | |
587 | sashsize = self._GetSashSize() | |
588 | if orient == wx.VERTICAL: | |
589 | x = pos | |
590 | y = 0 | |
591 | w = sashsize | |
592 | h = self.GetClientSize().height | |
593 | else: | |
594 | x = 0 | |
595 | y = pos | |
596 | w = self.GetClientSize().width | |
597 | h = sashsize | |
598 | dc.DrawRectangle(x, y, w, h) | |
599 | ||
600 | pos += self._GetSashSize() | |
601 | ||
602 | ||
603 | def _DrawSashTracker(self, x, y): | |
604 | # Draw a line to represent the dragging sash, for when not | |
605 | # doing live updates | |
606 | w, h = self.GetClientSize() | |
607 | dc = wx.ScreenDC() | |
608 | ||
609 | if self._orient == wx.HORIZONTAL: | |
610 | x1 = x | |
611 | y1 = 2 | |
612 | x2 = x | |
613 | y2 = h-2 | |
614 | if x1 > w: | |
615 | x1 = w | |
616 | x2 = w | |
617 | elif x1 < 0: | |
618 | x1 = 0 | |
619 | x2 = 0 | |
620 | else: | |
621 | x1 = 2 | |
622 | y1 = y | |
623 | x2 = w-2 | |
624 | y2 = y | |
625 | if y1 > h: | |
626 | y1 = h | |
627 | y2 = h | |
628 | elif y1 < 0: | |
629 | y1 = 0 | |
630 | y2 = 0 | |
631 | ||
632 | x1, y1 = self.ClientToScreenXY(x1, y1) | |
633 | x2, y2 = self.ClientToScreenXY(x2, y2) | |
634 | ||
635 | dc.SetLogicalFunction(wx.INVERT) | |
636 | dc.SetPen(self._sashTrackerPen) | |
637 | dc.SetBrush(wx.TRANSPARENT_BRUSH) | |
638 | dc.DrawLine(x1, y1, x2, y2) | |
639 | dc.SetLogicalFunction(wx.COPY) | |
640 | ||
641 | ||
642 | def _SashHitTest(self, x, y, tolerance=5): | |
643 | # if there are no splits then we're done. | |
644 | if len(self._windows) < 2: | |
645 | return -1 | |
646 | ||
647 | if self._orient == wx.HORIZONTAL: | |
648 | z = x | |
649 | else: | |
650 | z = y | |
651 | ||
652 | pos = 0 | |
653 | for idx, sash in enumerate(self._sashes[:-1]): | |
654 | pos += sash | |
655 | hitMin = pos - tolerance | |
656 | hitMax = pos + self._GetSashSize() + tolerance | |
657 | ||
658 | if z >= hitMin and z <= hitMax: | |
659 | return idx | |
660 | ||
661 | pos += self._GetSashSize() | |
662 | ||
663 | return -1 | |
664 | ||
665 | ||
666 | def _SizeWindows(self): | |
667 | # no windows yet? | |
668 | if not self._windows: | |
669 | return | |
670 | ||
671 | # are there any pending size settings? | |
672 | for window, spos in self._pending.items(): | |
673 | idx = self._windows.index(window) | |
674 | # TODO: this may need adjusted to make sure they all fit | |
675 | # in the current client size | |
676 | self._sashes[idx] = spos | |
677 | del self._pending[window] | |
678 | ||
679 | # are there any that still have a -1? | |
680 | for idx, spos in enumerate(self._sashes[:-1]): | |
681 | if spos == -1: | |
682 | # TODO: this should also be adjusted | |
683 | self._sashes[idx] = 100 | |
684 | ||
685 | cw, ch = self.GetClientSize() | |
686 | border = self._GetBorderSize() | |
687 | sash = self._GetSashSize() | |
688 | ||
689 | if len(self._windows) == 1: | |
690 | # there's only one, it's an easy layout | |
691 | self._windows[0].SetDimensions(border, border, | |
692 | cw - 2*border, ch - 2*border) | |
693 | else: | |
3a9604b4 | 694 | if 'wxMSW' in wx.PlatformInfo: |
94211100 | 695 | self.Freeze() |
0263d0bb RD |
696 | if self._orient == wx.HORIZONTAL: |
697 | x = y = border | |
698 | h = ch - 2*border | |
699 | for idx, spos in enumerate(self._sashes[:-1]): | |
700 | self._windows[idx].SetDimensions(x, y, spos, h) | |
701 | x += spos + sash | |
702 | # last one takes the rest of the space. TODO make this configurable | |
703 | last = cw - 2*border - x | |
704 | self._windows[idx+1].SetDimensions(x, y, last, h) | |
705 | if last > 0: | |
706 | self._sashes[idx+1] = last | |
707 | else: | |
708 | x = y = border | |
709 | w = cw - 2*border | |
710 | for idx, spos in enumerate(self._sashes[:-1]): | |
711 | self._windows[idx].SetDimensions(x, y, w, spos) | |
712 | y += spos + sash | |
713 | # last one takes the rest of the space. TODO make this configurable | |
714 | last = ch - 2*border - y | |
715 | self._windows[idx+1].SetDimensions(x, y, w, last) | |
716 | if last > 0: | |
717 | self._sashes[idx+1] = last | |
3a9604b4 | 718 | if 'wxMSW' in wx.PlatformInfo: |
94211100 | 719 | self.Thaw() |
0263d0bb RD |
720 | |
721 | self._DrawSash(wx.ClientDC(self)) | |
722 | self._needUpdating = False | |
723 | ||
724 | ||
725 | def _DoSendEvent(self, evt): | |
726 | return not self.GetEventHandler().ProcessEvent(evt) or evt.IsAllowed() | |
727 | ||
728 | #---------------------------------------------------------------------- | |
729 | ||
730 | class MultiSplitterEvent(wx.PyCommandEvent): | |
731 | """ | |
732 | This event class is almost the same as `wx.SplitterEvent` except | |
733 | it adds an accessor for the sash index that is being changed. The | |
734 | same event type IDs and event binders are used as with | |
735 | `wx.SplitterEvent`. | |
736 | """ | |
737 | def __init__(self, type=wx.wxEVT_NULL, splitter=None): | |
738 | wx.PyCommandEvent.__init__(self, type) | |
739 | if splitter: | |
740 | self.SetEventObject(splitter) | |
741 | self.SetId(splitter.GetId()) | |
742 | self.sashIdx = -1 | |
743 | self.sashPos = -1 | |
744 | self.isAllowed = True | |
745 | ||
746 | def SetSashIdx(self, idx): | |
747 | self.sashIdx = idx | |
748 | ||
749 | def SetSashPosition(self, pos): | |
750 | self.sashPos = pos | |
751 | ||
752 | def GetSashIdx(self): | |
753 | return self.sashIdx | |
754 | ||
755 | def GetSashPosition(self): | |
756 | return self.sashPos | |
757 | ||
758 | # methods from wx.NotifyEvent | |
759 | def Veto(self): | |
760 | self.isAllowed = False | |
761 | def Allow(self): | |
762 | self.isAllowed = True | |
763 | def IsAllowed(self): | |
764 | return self.isAllowed | |
765 | ||
766 | ||
767 | ||
768 | #---------------------------------------------------------------------- | |
769 | ||
770 | ||
771 |