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