]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/foldpanelbar.py
Patch from Andrea that fixes the following problems/issues:
[wxWidgets.git] / wxPython / wx / lib / foldpanelbar.py
1 # --------------------------------------------------------------------------- #
2 # FOLDPANELBAR wxPython IMPLEMENTATION
3 # Ported From Jorgen Bodde & Julian Smart (Extended Demo) C++ Code By:
4 #
5 # Andrea Gavana, @ 23 Mar 2005
6 # Latest Revision: 05 Nov 2005, 23.30 CET
7 #
8 #
9 # TODO List
10 #
11 # All The C++ TODOs Are Still Alive. I Am Not Able to Read Jorges's Mind
12 # So I Don't Really Know What Will Be The New Features/Additions He Will
13 # Make On His Code. At The Moment They Are:
14 #
15 # 1. OnPaint Function In CaptionBar Class:
16 # TODO: Maybe First A Memory Dc Should Draw All, And Then Paint It On The
17 # Caption. This Way A Flickering Arrow During Resize Is Not Visible.
18 #
19 # 2. OnChar Function In CaptionBar Class:
20 # TODO: This Is Easy To Do But I Don't Have Any Useful Idea On Which Kind
21 # Of Features To Add. Does Anyone Have An Intelligent Idea?
22 #
23 # 3. AddFoldPanelWindow Function In FoldPanelBar Class:
24 # TODO: Take Old And New Heights, And If Difference, Reposition All The
25 # Lower Panels. This Is Because The User Can Add New wxWindow Controls
26 # Somewhere In Between When Other Panels Are Already Present.
27 # Don't Know What It Means. Probably Is My Poor English...
28 #
29 # 4. OnSizePanel Function In FoldPanelBar Class:
30 # TODO: A Smart Way To Check Wether The Old - New Width Of The
31 # Panel Changed, If So No Need To Resize The Fold Panel Items
32 #
33 #
34 # DONE List:
35 #
36 # 1. Implemented Styles Like FPB_SINGLE_FOLD and FPB_EXCLUSIVE_FOLD
37 # Thanks To E. A. Tacao For His Nice Suggestions.
38 #
39 # 2. Added Some Maquillage To FoldPanelBar: When The Mouse Enters The Icon
40 # Region, It Is Changed To wx.CURSOR_HAND.
41 #
42 #
43 # For The Original TODO List From Jorgen, Please Refer To:
44 # http://www.solidsteel.nl/jorg/components/foldpanel/wxFoldPanelBar.php#todo_list
45 #
46 #
47 #
48 # For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please
49 # Write To Me At:
50 #
51 # andrea.gavana@agip.it
52 # andrea_gavan@tin.it
53 #
54 # Or, Obviously, To The wxPython Mailing List!!!
55 #
56 #
57 # End Of Comments
58 # --------------------------------------------------------------------------- #
59
60
61 """
62 The `FoldPanelBar` is a control that contains multiple panels (of type
63 `FoldPanelItem`) that can be expanded or collapsed. The captionbar of
64 the FoldPanel can be customized by setting it to a horizontal gradient
65 style, vertical gradient style, a single color, a rectangle or filled
66 rectangle. The FoldPanel items can be collapsed in place or to the
67 bottom of the control. `wx.Window` derived controls can be added
68 dynamically, and separated by separator lines. FoldPanelBar is
69 freeware and distributed under the wxPython license.
70
71
72 How does it work
73 ----------------
74
75 The internals of the FoldPanelBar is a list of FoldPanelItem objects. Through
76 the reference of FoldPanel these panels can be controlled by adding new controls
77 to a FoldPanel or adding new FoldPanels to the FoldPanelBar.
78 The CaptionBar fires events to the parent (container of all panel items) when a
79 sub-panel needs resizing (either folding or expanding). The fold or expand process
80 is simply a resize of the panel so it looks like all controls on it are gone. All
81 controls are still child of the FoldPanel they are located on. If they don't
82 handle the event (and they won't) then the owner of the FoldPanelBar gets the
83 events. This is what you need to handle the controls. There isn't much to it just
84 a lot of calculations to see what panel belongs where. There are no sizers
85 involved in the panels, everything is purely x-y positioning.
86
87
88 What can it do and what not?
89 ----------------------------
90
91 a) What it can do:
92 * Run-time addition of panels (no deletion just yet)
93 * Run time addition of controls to the panel (it will be resized accordingly)
94 * Creating panels in collapsed mode or expanded mode
95 * Various modes of caption behaviour and filling to make it more appealing
96 * Panels can be folded and collapsed (or all of them) to allow more space
97
98 b) What it cannot do:
99
100 * Selection of a panel like in a list ctrl
101 * Dragging and dropping the panels
102 * Re-ordering the panels (not yet)
103
104
105 Supported platforms
106 -------------------
107
108 FoldPanelBar is supported on the following platforms:
109 * Windows (Verified on Windows XP, 2000)
110 * Linux/Unix (GTK2) (Thanks To Toni Brkic And Robin Dunn)
111 * Mac OSX (Thanks To Robin Dunn For The CaptionBar Size Patch)
112
113
114 FoldPanelBar is based upon Jorgen Bodde's C++ implementation.
115 Latest Revision: Andrea Gavana @ 05 Nov 2005, 23.30 CET
116
117 """
118
119 import wx
120
121 #----------------------------------------------------------------------
122 # Collapsed And Expanded Bitmap Images
123 # Created With img2py.py
124 #----------------------------------------------------------------------
125
126 def GetCollapsedIconData():
127 return \
128 '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\
129 \x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\
130 \x00\x007IDAT8\x8dcddbf\xa0\x040Q\xa4{\xf0\x1b\xf0\xff\xdf\xdf\xff\x03\xe7\
131 \x02\x98\xed\x84\\A\x1b\x17\xa0\xdb\x8a\xcf\x15\xd4w\x01.\xdbp\x89S\xec\x02\
132 \xc6\xd1\xbc\xc0\x00\x00\x9a\xf5\x1b\xfa\xf9m$?\x00\x00\x00\x00IEND\xaeB`\
133 \x82'
134
135 def GetCollapsedIconBitmap():
136 return wx.BitmapFromImage(GetCollapsedIconImage())
137
138 def GetCollapsedIconImage():
139 import cStringIO
140 stream = cStringIO.StringIO(GetCollapsedIconData())
141 return wx.ImageFromStream(stream)
142
143 #----------------------------------------------------------------------
144 def GetExpandedIconData():
145 return \
146 '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\
147 \x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\
148 \x00\x00BIDAT8\x8dcddbf\xa0\x040Q\xa4{P\x18\xc0\x82.\xf0\xff\xdf\xdf\xff\xb8\
149 \x143213R\xdd\x05\x18\x06`\xb3\x05\x9f8m\x02\x11\xdd6\\\xb6\xd3\xce\x05\xc8\
150 \xb6\xe2\xb3\x9d*.`\x1c\xcd\x0b\x0c\x00\x9e\xbc\x04W\x19\xcfa\xb5\x00\x00\
151 \x00\x00IEND\xaeB`\x82'
152
153 def GetExpandedIconBitmap():
154 return wx.BitmapFromImage(GetExpandedIconImage())
155
156 def GetExpandedIconImage():
157 import cStringIO
158 stream = cStringIO.StringIO(GetExpandedIconData())
159 return wx.ImageFromStream(stream)
160
161 #----------------------------------------------------------------------
162
163 #----------------------------------------------------------------------
164 # FOLDPANELBAR Starts Here
165 #----------------------------------------------------------------------
166
167 # CAPTIONBAR STYLES
168 #
169 #- CAPTIONBAR_GRADIENT_V: Draws a vertical gradient from top to bottom
170 #- CAPTIONBAR_GRADIENT_H: Draws a horizontal gradient from left to right
171 #- CAPTIONBAR_SINGLE: Draws a single filled rectangle to draw the caption
172 #- CAPTIONBAR_RECTANGLE: Draws a single colour with a rectangle around the caption
173 #- CAPTIONBAR_FILLED_RECTANGLE: Draws a filled rectangle and a border around it
174
175 CAPTIONBAR_NOSTYLE = 0
176 CAPTIONBAR_GRADIENT_V = 1
177 CAPTIONBAR_GRADIENT_H = 2
178 CAPTIONBAR_SINGLE = 3
179 CAPTIONBAR_RECTANGLE = 4
180 CAPTIONBAR_FILLED_RECTANGLE = 5
181
182 FPB_EXTRA_X = 10
183 FPB_EXTRA_Y = 4
184
185 # pixels of the bmp to be aligned from the right filled with space
186 FPB_BMP_RIGHTSPACE = 2
187
188 # Now supported! Single fold forces
189 # other panels to close when they are open, and only opens the current panel.
190 # This will allow the open panel to gain the full size left in the client area
191 FPB_SINGLE_FOLD = 0x0001
192
193 # All panels are stacked to the bottom. When they are expanded again they
194 # show up at the top
195 FPB_COLLAPSE_TO_BOTTOM = 0x0002
196
197 # Now supported! Single fold plus panels
198 # will be stacked at the bottom
199 FPB_EXCLUSIVE_FOLD = 0x0004
200
201 # Orientation Flag
202 FPB_HORIZONTAL = wx.HORIZONTAL
203 FPB_VERTICAL = wx.VERTICAL
204
205 # Default Extrastyle of the FoldPanelBar
206 FPB_DEFAULT_EXTRASTYLE = 0
207 # Default style of the FoldPanelBar
208 FPB_DEFAULT_STYLE = wx.TAB_TRAVERSAL | wx.NO_BORDER
209
210 # FoldPanelItem default settings
211 FPB_ALIGN_LEFT = 0
212 FPB_ALIGN_WIDTH = 1
213
214 FPB_DEFAULT_LEFTSPACING = 5
215 FPB_DEFAULT_RIGHTSPACING = 10
216 FPB_DEFAULT_SPACING = 8
217
218 FPB_DEFAULT_LEFTLINESPACING = 2
219 FPB_DEFAULT_RIGHTLINESPACING = 2
220
221
222 # ------------------------------------------------------------------------------ #
223 # class CaptionBarStyle
224 # ------------------------------------------------------------------------------ #
225
226 class CaptionBarStyle:
227 """
228 This class encapsulates the styles you wish to set for the
229 `CaptionBar` (this is the part of the FoldPanel where the caption
230 is displayed). It can either be applied at creation time be
231 reapplied when styles need to be changed.
232
233 At construction time, all styles are set to their default
234 transparency. This means none of the styles will be applied to
235 the `CaptionBar` in question, meaning it will be created using the
236 default internals. When setting i.e the color, font or panel
237 style, these styles become active to be used.
238
239 """
240
241 def __init__(self):
242 """ Default constructor for this class."""
243
244 self.ResetDefaults()
245
246
247 def ResetDefaults(self):
248 """ Resets default CaptionBarStyle."""
249 self._firstColourUsed = False
250 self._secondColourUsed = False
251 self._textColourUsed = False
252 self._captionFontUsed = False
253 self._captionStyleUsed = False
254 self._captionStyle = CAPTIONBAR_GRADIENT_V
255
256
257 # ------- CaptionBar Font -------
258
259 def SetCaptionFont(self, font):
260 """
261 Sets font for the caption bar.
262
263 If this is not set, the font property is undefined and will
264 not be used. Use `CaptionFontUsed` to check if this style is
265 used.
266 """
267 self._captionFont = font
268 self._captionFontUsed = True
269
270
271 def CaptionFontUsed(self):
272 """ Checks if the caption bar font is set. """
273 return self._captionFontUsed
274
275
276 def GetCaptionFont(self):
277 """
278 Returns the font for the caption bar.
279
280 Please be warned this will result in an assertion failure when
281 this property is not previously set.
282
283 :see: `SetCaptionFont`, `CaptionFontUsed`
284 """
285 return self._captionFont
286
287
288 # ------- First Colour -------
289
290 def SetFirstColour(self, colour):
291 """
292 Sets first colour for the caption bar.
293
294 If this is not set, the colour property is undefined and will
295 not be used. Use `FirstColourUsed` to check if this style is
296 used.
297 """
298 self._firstColour = colour
299 self._firstColourUsed = True
300
301
302 def FirstColourUsed(self):
303 """ Checks if the first colour of the caption bar is set."""
304 return self._firstColourUsed
305
306
307 def GetFirstColour(self):
308 """
309 Returns the first colour for the caption bar.
310
311 Please be warned this will result in an assertion failure when
312 this property is not previously set.
313
314 :see: `SetFirstColour`, `FirstColourUsed`
315 """
316 return self._firstColour
317
318
319 # ------- Second Colour -------
320
321 def SetSecondColour(self, colour):
322 """
323 Sets second colour for the caption bar.
324
325 If this is not set, the colour property is undefined and will
326 not be used. Use `SecondColourUsed` to check if this style is
327 used.
328 """
329 self._secondColour = colour
330 self._secondColourUsed = True
331
332
333 def SecondColourUsed(self):
334 """ Checks if the second colour of the caption bar is set."""
335 return self._secondColourUsed
336
337
338 def GetSecondColour(self):
339 """
340 Returns the second colour for the caption bar.
341
342 Please be warned this will result in an assertion failure when
343 this property is not previously set.
344
345 :see: `SetSecondColour`, `SecondColourUsed`
346 """
347 return self._secondColour
348
349
350 # ------- Caption Text Colour -------
351
352 def SetCaptionColour(self, colour):
353 """
354 Sets caption colour for the caption bar.
355
356 If this is not set, the colour property is undefined and will
357 not be used. Use `CaptionColourUsed` to check if this style is
358 used.
359 """
360 self._textColour = colour
361 self._textColourUsed = True
362
363
364 def CaptionColourUsed(self):
365 """ Checks if the caption colour of the caption bar is set."""
366 return self._textColourUsed
367
368
369 def GetCaptionColour(self):
370 """
371 Returns the caption colour for the caption bar.
372
373 Please be warned this will result in an assertion failure
374 when this property is not previously set.
375 See also SetCaptionColour(), CaptionColourUsed()
376 """
377 return self._textColour
378
379
380 # ------- CaptionStyle -------
381
382 def SetCaptionStyle(self, style):
383 """
384 Sets caption style for the caption bar.
385
386 If this is not set, the property is undefined and will not be
387 used. Use CaptionStyleUsed() to check if this style is used.
388 The following styles can be applied:
389
390 * CAPTIONBAR_GRADIENT_V: Draws a vertical gradient from top to bottom
391
392 * CAPTIONBAR_GRADIENT_H: Draws a horizontal gradient from
393 left to right
394
395 * CAPTIONBAR_SINGLE: Draws a single filled rectangle to
396 draw the caption
397
398 * CAPTIONBAR_RECTANGLE: Draws a single colour with a
399 rectangle around the caption
400
401 * CAPTIONBAR_FILLED_RECTANGLE: Draws a filled rectangle
402 and a border around it
403
404 """
405 self._captionStyle = style
406 self._captionStyleUsed = True
407
408
409 def CaptionStyleUsed(self):
410 """ Checks if the caption style of the caption bar is set."""
411 return self._captionStyleUsed
412
413
414 def GetCaptionStyle(self):
415 """
416 Returns the caption style for the caption bar.
417
418 Please be warned this will result in an assertion failure
419 when this property is not previously set.
420
421 :see: `SetCaptionStyle`, `CaptionStyleUsed`
422 """
423 return self._captionStyle
424
425
426 #-----------------------------------#
427 # CaptionBarEvent
428 #-----------------------------------#
429 wxEVT_CAPTIONBAR = wx.NewEventType()
430 EVT_CAPTIONBAR = wx.PyEventBinder(wxEVT_CAPTIONBAR, 0)
431
432
433 # ---------------------------------------------------------------------------- #
434 # class CaptionBarEvent
435 # ---------------------------------------------------------------------------- #
436
437 class CaptionBarEvent(wx.PyCommandEvent):
438 """
439 This event will be sent when a EVT_CAPTIONBAR is mapped in the parent.
440 It is to notify the parent that the bar is now in collapsed or expanded
441 state. The parent should re-arrange the associated windows accordingly
442 """
443 def __init__(self, evtType):
444 """ Default Constructor For This Class."""
445 wx.PyCommandEvent.__init__(self, evtType)
446
447
448 def GetFoldStatus(self):
449 """
450 Returns whether the bar is expanded or collapsed. True means
451 expanded.
452 """
453 return not self._bar.IsCollapsed()
454
455
456 def GetBar(self):
457 """ Returns The CaptionBar Selected."""
458 return self._bar
459
460
461 def SetTag(self, tag):
462 """ Assign A Tag To The Selected CaptionBar."""
463 self._tag = tag
464
465
466 def GetTag(self):
467 """ Returns The Tag Assigned To The Selected CaptionBar."""
468 return self._tag
469
470
471 def SetBar(self, bar):
472 """
473 Sets the bar associated with this event.
474
475 Should not used by any other then the originator of the event.
476 """
477 self._bar = bar
478
479
480 # -------------------------------------------------------------------------------- #
481 # class CaptionBar
482 # -------------------------------------------------------------------------------- #
483
484 class CaptionBar(wx.Window):
485 """
486 This class is a graphical caption component that consists of a
487 caption and a clickable arrow.
488
489 The CaptionBar fires an event EVT_CAPTIONBAR which is a
490 `CaptionBarEvent`. This event can be caught and the parent window
491 can act upon the collapsed or expanded state of the bar (which is
492 actually just the icon which changed). The parent panel can
493 reduce size or expand again.
494 """
495
496 # Define Empty CaptionBar Style
497 EmptyCaptionBarStyle = CaptionBarStyle()
498
499 def __init__(self, parent, id, pos, size, caption="",
500 foldIcons=None, cbstyle=EmptyCaptionBarStyle,
501 rightIndent=FPB_BMP_RIGHTSPACE,
502 iconWidth=16, iconHeight=16, collapsed=False):
503 """ Default Class Constructor."""
504
505 wx.Window.__init__(self, parent, wx.ID_ANY, pos=pos,
506 size=(20,20), style=wx.NO_BORDER)
507
508 self._controlCreated = False
509 self._collapsed = collapsed
510 self.ApplyCaptionStyle(cbstyle, True)
511
512 if foldIcons is None:
513 foldIcons = wx.ImageList(16, 16)
514
515 bmp = GetExpandedIconBitmap()
516 foldIcons.Add(bmp)
517 bmp = GetCollapsedIconBitmap()
518 foldIcons.Add(bmp)
519
520 # set initial size
521 if foldIcons:
522 assert foldIcons.GetImageCount() > 1
523 iconWidth, iconHeight = foldIcons.GetSize(0)
524
525 self._caption = caption
526 self._foldIcons = foldIcons
527 self._style = cbstyle
528 self._rightIndent = rightIndent
529 self._iconWidth = iconWidth
530 self._iconHeight = iconHeight
531 self._oldSize = wx.Size(20,20)
532
533 self._controlCreated = True
534
535 self.Bind(wx.EVT_PAINT, self.OnPaint)
536 self.Bind(wx.EVT_SIZE, self.OnSize)
537 self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseEvent)
538 self.Bind(wx.EVT_CHAR, self.OnChar)
539
540
541 def ApplyCaptionStyle(self, cbstyle=EmptyCaptionBarStyle, applyDefault=True):
542 """ Applies the style defined in cbstyle to the CaptionBar."""
543
544 newstyle = cbstyle
545
546 if applyDefault:
547
548 # get first colour from style or make it default
549 if not newstyle.FirstColourUsed():
550 newstyle.SetFirstColour(wx.WHITE)
551
552 # get second colour from style or make it default
553 if not newstyle.SecondColourUsed():
554 # make the second colour slightly darker then the background
555 color = self.GetParent().GetBackgroundColour()
556 r, g, b = int(color.Red()), int(color.Green()), int(color.Blue())
557 color = ((r >> 1) + 20, (g >> 1) + 20, (b >> 1) + 20)
558 newstyle.SetSecondColour(wx.Colour(color[0], color[1], color[2]))
559
560 # get text colour
561 if not newstyle.CaptionColourUsed():
562 newstyle.SetCaptionColour(wx.BLACK)
563
564 # get font colour
565 if not newstyle.CaptionFontUsed():
566 newstyle.SetCaptionFont(self.GetParent().GetFont())
567
568 # apply caption style
569 if not newstyle.CaptionStyleUsed():
570 newstyle.SetCaptionStyle(CAPTIONBAR_GRADIENT_V)
571
572 self._style = newstyle
573
574
575 def SetCaptionStyle(self, cbstyle=EmptyCaptionBarStyle, applyDefault=True):
576 """
577 Sets CaptionBar styles with CapionBarStyle class.
578
579 All styles that are actually set, are applied. If you set
580 applyDefault to True, all other (not defined) styles will be
581 set to default. If it is False, the styles which are not set
582 in the CaptionBarStyle will be ignored.
583 """
584 self.ApplyCaptionStyle(cbstyle, applyDefault)
585 self.Refresh()
586
587
588 def GetCaptionStyle(self):
589 """
590 Returns the current style of the captionbar in a
591 `CaptionBarStyle` class.
592
593 This can be used to change and set back the changes.
594 """
595 return self._style
596
597
598 def IsCollapsed(self):
599 """
600 Returns wether the status of the bar is expanded or collapsed.
601 """
602 return self._collapsed
603
604
605 def SetRightIndent(self, pixels):
606 """
607 Sets the amount of pixels on the right from which the bitmap
608 is trailing.
609
610 If this is 0, it will be drawn all the way to the right,
611 default is equal to FPB_BMP_RIGHTSPACE. Assign this before
612 assigning an image list to prevent a redraw.
613 """
614 assert pixels >= 0
615 self._rightIndent = pixels
616 if self._foldIcons:
617 self.Refresh()
618
619
620 def Collapse(self):
621 """
622 This sets the internal state / representation to collapsed.
623
624 This does not trigger a `CaptionBarEvent` to be sent to the
625 parent.
626 """
627 self._collapsed = True
628 self.RedrawIconBitmap()
629
630
631 def Expand(self):
632 """
633 This sets the internal state / representation to expanded.
634
635 This does not trigger a `CaptionBarEvent` to be sent to the
636 parent.
637 """
638 self._collapsed = False
639 self.RedrawIconBitmap()
640
641
642 def SetBoldFont(self):
643 """ Sets the CaptionBarFont weight to BOLD."""
644
645 self.GetFont().SetWeight(wx.BOLD)
646
647
648 def SetNormalFont(self):
649 """ Sets the CaptionBarFont weight to NORMAL."""
650
651 self.GetFont().SetWeight(wx.NORMAL)
652
653
654 def IsVertical(self):
655 """
656 Returns wether the CaptionBar Has Default Orientation Or Not.
657
658 Default is vertical.
659 """
660
661 fld = self.GetParent().GetGrandParent()
662 if isinstance(fld, FoldPanelBar):
663 return self.GetParent().GetGrandParent().IsVertical()
664 else:
665 raise "ERROR: Wrong Parent " + repr(fld)
666
667
668 def OnPaint(self, event):
669 """ The paint event for flat or gradient fill. """
670
671 if not self._controlCreated:
672 event.Skip()
673 return
674
675 dc = wx.PaintDC(self)
676 wndRect = self.GetRect()
677 vertical = self.IsVertical()
678
679 # TODO: Maybe first a memory DC should draw all, and then paint it on
680 # the caption. This way a flickering arrow during resize is not visible
681
682 self.FillCaptionBackground(dc)
683 dc.SetFont(self._style.GetCaptionFont())
684 dc.SetTextForeground(self._style.GetCaptionColour())
685
686 if vertical:
687 dc.DrawText(self._caption, 4, FPB_EXTRA_Y/2)
688 else:
689 dc.DrawRotatedText(self._caption, FPB_EXTRA_Y/2,
690 wndRect.GetBottom() - 4, 90)
691
692 # draw small icon, either collapsed or expanded
693 # based on the state of the bar. If we have any bmp's
694
695 if self._foldIcons:
696
697 index = self._collapsed
698
699 if vertical:
700 drw = wndRect.GetRight() - self._iconWidth - self._rightIndent
701 self._foldIcons.Draw(index, dc, drw,
702 (wndRect.GetHeight() - self._iconHeight)/2,
703 wx.IMAGELIST_DRAW_TRANSPARENT)
704 else:
705 self._foldIcons.Draw(index, dc,
706 (wndRect.GetWidth() - self._iconWidth)/2,
707 self._rightIndent, wx.IMAGELIST_DRAW_TRANSPARENT)
708
709 ## event.Skip()
710
711
712 def FillCaptionBackground(self, dc):
713 """
714 Fills the background of the caption with either a gradient or
715 a solid color.
716 """
717
718 style = self._style.GetCaptionStyle()
719
720 if style == CAPTIONBAR_GRADIENT_V:
721 if self.IsVertical():
722 self.DrawVerticalGradient(dc, self.GetRect())
723 else:
724 self.DrawHorizontalGradient(dc, self.GetRect())
725
726 elif style == CAPTIONBAR_GRADIENT_H:
727 if self.IsVertical():
728 self.DrawHorizontalGradient(dc, self.GetRect())
729 else:
730 self.DrawVerticalGradient(dc, self.GetRect())
731
732 elif style == CAPTIONBAR_SINGLE:
733 self.DrawSingleColour(dc, self.GetRect())
734 elif style == CAPTIONBAR_RECTANGLE or style == CAPTIONBAR_FILLED_RECTANGLE:
735 self.DrawSingleRectangle(dc, self.GetRect())
736 else:
737 raise "STYLE Error: Undefined Style Selected: " + repr(style)
738
739
740 def OnMouseEvent(self, event):
741 """
742 Catches the mouse click-double click.
743
744 If clicked on the arrow (single) or double on the caption we change state
745 and an event must be fired to let this panel collapse or expand.
746 """
747
748 send_event = False
749 vertical = self.IsVertical()
750
751 if event.LeftDown() and self._foldIcons:
752
753 pt = event.GetPosition()
754 rect = self.GetRect()
755
756 drw = (rect.GetWidth() - self._iconWidth - self._rightIndent)
757 if vertical and pt.x > drw or not vertical and \
758 pt.y < (self._iconHeight + self._rightIndent):
759 send_event = True
760
761 elif event.LeftDClick():
762 self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
763 send_event = True
764
765 elif event.Entering() and self._foldIcons:
766 pt = event.GetPosition()
767 rect = self.GetRect()
768
769 drw = (rect.GetWidth() - self._iconWidth - self._rightIndent)
770 if vertical and pt.x > drw or not vertical and \
771 pt.y < (self._iconHeight + self._rightIndent):
772 self.SetCursor(wx.StockCursor(wx.CURSOR_HAND))
773 else:
774 self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
775
776 elif event.Leaving():
777 self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
778
779 elif event.Moving():
780 pt = event.GetPosition()
781 rect = self.GetRect()
782
783 drw = (rect.GetWidth() - self._iconWidth - self._rightIndent)
784 if vertical and pt.x > drw or not vertical and \
785 pt.y < (self._iconHeight + self._rightIndent):
786 self.SetCursor(wx.StockCursor(wx.CURSOR_HAND))
787 else:
788 self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
789
790 # send the collapse, expand event to the parent
791
792 if send_event:
793 event = CaptionBarEvent(wxEVT_CAPTIONBAR)
794 event.SetId(self.GetId())
795 event.SetEventObject(self)
796 event.SetBar(self)
797 self.GetEventHandler().ProcessEvent(event)
798
799
800 def OnChar(self, event):
801 """ Unused Methods. Any Ideas?!?"""
802 # TODO: Anything here?
803 event.Skip()
804
805
806 def DoGetBestSize(self):
807 """
808 Returns the best size for this panel, based upon the font
809 assigned to this window, and the caption string
810 """
811
812 if self.IsVertical():
813 x, y = self.GetTextExtent(self._caption)
814 else:
815 y, x = self.GetTextExtent(self._caption)
816
817 if x < self._iconWidth:
818 x = self._iconWidth
819
820 if y < self._iconHeight:
821 y = self._iconHeight
822
823 # TODO: The extra FPB_EXTRA_X constants should be adjustable as well
824
825 return wx.Size(x + FPB_EXTRA_X, y + FPB_EXTRA_Y)
826
827
828 def DrawVerticalGradient(self, dc, rect):
829 """ Gradient fill from colour 1 to colour 2 with top to bottom. """
830
831 if rect.height < 1 or rect.width < 1:
832 return
833
834 dc.SetPen(wx.TRANSPARENT_PEN)
835
836 # calculate gradient coefficients
837 col2 = self._style.GetSecondColour()
838 col1 = self._style.GetFirstColour()
839
840 r1, g1, b1 = int(col1.Red()), int(col1.Green()), int(col1.Blue())
841 r2, g2, b2 = int(col2.Red()), int(col2.Green()), int(col2.Blue())
842
843 flrect = float(rect.height)
844
845 rstep = float((r2 - r1)) / flrect
846 gstep = float((g2 - g1)) / flrect
847 bstep = float((b2 - b1)) / flrect
848
849 rf, gf, bf = 0, 0, 0
850
851 for y in range(rect.y, rect.y + rect.height):
852 currCol = (r1 + rf, g1 + gf, b1 + bf)
853
854 dc.SetBrush(wx.Brush(currCol, wx.SOLID))
855 dc.DrawRectangle(rect.x, rect.y + (y - rect.y), rect.width, rect.height)
856 rf = rf + rstep
857 gf = gf + gstep
858 bf = bf + bstep
859
860
861 def DrawHorizontalGradient(self, dc, rect):
862 """ Gradient fill from colour 1 to colour 2 with left to right. """
863
864 if rect.height < 1 or rect.width < 1:
865 return
866
867 dc.SetPen(wx.TRANSPARENT_PEN)
868
869 # calculate gradient coefficients
870 col2 = self._style.GetSecondColour()
871 col1 = self._style.GetFirstColour()
872
873 r1, g1, b1 = int(col1.Red()), int(col1.Green()), int(col1.Blue())
874 r2, g2, b2 = int(col2.Red()), int(col2.Green()), int(col2.Blue())
875
876 flrect = float(rect.width)
877
878 rstep = float((r2 - r1)) / flrect
879 gstep = float((g2 - g1)) / flrect
880 bstep = float((b2 - b1)) / flrect
881
882 rf, gf, bf = 0, 0, 0
883
884 for x in range(rect.x, rect.x + rect.width):
885 currCol = (r1 + rf, g1 + gf, b1 + bf)
886
887 dc.SetBrush(wx.Brush(currCol, wx.SOLID))
888 dc.DrawRectangle(rect.x + (x - rect.x), rect.y, 1, rect.height)
889 rf = rf + rstep
890 gf = gf + gstep
891 bf = bf + bstep
892
893
894 def DrawSingleColour(self, dc, rect):
895 """ Single colour fill. This is the most easy one to find. """
896
897 if rect.height < 1 or rect.width < 1:
898 return
899
900 dc.SetPen(wx.TRANSPARENT_PEN)
901
902 # draw simple rectangle
903 dc.SetBrush(wx.Brush(self._style.GetFirstColour(), wx.SOLID))
904 dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height)
905
906
907 def DrawSingleRectangle(self, dc, rect):
908 """ Single rectangle. This is the most easy one to find. """
909
910 if rect.height < 2 or rect.width < 1:
911 return
912
913 # single frame, set up internal fill colour
914
915 if self._style.GetCaptionStyle() == CAPTIONBAR_RECTANGLE:
916 color = self.GetParent().GetBackgroundColour()
917 br = wx.Brush(color, wx.SOLID)
918 else:
919 color = self._style.GetFirstColour()
920 br = wx.Brush(color, wx.SOLID)
921
922 # setup the pen frame
923
924 pen = wx.Pen(self._style.GetSecondColour())
925 dc.SetPen(pen)
926 dc.SetBrush(br)
927 dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height - 1)
928
929 bgpen = wx.Pen(self.GetParent().GetBackgroundColour())
930 dc.SetPen(bgpen)
931 dc.DrawLine(rect.x, rect.y + rect.height - 1, rect.x + rect.width,
932 rect.y + rect.height - 1)
933
934
935 def OnSize(self, event):
936 """ Handles the size events for the CaptionBar."""
937
938 if not self._controlCreated:
939 event.Skip()
940 return
941
942 size = event.GetSize()
943
944 if self._foldIcons:
945
946 # What I am doing here is simply invalidating the part of the window
947 # exposed. So when I make a rect with as width the newly exposed part,
948 # and the x,y of the old window size origin, I don't need a bitmap
949 # calculation in it, or do I ? The bitmap needs redrawing anyway.
950 # Leave it like this until I figured it out.
951
952 # set rect to redraw as old bitmap area which is entitled to redraw
953
954 rect = wx.Rect(size.GetWidth() - self._iconWidth - self._rightIndent, 0,
955 self._iconWidth + self._rightIndent,
956 self._iconWidth + self._rightIndent)
957
958 # adjust rectangle when more is slided so we need to redraw all
959 # the old stuff but not all (ugly flickering)
960
961 diffX = size.GetWidth() - self._oldSize.GetWidth()
962
963 if diffX > 1:
964
965 # adjust the rect with all the crap to redraw
966
967 rect.SetWidth(rect.GetWidth() + diffX + 10)
968 rect.SetX(rect.GetX() - diffX - 10)
969
970 self.RefreshRect(rect)
971
972 else:
973
974 rect = self.GetRect()
975 self.RefreshRect(rect)
976
977 self._oldSize = size
978
979
980 def RedrawIconBitmap(self):
981 """ Redraws the icons (if they exists). """
982
983 if self._foldIcons:
984
985 # invalidate the bitmap area and force a redraw
986
987 rect = self.GetRect()
988
989 rect.SetX(rect.GetWidth() - self._iconWidth - self._rightIndent)
990 rect.SetWidth(self._iconWidth + self._rightIndent)
991 self.RefreshRect(rect)
992
993
994 # ---------------------------------------------------------------------------------- #
995 # class FoldPanelBar
996 # ---------------------------------------------------------------------------------- #
997
998 class FoldPanelBar(wx.Panel):
999 """
1000 The FoldPanelBar is a class which can maintain a list of
1001 collapsable panels. Once a panel is collapsed, only it's caption
1002 bar is visible to the user. This will provide more space for the
1003 other panels, or allow the user to close panels which are not used
1004 often to get the most out of the work area.
1005
1006 This control is easy to use. Simply create it as a child for a
1007 panel or sash window, and populate panels with
1008 `AddFoldPanel`. Then use the `AddFoldPanelWindow` to add
1009 `wx.Window` derived controls to the current fold panel. Use
1010 `AddFoldPanelSeparator` to put separators between the groups of
1011 controls that need a visual separator to group them
1012 together. After all is constructed, the user can fold the panels
1013 by doubleclicking on the bar or single click on the arrow, which
1014 will indicate the collapsed or expanded state.
1015 """
1016 # Define Empty CaptionBar Style
1017 EmptyCaptionBarStyle = CaptionBarStyle()
1018
1019 def __init__(self, parent, id=-1, pos=wx.DefaultPosition, size=wx.DefaultSize,
1020 style=FPB_DEFAULT_STYLE, extraStyle=FPB_DEFAULT_EXTRASTYLE):
1021 """ Default Class Constructor. """
1022
1023 self._controlCreated = False
1024 self._extraStyle = extraStyle
1025
1026 # make sure there is any orientation
1027 if style & FPB_HORIZONTAL != FPB_HORIZONTAL:
1028 style = style | FPB_VERTICAL
1029
1030 if style & FPB_HORIZONTAL == 4:
1031 self._isVertical = False
1032 else:
1033 self._isVertical = True
1034
1035
1036 # create the panel (duh!). This causes a size event, which we are going
1037 # to skip when we are not initialised
1038
1039 wx.Panel.__init__(self, parent, id, pos, size, style)
1040
1041 # the fold panel area
1042
1043 self._foldPanel = wx.Panel(self, wx.ID_ANY, pos, size,
1044 wx.NO_BORDER | wx.TAB_TRAVERSAL)
1045
1046 self._controlCreated = True
1047 self._panels = []
1048
1049 self.Bind(EVT_CAPTIONBAR, self.OnPressCaption)
1050 self.Bind(wx.EVT_SIZE, self.OnSizePanel)
1051
1052
1053 def AddFoldPanel(self, caption="", collapsed=False, foldIcons=None,
1054 cbstyle=EmptyCaptionBarStyle):
1055 """
1056 Adds a fold panel to the list of panels.
1057
1058 If the flag collapsed is set to True, the panel is collapsed
1059 initially. The FoldPanel item which is returned, can be used
1060 as a reference to perform actions upon the fold panel like
1061 collapsing it, expanding it, or deleting it from the list.
1062
1063 Use this foldpanel to add windows to it. Please consult
1064 `AddFoldPanelWindow` and `AddFoldPanelSeparator` to know how
1065 to add items derived from `wx.Window` to the panels.
1066 """
1067
1068 # create a fold panel item, which is first only the caption.
1069 # the user can now add a panel area which will be folded in
1070 # when pressed.
1071
1072 if foldIcons is None:
1073 foldIcons = wx.ImageList(16, 16)
1074
1075 bmp = GetExpandedIconBitmap()
1076 foldIcons.Add(bmp)
1077 bmp = GetCollapsedIconBitmap()
1078 foldIcons.Add(bmp)
1079
1080 item = FoldPanelItem(self._foldPanel, -1, caption=caption,
1081 foldIcons=foldIcons,
1082 collapsed=collapsed, cbstyle=cbstyle)
1083
1084 pos = 0
1085 if len(self._panels) > 0:
1086 pos = self._panels[-1].GetItemPos() + self._panels[-1].GetPanelLength()
1087
1088 item.Reposition(pos)
1089 self._panels.append(item)
1090
1091 return item
1092
1093
1094 def AddFoldPanelWindow(self, panel, window, flags=FPB_ALIGN_WIDTH,
1095 Spacing=FPB_DEFAULT_SPACING,
1096 leftSpacing=FPB_DEFAULT_LEFTLINESPACING,
1097 rightSpacing=FPB_DEFAULT_RIGHTLINESPACING):
1098 """
1099 Adds a `wx.Window` derived instance to the referenced
1100 FoldPanel.
1101
1102 IMPORTANT: Make the window be a child of the FoldPanel. See
1103 example that follows. The flags to be used are:
1104
1105 * FPB_ALIGN_WIDTH: Which means the wxWindow to be added
1106 will be aligned to fit the width of the FoldPanel when
1107 it is resized. Very handy for sizer items, buttons and
1108 text boxes.
1109
1110 * FPB_ALIGN_LEFT: Aligns left instead of fitting the
1111 width of the child window to be added. Use either this
1112 one or FPB_ALIGN_WIDTH.
1113
1114 The wx.Window to be added can be slightly indented from left
1115 and right so it is more visibly placed in the FoldPanel. Use
1116 Spacing > 0 to give the control an y offset from the previous
1117 wx.Window added, use leftSpacing to give it a slight indent
1118 from the left, and rightSpacing also reserves a little space
1119 on the right so the wxWindow can be properly placed in the
1120 FoldPanel.
1121
1122 The following example adds a FoldPanel to the FoldPanelBar and
1123 adds two wx.Window derived controls to the FoldPanel::
1124
1125 # create the FoldPanelBar
1126 >>> m_pnl = FoldPanelBar(self, wx.ID_ANY, wx.DefaultPosition,
1127 wx.DefaultSize, FPB_DEFAULT_STYLE,
1128 FPB_COLLAPSE_TO_BOTTOM)
1129
1130 # add a foldpanel to the control. "Test me" is the caption and it is
1131 # initially not collapsed.
1132 >>> item = m_pnl.AddFoldPanel("Test me", False)
1133
1134 # now add a button to the fold panel. Mind that the button should be
1135 # made child of the FoldPanel and not of the main form.
1136 >>> m_pnl.AddFoldPanelWindow(item, wx.Button(item, ID_COLLAPSEME,
1137 "Collapse Me"))
1138
1139 # add a separator between the two controls. This is purely a visual
1140 # line that can have a certain color and also the indents and width
1141 # aligning like a control.
1142 >>> m_pnl.AddFoldPanelSeparator(item)
1143
1144 # now add a text ctrl. Also very easy. Align this on width so that
1145 # when the control gets wider the text control also sizes along.
1146 >>> m_pnl.AddFoldPanelWindow(item, wx.TextCtrl(item, wx.ID_ANY, "Comment"),
1147 FPB_ALIGN_WIDTH, FPB_DEFAULT_SPACING, 20)
1148
1149 """
1150
1151 try:
1152 item = self._panels.index(panel)
1153 except:
1154 raise "ERROR: Invalid Panel Passed To AddFoldPanelWindow: " + repr(panel)
1155
1156 panel.AddWindow(window, flags, Spacing, leftSpacing, rightSpacing)
1157
1158 # TODO: Take old and new height, and if difference, reposition all the lower
1159 # panels this is because the user can add new wxWindow controls somewhere in
1160 # between when other panels are already present.
1161
1162 return 0
1163
1164
1165 def AddFoldPanelSeparator(self, panel, colour=wx.BLACK,
1166 Spacing=FPB_DEFAULT_SPACING,
1167 leftSpacing=FPB_DEFAULT_LEFTLINESPACING,
1168 rightSpacing=FPB_DEFAULT_RIGHTLINESPACING):
1169 """
1170 Adds a separator line to the current FoldPanel.
1171
1172 The seperator is a simple line which is drawn and is no real
1173 component. It can be used to separate groups of controls
1174 which belong to each other. The colour is adjustable, and it
1175 takes the same Spacing, leftSpacing and rightSpacing as
1176 `AddFoldPanelWindow`.
1177 """
1178
1179 try:
1180 item = self._panels.index(panel)
1181 except:
1182 raise "ERROR: Invalid Panel Passed To AddFoldPanelSeparator: " + repr(panel)
1183
1184 panel.AddSeparator(colour, Spacing, leftSpacing, rightSpacing)
1185 return 0
1186
1187
1188 def OnSizePanel(self, event):
1189 """ Handles the EVT_SIZE event for the FoldPanelBar. """
1190
1191 # skip all stuff when we are not initialised yet
1192
1193 if not self._controlCreated:
1194 event.Skip()
1195 return
1196
1197 foldrect = self.GetRect()
1198
1199 # fold panel itself. If too little space,
1200 # don't show it
1201
1202 foldrect.SetX(0)
1203 foldrect.SetY(0)
1204
1205 self._foldPanel.SetSize(foldrect[2:])
1206
1207 if self._extraStyle & FPB_COLLAPSE_TO_BOTTOM or self._extraStyle & FPB_EXCLUSIVE_FOLD:
1208 rect = self.RepositionCollapsedToBottom()
1209 vertical = self.IsVertical()
1210 if vertical and rect.GetHeight() > 0 or not vertical and rect.GetWidth() > 0:
1211 self.RefreshRect(rect)
1212
1213 # TODO: A smart way to check wether the old - new width of the
1214 # panel changed, if so no need to resize the fold panel items
1215
1216 self.RedisplayFoldPanelItems()
1217
1218
1219 def OnPressCaption(self, event):
1220 """ Handles the EVT_CAPTIONBAR event in the FoldPanelBar. """
1221
1222 # act upon the folding or expanding status of the bar
1223 # to expand or collapse the panel(s)
1224
1225 if event.GetFoldStatus():
1226 self.Collapse(event.GetTag())
1227 else:
1228 self.Expand(event.GetTag())
1229
1230 event.Skip()
1231
1232
1233 def RefreshPanelsFrom(self, item):
1234 """ Refreshes all the panels from given index down to last one. """
1235
1236 try:
1237 i = self._panels.index(item)
1238 except:
1239 raise "ERROR: Invalid Panel Passed To RefreshPanelsFrom: " + repr(item)
1240
1241 self.Freeze()
1242
1243 # if collapse to bottom is on, the panels that are not expanded
1244 # should be drawn at the bottom. All panels that are expanded
1245 # are drawn on top. The last expanded panel gets all the extra space
1246
1247 if self._extraStyle & FPB_COLLAPSE_TO_BOTTOM or self._extraStyle & FPB_EXCLUSIVE_FOLD:
1248
1249 offset = 0
1250
1251 for panels in self._panels:
1252
1253 if panels.IsExpanded():
1254 offset = offset + panels.Reposition(offset)
1255
1256 # put all non collapsed panels at the bottom where there is space,
1257 # else put them right behind the expanded ones
1258
1259 self.RepositionCollapsedToBottom()
1260
1261 else:
1262
1263 pos = self._panels[i].GetItemPos() + self._panels[i].GetPanelLength()
1264 for j in range(i+1, len(self._panels)):
1265 pos = pos + self._panels[j].Reposition(pos)
1266
1267 self.Thaw()
1268
1269
1270 def RedisplayFoldPanelItems(self):
1271 """ Resizes the fold panels so they match the width. """
1272 # resize them all. No need to reposition
1273 for panels in self._panels:
1274 panels.ResizePanel()
1275 panels.Refresh()
1276
1277
1278 def RepositionCollapsedToBottom(self):
1279 """
1280 Repositions all the collapsed panels to the bottom.
1281
1282 When it is not possible to align them to the bottom, stick
1283 them behind the visible panels. The Rect holds the slack area
1284 left between last repositioned panel and the bottom
1285 panels. This needs to get a refresh.
1286 """
1287
1288 value = wx.Rect(0,0,0,0)
1289 vertical = self.IsVertical()
1290
1291 # determine wether the number of panels left
1292 # times the size of their captions is enough
1293 # to be placed in the left over space
1294
1295 expanded = 0
1296 collapsed = 0
1297 collapsed, expanded, values = self.GetPanelsLength(collapsed, expanded)
1298
1299 # if no room stick them behind the normal ones, else
1300 # at the bottom
1301
1302 if (vertical and [self.GetSize().GetHeight()] or \
1303 [self.GetSize().GetWidth()])[0] - expanded - collapsed < 0:
1304 offset = expanded
1305 else:
1306
1307 # value is the region which is left unpainted
1308 # I will send it back as 'slack' so it does not need to
1309 # be recalculated.
1310
1311 value.SetHeight(self.GetSize().GetHeight())
1312 value.SetWidth(self.GetSize().GetWidth())
1313
1314 if vertical:
1315 value.SetY(expanded)
1316 value.SetHeight(value.GetHeight() - expanded)
1317 else:
1318 value.SetX(expanded)
1319 value.SetWidth(value.GetWidth() - expanded)
1320
1321 offset = (vertical and [self.GetSize().GetHeight()] or \
1322 [self.GetSize().GetWidth()])[0] - collapsed
1323
1324
1325 # go reposition
1326
1327 for panels in self._panels:
1328 if not panels.IsExpanded():
1329 offset = offset + panels.Reposition(offset)
1330
1331 return value
1332
1333
1334 def GetPanelsLength(self, collapsed, expanded):
1335 """
1336 Returns the length of the panels that are expanded and
1337 collapsed.
1338
1339 This is useful to determine quickly what size is used to
1340 display, and what is left at the bottom (right) to align the
1341 collapsed panels.
1342 """
1343
1344 value = 0
1345
1346 # assumed here that all the panels that are expanded
1347 # are positioned after each other from 0,0 to end.
1348
1349 for j in range(0, len(self._panels)):
1350 offset = self._panels[j].GetPanelLength()
1351 value = value + offset
1352 if self._panels[j].IsExpanded():
1353 expanded = expanded + offset
1354 else:
1355 collapsed = collapsed + offset
1356
1357 return collapsed, expanded, value
1358
1359
1360 def Collapse(self, foldpanel):
1361 """
1362 Collapses the given FoldPanel reference, and updates the
1363 foldpanel bar.
1364
1365 In the FPB_COLLAPSE_TO_BOTTOM style, all collapsed captions
1366 are put at the bottom of the control. In the normal mode, they
1367 stay where they are.
1368 """
1369
1370 try:
1371 item = self._panels.index(foldpanel)
1372 except:
1373 raise "ERROR: Invalid Panel Passed To Collapse: " + repr(foldpanel)
1374
1375 foldpanel.Collapse()
1376 self.RefreshPanelsFrom(foldpanel)
1377
1378
1379 def Expand(self, foldpanel):
1380 """
1381 Expands the given FoldPanel reference, and updates the
1382 foldpanel bar.
1383
1384 In the FPB_COLLAPSE_TO_BOTTOM style, they will be removed from
1385 the bottom and the order where the panel originally was placed
1386 is restored.
1387 """
1388
1389 fpbextrastyle = 0
1390
1391 if self._extraStyle & FPB_SINGLE_FOLD or self._extraStyle & FPB_EXCLUSIVE_FOLD:
1392 fpbextrastyle = 1
1393 for panel in self._panels:
1394 panel.Collapse()
1395
1396 foldpanel.Expand()
1397
1398 if fpbextrastyle:
1399 if self._extraStyle & FPB_EXCLUSIVE_FOLD:
1400 self.RepositionCollapsedToBottom()
1401 self.RefreshPanelsFrom(self._panels[0])
1402 else:
1403 self.RefreshPanelsFrom(foldpanel)
1404
1405
1406 def ApplyCaptionStyle(self, foldpanel, cbstyle):
1407 """
1408 Sets the style of the caption bar (`CaptionBar`) of the
1409 FoldPanel.
1410
1411 The changes are applied immediately. All styles not set in the
1412 CaptionBarStyle class are not applied. Use the CaptionBar
1413 reference to indicate what captionbar you want to apply the
1414 style to. To apply one style to all CaptionBar items, use
1415 `ApplyCaptionStyleAll`
1416 """
1417 foldpanel.ApplyCaptionStyle(cbstyle)
1418
1419
1420 def ApplyCaptionStyleAll(self, cbstyle):
1421 """
1422 Sets the style of all the caption bars of the FoldPanel.
1423
1424 The changes are applied immediately.
1425 """
1426 for panels in self._panels:
1427 self.ApplyCaptionStyle(panels, cbstyle)
1428
1429
1430 def GetCaptionStyle(self, foldpanel):
1431 """
1432 Returns the currently used caption style for the FoldPanel.
1433
1434 It is returned as a CaptionBarStyle class. After modifying it,
1435 it can be set again.
1436 """
1437 return foldpanel.GetCaptionStyle()
1438
1439
1440 def IsVertical(self):
1441 """
1442 Returns whether the CaptionBar has default orientation or not.
1443
1444 Default is vertical.
1445 """
1446 return self._isVertical
1447
1448
1449 def GetFoldPanel(self, item):
1450 """
1451 Returns the panel associated with the index "item".
1452
1453 See the example at the bottom of the module, especially the events
1454 for the "Collapse Me" and "Expand Me" buttons.
1455 """
1456 try:
1457 ind = self._panels[item]
1458 return self._panels[item]
1459 except:
1460 raise "ERROR: List Index Out Of Range Or Bad Item Passed: " + repr(item) + \
1461 ". Item Should Be An Integer Between " + repr(0) + " And " + \
1462 repr(len(self._panels))
1463
1464
1465 def GetCount(self):
1466 """ Returns the number of panels in the FoldPanelBar. """
1467
1468 try:
1469 return len(self._panels)
1470 except:
1471 raise "ERROR: No Panels Have Been Added To FoldPanelBar"
1472
1473
1474
1475 # --------------------------------------------------------------------------------- #
1476 # class FoldPanelItem
1477 # --------------------------------------------------------------------------------- #
1478
1479 class FoldPanelItem(wx.Panel):
1480 """
1481 This class is a child sibling of the `FoldPanelBar` class. It will
1482 contain a `CaptionBar` class for receiving of events, and a the
1483 rest of the area can be populated by a `wx.Panel` derived class.
1484 """
1485 # Define Empty CaptionBar Style
1486 EmptyCaptionBarStyle = CaptionBarStyle()
1487
1488 def __init__(self, parent, id=wx.ID_ANY, caption="", foldIcons=None,
1489 collapsed=False, cbstyle=EmptyCaptionBarStyle):
1490 """ Default Class Constructor. """
1491
1492 wx.Panel.__init__(self, parent, id, wx.Point(0,0), style=wx.CLIP_CHILDREN)
1493 self._controlCreated = False
1494 self._UserSize = 0
1495 self._PanelSize = 0
1496 self._LastInsertPos = 0
1497 self._itemPos = 0
1498 self._userSized = False
1499
1500 if foldIcons is None:
1501 foldIcons = wx.ImageList(16, 16)
1502
1503 bmp = GetExpandedIconBitmap()
1504 foldIcons.Add(bmp)
1505 bmp = GetCollapsedIconBitmap()
1506 foldIcons.Add(bmp)
1507
1508 self._foldIcons = foldIcons
1509
1510 # create the caption bar, in collapsed or expanded state
1511
1512 self._captionBar = CaptionBar(self, wx.ID_ANY, wx.Point(0,0),
1513 size=wx.DefaultSize, caption=caption,
1514 foldIcons=foldIcons, cbstyle=cbstyle)
1515
1516 if collapsed:
1517 self._captionBar.Collapse()
1518
1519 self._controlCreated = True
1520
1521 # make initial size for component, if collapsed, the
1522 # size is determined on the panel height and won't change
1523
1524 size = self._captionBar.GetSize()
1525
1526 self._PanelSize = (self.IsVertical() and [size.GetHeight()] or \
1527 [size.GetWidth()])[0]
1528
1529 self._LastInsertPos = self._PanelSize
1530 self._items = []
1531
1532 self.Bind(EVT_CAPTIONBAR, self.OnPressCaption)
1533 self.Bind(wx.EVT_PAINT, self.OnPaint)
1534
1535
1536 def AddWindow(self, window, flags=FPB_ALIGN_WIDTH, Spacing=FPB_DEFAULT_SPACING,
1537 leftSpacing=FPB_DEFAULT_LEFTLINESPACING,
1538 rightSpacing=FPB_DEFAULT_RIGHTLINESPACING):
1539 """
1540 Adds a window item to the list of items on this panel.
1541
1542 The flags are FPB_ALIGN_LEFT for a non sizing window element,
1543 and FPB_ALIGN_WIDTH for a width aligned item. The Spacing
1544 parameter reserves a number of pixels before the window
1545 element, and leftSpacing is an indent. rightSpacing is only
1546 relevant when the style FPB_ALIGN_WIDTH is chosen.
1547 """
1548
1549 wi = FoldWindowItem(self, window, Type="WINDOW", flags=flags, Spacing=Spacing,
1550 leftSpacing=leftSpacing, rightSpacing=rightSpacing)
1551
1552 self._items.append(wi)
1553
1554 vertical = self.IsVertical()
1555
1556 self._Spacing = Spacing
1557 self._leftSpacing = leftSpacing
1558 self._rightSpacing = rightSpacing
1559
1560 xpos = (vertical and [leftSpacing] or [self._LastInsertPos + Spacing])[0]
1561 ypos = (vertical and [self._LastInsertPos + Spacing] or [leftSpacing])[0]
1562
1563 window.SetDimensions(xpos, ypos, -1, -1, wx.SIZE_USE_EXISTING)
1564
1565 self._LastInsertPos = self._LastInsertPos + wi.GetWindowLength(vertical)
1566 self.ResizePanel()
1567
1568
1569 def AddSeparator(self, colour=wx.BLACK, Spacing=FPB_DEFAULT_SPACING,
1570 leftSpacing=FPB_DEFAULT_LEFTSPACING,
1571 rightSpacing=FPB_DEFAULT_RIGHTSPACING):
1572 """
1573 Adds a separator item to the list of items on this panel. """
1574
1575 wi = FoldWindowItem(self, window=None, Type="SEPARATOR",
1576 flags=FPB_ALIGN_WIDTH, y=self._LastInsertPos,
1577 colour=colour, Spacing=Spacing, leftSpacing=leftSpacing,
1578 rightSpacing=rightSpacing)
1579
1580 self._items.append(wi)
1581 self._LastInsertPos = self._LastInsertPos + \
1582 wi.GetWindowLength(self.IsVertical())
1583
1584 self.ResizePanel()
1585
1586
1587 def Reposition(self, pos):
1588 """
1589 Repositions this FoldPanelBar and reports the length occupied
1590 for the next FoldPanelBar in the list.
1591 """
1592 # NOTE: Call Resize before Reposition when an item is added, because the new
1593 # size needed will be calculated by Resize. Of course the relative position
1594 # of the controls have to be correct in respect to the caption bar
1595
1596 self.Freeze()
1597
1598 vertical = self.IsVertical()
1599 xpos = (vertical and [-1] or [pos])[0]
1600 ypos = (vertical and [pos] or [-1])[0]
1601
1602 self.SetDimensions(xpos, ypos, -1, -1, wx.SIZE_USE_EXISTING)
1603 self._itemPos = pos
1604
1605 self.Thaw()
1606
1607 return self.GetPanelLength()
1608
1609
1610 def OnPressCaption(self, event):
1611 """ Handles the EVT_CAPTIONBAR event in the FoldPanelItem. """
1612
1613 # tell the upper container we are responsible
1614 # for this event, so it can fold the panel item
1615 # and do a refresh
1616
1617 event.SetTag(self)
1618 event.Skip()
1619
1620
1621 def ResizePanel(self):
1622 """ Resizes the panel. """
1623
1624 # prevent unnecessary updates by blocking repaints for a sec
1625
1626 self.Freeze()
1627
1628 vertical = self.IsVertical()
1629 # force this panel to take the width of the parent panel and the y of the
1630 # user or calculated width (which will be recalculated by the contents here)
1631
1632
1633 if self._captionBar.IsCollapsed():
1634 size = self._captionBar.GetSize()
1635 self._PanelSize = (vertical and [size.GetHeight()] or [size.GetWidth()])[0]
1636 else:
1637 size = self.GetBestSize()
1638 self._PanelSize = (vertical and [size.GetHeight()] or [size.GetWidth()])[0]
1639
1640 if self._UserSize:
1641 if vertical:
1642 size.SetHeight(self._UserSize)
1643 else:
1644 size.SetWidth(self._UserSize)
1645
1646 pnlsize = self.GetParent().GetSize()
1647
1648 if vertical:
1649 size.SetWidth(pnlsize.GetWidth())
1650 else:
1651 size.SetHeight(pnlsize.GetHeight())
1652
1653 # resize caption bar
1654 xsize = (vertical and [size.GetWidth()] or [-1])[0]
1655 ysize = (vertical and [-1] or [size.GetHeight()])[0]
1656
1657 self._captionBar.SetSize((xsize, ysize))
1658
1659 # resize the panel
1660 self.SetSize(size)
1661
1662 # go by all the controls and call Layout
1663
1664 for items in self._items:
1665 items.ResizeItem((vertical and [size.GetWidth()] or \
1666 [size.GetHeight()])[0], vertical)
1667
1668 self.Thaw()
1669
1670
1671 def OnPaint(self, event):
1672 """ Handles the EVT_PAINT event in the FoldPanelItem. """
1673
1674 # draw all the items that are lines
1675
1676 dc = wx.PaintDC(self)
1677 vertical = self.IsVertical()
1678
1679 for item in self._items:
1680
1681 if item.GetType() == "SEPARATOR":
1682 pen = wx.Pen(item.GetLineColour(), 1, wx.SOLID)
1683 dc.SetPen(pen)
1684 a = item.GetLeftSpacing()
1685 b = item.GetLineY() + item.GetSpacing()
1686 c = item.GetLineLength()
1687 d = a + c
1688
1689 if vertical:
1690 dc.DrawLine(a, b, d, b)
1691 else:
1692 dc.DrawLine(b, a, b, d)
1693
1694 event.Skip()
1695
1696
1697 def IsVertical(self):
1698 """
1699 Returns wether the CaptionBar Has Default Orientation Or Not.
1700
1701 Default is vertical.
1702 """
1703
1704 # grandparent of FoldPanelItem is FoldPanelBar
1705 # default is vertical
1706
1707 if isinstance(self.GetGrandParent(), FoldPanelBar):
1708 return self.GetGrandParent().IsVertical()
1709 else:
1710 raise "ERROR: Wrong Parent " + repr(self.GetGrandParent())
1711
1712
1713 def IsExpanded(self):
1714 """
1715 Returns expanded or collapsed status. If the panel is
1716 expanded, True is returned.
1717 """
1718
1719 return not self._captionBar.IsCollapsed()
1720
1721
1722 def GetItemPos(self):
1723 """ Returns item's position. """
1724
1725 return self._itemPos
1726
1727
1728 def Collapse(self):
1729 # this should not be called by the user, because it doesn't trigger the
1730 # parent to tell it that we are collapsed or expanded, it only changes
1731 # visual state
1732
1733 self._captionBar.Collapse()
1734 self.ResizePanel()
1735
1736
1737 def Expand(self):
1738 # this should not be called by the user, because it doesn't trigger the
1739 # parent to tell it that we are collapsed or expanded, it only changes
1740 # visual state
1741
1742 self._captionBar.Expand()
1743 self.ResizePanel()
1744
1745
1746 def GetPanelLength(self):
1747 """ Returns size of panel. """
1748
1749 if self._captionBar.IsCollapsed():
1750 return self.GetCaptionLength()
1751 elif self._userSized:
1752 return self._UserSize
1753
1754 return self._PanelSize
1755
1756
1757 def GetCaptionLength(self):
1758 """
1759 Returns height of caption only. This is for folding
1760 calculation purposes.
1761 """
1762
1763 size = self._captionBar.GetSize()
1764 return (self.IsVertical() and [size.GetHeight()] or [size.GetWidth()])[0]
1765
1766
1767 def ApplyCaptionStyle(self, cbstyle):
1768 """ Applies the style defined in cbstyle to the CaptionBar."""
1769
1770 self._captionBar.SetCaptionStyle(cbstyle)
1771
1772
1773 def GetCaptionStyle(self):
1774 """
1775 Returns the current style of the captionbar in a
1776 CaptionBarStyle class.
1777
1778 This can be used to change and set back the changes.
1779 """
1780
1781 return self._captionBar.GetCaptionStyle()
1782
1783
1784 # ----------------------------------------------------------------------------------- #
1785 # class FoldWindowItem
1786 # ----------------------------------------------------------------------------------- #
1787
1788 class FoldWindowItem:
1789 """
1790 This class is a child sibling of the `FoldPanelItem` class. It
1791 will contain wx.Window that can be either a separator (a colored
1792 line simulated by a wx.Window) or a wxPython controls (such as a
1793 wx.Button, a wx.ListCtrl etc...).
1794 """
1795 def __init__(self, parent, window=None, **kw):
1796 """
1797 Default Class Constructor
1798
1799 Initialize with::
1800
1801 Type = "WINDOW", flags = FPB_ALIGN_WIDTH,
1802 Spacing = FPB_DEFAULT_SPACING,
1803 leftSpacing = FPB_DEFAULT_LEFTSPACING,
1804 rightSpacing = FPB_DEFAULT_RIGHTSPACING
1805
1806 or::
1807
1808 Type = "SEPARATOR"
1809 y, lineColor = wx.BLACK,
1810 flags = FPB_ALIGN_WIDTH,
1811 Spacing = FPB_DEFAULT_SPACING,
1812 leftSpacing = FPB_DEFAULT_LEFTLINESPACING,
1813 rightSpacing = FPB_DEFAULT_RIGHTLINESPACING
1814 """
1815
1816
1817 if not kw.has_key("Type"):
1818 raise 'ERROR: Missing Window Type Information. This Should Be "WINDOW" Or "SEPARATOR"'
1819
1820 if kw.get("Type") == "WINDOW":
1821 # Window constructor. This initialises the class as a wx.Window Type
1822
1823 if kw.has_key("flags"):
1824 self._flags = kw.get("flags")
1825 else:
1826 self._flags = FPB_ALIGN_WIDTH
1827 if kw.has_key("Spacing"):
1828 self._Spacing = kw.get("Spacing")
1829 else:
1830 self._Spacing = FPB_DEFAULT_SPACING
1831 if kw.has_key("leftSpacing"):
1832 self._leftSpacing = kw.get("leftSpacing")
1833 else:
1834 self._leftSpacing = FPB_DEFAULT_LEFTSPACING
1835 if kw.has_key("rightSpacing"):
1836 self._rightSpacing = kw.get("rightSpacing")
1837 else:
1838 self._rightSpacing = FPB_DEFAULT_RIGHTSPACING
1839
1840 self._lineY = 0
1841 self._sepLineColour = None
1842 self._wnd = window
1843
1844
1845 elif kw.get("Type") == "SEPARATOR":
1846 # separator constructor. This initialises the class as a separator type
1847
1848 if kw.has_key("y"):
1849 self._lineY = kw.get("y")
1850 else:
1851 raise "ERROR: Undefined Y Position For The Separator"
1852 if kw.has_key("lineColour"):
1853 self._sepLineColour = kw.get("lineColour")
1854 else:
1855 self._sepLineColour = wx.BLACK
1856 if kw.has_key("flags"):
1857 self._flags = kw.get("flags")
1858 else:
1859 self._flags = FPB_ALIGN_WIDTH
1860 if kw.has_key("Spacing"):
1861 self._Spacing = kw.get("Spacing")
1862 else:
1863 self._Spacing = FPB_DEFAULT_SPACING
1864 if kw.has_key("leftSpacing"):
1865 self._leftSpacing = kw.get("leftSpacing")
1866 else:
1867 self._leftSpacing = FPB_DEFAULT_LEFTSPACING
1868 if kw.has_key("rightSpacing"):
1869 self._rightSpacing = kw.get("rightSpacing")
1870 else:
1871 self._rightSpacing = FPB_DEFAULT_RIGHTSPACING
1872
1873 self._wnd = window
1874
1875 else:
1876 raise "ERROR: Undefined Window Type Selected: " + repr(kw.get("Type"))
1877
1878 self._type = kw.get("Type")
1879 self._lineLength = 0
1880
1881
1882 def GetType(self):
1883 return self._type
1884
1885 def GetLineY(self):
1886 return self._lineY
1887
1888 def GetLineLength(self):
1889 return self._lineLength
1890
1891 def GetLineColour(self):
1892 return self._sepLineColour
1893
1894 def GetLeftSpacing(self):
1895 return self._leftSpacing
1896
1897 def GetRightSpacing(self):
1898 return self._rightSpacing
1899
1900 def GetSpacing(self):
1901 return self._Spacing
1902
1903
1904 def GetWindowLength(self, vertical=True):
1905 """
1906 Returns space needed by the window if type is FoldWindowItem
1907 "WINDOW" and returns the total size plus the extra spacing.
1908 """
1909
1910 value = 0
1911 if self._type == "WINDOW":
1912 size = self._wnd.GetSize()
1913 value = (vertical and [size.GetHeight()] or [size.GetWidth()])[0] + \
1914 self._Spacing
1915
1916 elif self._type == "SEPARATOR":
1917 value = 1 + self._Spacing
1918
1919 return value
1920
1921
1922 def ResizeItem(self, size, vertical=True):
1923 """
1924 Resizes the element, whatever it is.
1925
1926 A separator or line will be always aligned by width or height
1927 depending on orientation of the whole panel.
1928 """
1929
1930 if self._flags & FPB_ALIGN_WIDTH:
1931 # align by taking full width
1932 mySize = size - self._leftSpacing - self._rightSpacing
1933
1934 if mySize < 0:
1935 mySize = 10 # can't have negative width
1936
1937 if self._type == "SEPARATOR":
1938 self._lineLength = mySize
1939 else:
1940 xsize = (vertical and [mySize] or [-1])[0]
1941 ysize = (vertical and [-1] or [mySize])[0]
1942
1943 self._wnd.SetSize((xsize, ysize))
1944