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