]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/flatnotebook.py
update to latest from Andrea
[wxWidgets.git] / wxPython / wx / lib / flatnotebook.py
1 # --------------------------------------------------------------------------- #
2 # FLATNOTEBOOK Widget wxPython IMPLEMENTATION
3 #
4 # Original C++ Code From Eran. You Can Find It At:
5 #
6 # http://wxforum.shadonet.com/viewtopic.php?t=5761&start=0
7 #
8 # License: wxWidgets license
9 #
10 #
11 # Python Code By:
12 #
13 # Andrea Gavana, @ 02 Oct 2006
14 # Latest Revision: 05 Oct 2006, 20.00 GMT
15 #
16 #
17 # For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please
18 # Write To Me At:
19 #
20 # andrea.gavana@gmail.com
21 # gavana@kpo.kz
22 #
23 # Or, Obviously, To The wxPython Mailing List!!!
24 #
25 #
26 # End Of Comments
27 # --------------------------------------------------------------------------- #
28
29 """
30 The FlatNotebook is a full implementation of the wx.Notebook, and designed to be
31 a drop-in replacement for wx.Notebook. The API functions are similar so one can
32 expect the function to behave in the same way.
33
34 Some features:
35 - The buttons are highlighted a la Firefox style
36 - The scrolling is done for bulks of tabs (so, the scrolling is faster and better)
37 - The buttons area is never overdrawn by tabs (unlike many other implementations I saw)
38 - It is a generic control
39 - Currently there are 4 differnt styles - VC8, VC 71, Standard and Fancy.
40 - Mouse middle click can be used to close tabs
41 - A function to add right click menu for tabs (simple as SetRightClickMenu)
42 - All styles has bottom style as well (they can be drawn in the bottom of screen)
43 - An option to hide 'X' button or navigation buttons (separately)
44 - Gradient coloring of the selected tabs and border
45 - Support for drag 'n' drop of tabs, both in the same notebook or to another notebook
46 - Possibility to have closing button on the active tab directly
47 - Support for disabled tabs
48 - Colours for active/inactive tabs, and captions
49 - Background of tab area can be painted in gradient (VC8 style only)
50 - Colourful tabs - a random gentle colour is generated for each new tab (very cool,
51 VC8 style only)
52
53 And much more.
54
55
56 Usage:
57
58 The following example shows a simple implementation that uses FlatNotebook inside
59 a very simple frame::
60
61 import wx
62 import wx.lib.flatnotebook as FNB
63
64 class MyFrame(wx.Frame):
65
66 def __init__(self, parent, id=wx.ID_ANY, title="", pos=wx.DefaultPosition, size=(800, 600),
67 style=wx.DEFAULT_FRAME_STYLE | wx.MAXIMIZE |wx.NO_FULL_REPAINT_ON_RESIZE):
68
69 wx.Frame.__init__(self, parent, id, title, pos, size, style)
70
71 mainSizer = wx.BoxSizer(wx.VERTICAL)
72 self.SetSizer(mainSizer)
73
74 bookStyle = FNB.FNB_TABS_BORDER_SIMPLE
75
76 self.book = FNB.StyledNotebook(self, wx.ID_ANY, style=bookStyle)
77 mainSizer.Add(self.book, 1, wx.EXPAND)
78
79 # Add some pages to the notebook
80 self.Freeze()
81
82 text = wx.TextCtrl(self.book, -1, "Book Page 1", style=wx.TE_MULTILINE)
83 self.book.AddPage(text, "Book Page 1")
84
85 text = wx.TextCtrl(self.book, -1, "Book Page 2", style=wx.TE_MULTILINE)
86 self.book.AddPage(text, "Book Page 2")
87
88 self.Thaw()
89
90 mainSizer.Layout()
91 self.SendSizeEvent()
92
93 # our normal wxApp-derived class, as usual
94
95 app = wx.PySimpleApp()
96
97 frame = MyFrame(None)
98 app.SetTopWindow(frame)
99 frame.Show()
100
101 app.MainLoop()
102
103
104 License And Version:
105
106 FlatNotebook Is Freeware And Distributed Under The wxPython License.
107
108 Latest Revision: Andrea Gavana @ 05 Oct 2006, 20.00 GMT
109 Version 0.4.
110
111 """
112
113 import wx
114 import random
115 import math
116 import weakref
117 import cPickle
118
119 # Check for the new method in 2.7 (not present in 2.6.3.3)
120 if wx.VERSION_STRING < "2.7":
121 wx.Rect.Contains = lambda self, point: wx.Rect.Inside(self, point)
122
123 FNB_HEIGHT_SPACER = 10
124
125 # Use Visual Studio 2003 (VC7.1) Style for tabs
126 FNB_VC71 = 1
127
128 # Use fancy style - square tabs filled with gradient coloring
129 FNB_FANCY_TABS = 2
130
131 # Draw thin border around the page
132 FNB_TABS_BORDER_SIMPLE = 4
133
134 # Do not display the 'X' button
135 FNB_NO_X_BUTTON = 8
136
137 # Do not display the Right / Left arrows
138 FNB_NO_NAV_BUTTONS = 16
139
140 # Use the mouse middle button for cloing tabs
141 FNB_MOUSE_MIDDLE_CLOSES_TABS = 32
142
143 # Place tabs at bottom - the default is to place them
144 # at top
145 FNB_BOTTOM = 64
146
147 # Disable dragging of tabs
148 FNB_NODRAG = 128
149
150 # Use Visual Studio 2005 (VC8) Style for tabs
151 FNB_VC8 = 256
152
153 # Place 'X' on a tab
154 # Note: This style is not supported on VC8 style
155 FNB_X_ON_TAB = 512
156
157 FNB_BACKGROUND_GRADIENT = 1024
158
159 FNB_COLORFUL_TABS = 2048
160
161 # Style to close tab using double click - styles 1024, 2048 are reserved
162 FNB_DCLICK_CLOSES_TABS = 4096
163
164 VERTICAL_BORDER_PADDING = 4
165
166 # Button size is a 16x16 xpm bitmap
167 BUTTON_SPACE = 16
168
169 VC8_SHAPE_LEN = 16
170
171 MASK_COLOR = wx.Color(0, 128, 128)
172
173 # Button status
174 FNB_BTN_PRESSED = 2
175 FNB_BTN_HOVER = 1
176 FNB_BTN_NONE = 0
177
178
179 # Hit Test results
180 FNB_TAB = 1 # On a tab
181 FNB_X = 2 # On the X button
182 FNB_TAB_X = 3 # On the 'X' button (tab's X button)
183 FNB_LEFT_ARROW = 4 # On the rotate left arrow button
184 FNB_RIGHT_ARROW = 5 # On the rotate right arrow button
185 FNB_NOWHERE = 0 # Anywhere else
186
187 FNB_DEFAULT_STYLE = FNB_MOUSE_MIDDLE_CLOSES_TABS
188
189 # FlatNotebook Events:
190 # wxEVT_FLATNOTEBOOK_PAGE_CHANGED: Event Fired When You Switch Page;
191 # wxEVT_FLATNOTEBOOK_PAGE_CHANGING: Event Fired When You Are About To Switch
192 # Pages, But You Can Still "Veto" The Page Changing By Avoiding To Call
193 # event.Skip() In Your Event Handler;
194 # wxEVT_FLATNOTEBOOK_PAGE_CLOSING: Event Fired When A Page Is Closing, But
195 # You Can Still "Veto" The Page Changing By Avoiding To Call event.Skip()
196 # In Your Event Handler;
197 # wxEVT_FLATNOTEBOOK_PAGE_CLOSED: Event Fired When A Page Is Closed.
198 # wxEVT_FLATNOTEBOOK_PAGE_CONTEXT_MENU: Event Fired When A Menu Pops-up In A Tab.
199
200 wxEVT_FLATNOTEBOOK_PAGE_CHANGED = wx.NewEventType()
201 wxEVT_FLATNOTEBOOK_PAGE_CHANGING = wx.NewEventType()
202 wxEVT_FLATNOTEBOOK_PAGE_CLOSING = wx.NewEventType()
203 wxEVT_FLATNOTEBOOK_PAGE_CLOSED = wx.NewEventType()
204 wxEVT_FLATNOTEBOOK_PAGE_CONTEXT_MENU = wx.NewEventType()
205
206 #-----------------------------------#
207 # FlatNotebookEvent
208 #-----------------------------------#
209
210 EVT_FLATNOTEBOOK_PAGE_CHANGED = wx.PyEventBinder(wxEVT_FLATNOTEBOOK_PAGE_CHANGED, 1)
211 EVT_FLATNOTEBOOK_PAGE_CHANGING = wx.PyEventBinder(wxEVT_FLATNOTEBOOK_PAGE_CHANGING, 1)
212 EVT_FLATNOTEBOOK_PAGE_CLOSING = wx.PyEventBinder(wxEVT_FLATNOTEBOOK_PAGE_CLOSING, 1)
213 EVT_FLATNOTEBOOK_PAGE_CLOSED = wx.PyEventBinder(wxEVT_FLATNOTEBOOK_PAGE_CLOSED, 1)
214 EVT_FLATNOTEBOOK_PAGE_CONTEXT_MENU = wx.PyEventBinder(wxEVT_FLATNOTEBOOK_PAGE_CONTEXT_MENU, 1)
215
216 # Some icons in XPM format
217
218 left_arrow_disabled_xpm = [
219 " 16 16 8 1",
220 "` c #008080",
221 ". c #555555",
222 "# c #000000",
223 "a c #000000",
224 "b c #000000",
225 "c c #000000",
226 "d c #000000",
227 "e c #000000",
228 "````````````````",
229 "````````````````",
230 "````````````````",
231 "````````.```````",
232 "```````..```````",
233 "``````.`.```````",
234 "`````.``.```````",
235 "````.```.```````",
236 "`````.``.```````",
237 "``````.`.```````",
238 "```````..```````",
239 "````````.```````",
240 "````````````````",
241 "````````````````",
242 "````````````````",
243 "````````````````"
244 ]
245
246 x_button_pressed_xpm = [
247 " 16 16 8 1",
248 "` c #008080",
249 ". c #4766e0",
250 "# c #9e9ede",
251 "a c #000000",
252 "b c #000000",
253 "c c #000000",
254 "d c #000000",
255 "e c #000000",
256 "````````````````",
257 "`..............`",
258 "`.############.`",
259 "`.############.`",
260 "`.############.`",
261 "`.###aa####aa#.`",
262 "`.####aa##aa##.`",
263 "`.#####aaaa###.`",
264 "`.######aa####.`",
265 "`.#####aaaa###.`",
266 "`.####aa##aa##.`",
267 "`.###aa####aa#.`",
268 "`.############.`",
269 "`..............`",
270 "````````````````",
271 "````````````````"
272 ]
273
274
275 left_arrow_xpm = [
276 " 16 16 8 1",
277 "` c #008080",
278 ". c #555555",
279 "# c #000000",
280 "a c #000000",
281 "b c #000000",
282 "c c #000000",
283 "d c #000000",
284 "e c #000000",
285 "````````````````",
286 "````````````````",
287 "````````````````",
288 "````````.```````",
289 "```````..```````",
290 "``````...```````",
291 "`````....```````",
292 "````.....```````",
293 "`````....```````",
294 "``````...```````",
295 "```````..```````",
296 "````````.```````",
297 "````````````````",
298 "````````````````",
299 "````````````````",
300 "````````````````"
301 ]
302
303 x_button_hilite_xpm = [
304 " 16 16 8 1",
305 "` c #008080",
306 ". c #4766e0",
307 "# c #c9dafb",
308 "a c #000000",
309 "b c #000000",
310 "c c #000000",
311 "d c #000000",
312 "e c #000000",
313 "````````````````",
314 "`..............`",
315 "`.############.`",
316 "`.############.`",
317 "`.##aa####aa##.`",
318 "`.###aa##aa###.`",
319 "`.####aaaa####.`",
320 "`.#####aa#####.`",
321 "`.####aaaa####.`",
322 "`.###aa##aa###.`",
323 "`.##aa####aa##.`",
324 "`.############.`",
325 "`.############.`",
326 "`..............`",
327 "````````````````",
328 "````````````````"
329 ]
330
331 x_button_xpm = [
332 " 16 16 8 1",
333 "` c #008080",
334 ". c #555555",
335 "# c #000000",
336 "a c #000000",
337 "b c #000000",
338 "c c #000000",
339 "d c #000000",
340 "e c #000000",
341 "````````````````",
342 "````````````````",
343 "````````````````",
344 "````````````````",
345 "````..````..````",
346 "`````..``..`````",
347 "``````....``````",
348 "```````..```````",
349 "``````....``````",
350 "`````..``..`````",
351 "````..````..````",
352 "````````````````",
353 "````````````````",
354 "````````````````",
355 "````````````````",
356 "````````````````"
357 ]
358
359 left_arrow_pressed_xpm = [
360 " 16 16 8 1",
361 "` c #008080",
362 ". c #4766e0",
363 "# c #9e9ede",
364 "a c #000000",
365 "b c #000000",
366 "c c #000000",
367 "d c #000000",
368 "e c #000000",
369 "````````````````",
370 "`..............`",
371 "`.############.`",
372 "`.############.`",
373 "`.#######a####.`",
374 "`.######aa####.`",
375 "`.#####aaa####.`",
376 "`.####aaaa####.`",
377 "`.###aaaaa####.`",
378 "`.####aaaa####.`",
379 "`.#####aaa####.`",
380 "`.######aa####.`",
381 "`.#######a####.`",
382 "`..............`",
383 "````````````````",
384 "````````````````"
385 ]
386
387 left_arrow_hilite_xpm = [
388 " 16 16 8 1",
389 "` c #008080",
390 ". c #4766e0",
391 "# c #c9dafb",
392 "a c #000000",
393 "b c #000000",
394 "c c #000000",
395 "d c #000000",
396 "e c #000000",
397 "````````````````",
398 "`..............`",
399 "`.############.`",
400 "`.######a#####.`",
401 "`.#####aa#####.`",
402 "`.####aaa#####.`",
403 "`.###aaaa#####.`",
404 "`.##aaaaa#####.`",
405 "`.###aaaa#####.`",
406 "`.####aaa#####.`",
407 "`.#####aa#####.`",
408 "`.######a#####.`",
409 "`.############.`",
410 "`..............`",
411 "````````````````",
412 "````````````````"
413 ]
414
415 right_arrow_disabled_xpm = [
416 " 16 16 8 1",
417 "` c #008080",
418 ". c #555555",
419 "# c #000000",
420 "a c #000000",
421 "b c #000000",
422 "c c #000000",
423 "d c #000000",
424 "e c #000000",
425 "````````````````",
426 "````````````````",
427 "````````````````",
428 "```````.````````",
429 "```````..```````",
430 "```````.`.``````",
431 "```````.``.`````",
432 "```````.```.````",
433 "```````.``.`````",
434 "```````.`.``````",
435 "```````..```````",
436 "```````.````````",
437 "````````````````",
438 "````````````````",
439 "````````````````",
440 "````````````````"
441 ]
442
443 right_arrow_hilite_xpm = [
444 " 16 16 8 1",
445 "` c #008080",
446 ". c #4766e0",
447 "# c #c9dafb",
448 "a c #000000",
449 "b c #000000",
450 "c c #000000",
451 "d c #000000",
452 "e c #000000",
453 "````````````````",
454 "`..............`",
455 "`.############.`",
456 "`.####a#######.`",
457 "`.####aa######.`",
458 "`.####aaa#####.`",
459 "`.####aaaa####.`",
460 "`.####aaaaa###.`",
461 "`.####aaaa####.`",
462 "`.####aaa#####.`",
463 "`.####aa######.`",
464 "`.####a#######.`",
465 "`.############.`",
466 "`..............`",
467 "````````````````",
468 "````````````````"
469 ]
470
471 right_arrow_pressed_xpm = [
472 " 16 16 8 1",
473 "` c #008080",
474 ". c #4766e0",
475 "# c #9e9ede",
476 "a c #000000",
477 "b c #000000",
478 "c c #000000",
479 "d c #000000",
480 "e c #000000",
481 "````````````````",
482 "`..............`",
483 "`.############.`",
484 "`.############.`",
485 "`.#####a######.`",
486 "`.#####aa#####.`",
487 "`.#####aaa####.`",
488 "`.#####aaaa###.`",
489 "`.#####aaaaa##.`",
490 "`.#####aaaa###.`",
491 "`.#####aaa####.`",
492 "`.#####aa#####.`",
493 "`.#####a######.`",
494 "`..............`",
495 "````````````````",
496 "````````````````"
497 ]
498
499
500 right_arrow_xpm = [
501 " 16 16 8 1",
502 "` c #008080",
503 ". c #555555",
504 "# c #000000",
505 "a c #000000",
506 "b c #000000",
507 "c c #000000",
508 "d c #000000",
509 "e c #000000",
510 "````````````````",
511 "````````````````",
512 "````````````````",
513 "```````.````````",
514 "```````..```````",
515 "```````...``````",
516 "```````....`````",
517 "```````.....````",
518 "```````....`````",
519 "```````...``````",
520 "```````..```````",
521 "```````.````````",
522 "````````````````",
523 "````````````````",
524 "````````````````",
525 "````````````````"
526 ]
527
528
529
530 def LightColour(color, percent):
531 """ Brighten input colour by percent. """
532
533 end_color = wx.WHITE
534
535 rd = end_color.Red() - color.Red()
536 gd = end_color.Green() - color.Green()
537 bd = end_color.Blue() - color.Blue()
538
539 high = 100
540
541 # We take the percent way of the color from color -. white
542 i = percent
543 r = color.Red() + ((i*rd*100)/high)/100
544 g = color.Green() + ((i*gd*100)/high)/100
545 b = color.Blue() + ((i*bd*100)/high)/100
546 return wx.Color(r, g, b)
547
548
549 def PaintStraightGradientBox(dc, rect, startColor, endColor, vertical=True):
550 """ Draws a gradient colored box from startColor to endColor. """
551
552 rd = endColor.Red() - startColor.Red()
553 gd = endColor.Green() - startColor.Green()
554 bd = endColor.Blue() - startColor.Blue()
555
556 # Save the current pen and brush
557 savedPen = dc.GetPen()
558 savedBrush = dc.GetBrush()
559
560 if vertical:
561 high = rect.GetHeight()-1
562 else:
563 high = rect.GetWidth()-1
564
565 if high < 1:
566 return
567
568 for i in xrange(high+1):
569
570 r = startColor.Red() + ((i*rd*100)/high)/100
571 g = startColor.Green() + ((i*gd*100)/high)/100
572 b = startColor.Blue() + ((i*bd*100)/high)/100
573
574 p = wx.Pen(wx.Color(r, g, b))
575 dc.SetPen(p)
576
577 if vertical:
578 dc.DrawLine(rect.x, rect.y+i, rect.x+rect.width, rect.y+i)
579 else:
580 dc.DrawLine(rect.x+i, rect.y, rect.x+i, rect.y+rect.height)
581
582 # Restore the pen and brush
583 dc.SetPen(savedPen)
584 dc.SetBrush(savedBrush)
585
586
587 def RandomColor():
588 """ Creates a random colour. """
589
590 r = random.randint(0, 255) # Random value betweem 0-255
591 g = random.randint(0, 255) # Random value betweem 0-255
592 b = random.randint(0, 255) # Random value betweem 0-255
593
594 return wx.Color(r, g, b)
595
596
597 # ---------------------------------------------------------------------------- #
598 # Class FNBDragInfo
599 # Stores All The Information To Allow Drag And Drop Between Different
600 # FlatNotebooks.
601 # ---------------------------------------------------------------------------- #
602
603 class FNBDragInfo:
604
605 _map = weakref.WeakValueDictionary()
606
607 def __init__(self, container, pageindex):
608 """ Default class constructor. """
609
610 self._id = id(container)
611 FNBDragInfo._map[self._id] = container
612 self._pageindex = pageindex
613
614
615 def GetContainer(self):
616 """ Returns the FlatNotebook page (usually a panel). """
617
618 return FNBDragInfo._map.get(self._id, None)
619
620
621 def GetPageIndex(self):
622 """ Returns the page index associated with a page. """
623
624 return self._pageindex
625
626
627 # ---------------------------------------------------------------------------- #
628 # Class FNBDropTarget
629 # Simply Used To Handle The OnDrop() Method When Dragging And Dropping Between
630 # Different FlatNotebooks.
631 # ---------------------------------------------------------------------------- #
632
633 class FNBDropTarget(wx.DropTarget):
634
635 def __init__(self, parent):
636 """ Default class constructor. """
637
638 wx.DropTarget.__init__(self)
639
640 self._parent = parent
641 self._dataobject = wx.CustomDataObject(wx.CustomDataFormat("FlatNotebook"))
642 self.SetDataObject(self._dataobject)
643
644
645 def OnData(self, x, y, dragres):
646 """ Handles the OnData() method to call the real DnD routine. """
647
648 if not self.GetData():
649 return wx.DragNone
650
651 draginfo = self._dataobject.GetData()
652 drginfo = cPickle.loads(draginfo)
653
654 return self._parent.OnDropTarget(x, y, drginfo.GetPageIndex(), drginfo.GetContainer())
655
656
657 # ---------------------------------------------------------------------------- #
658 # Class PageInfo
659 # Contains parameters for every FlatNotebook page
660 # ---------------------------------------------------------------------------- #
661
662 class PageInfo:
663
664 def __init__(self, caption="", imageindex=-1, tabangle=0, enabled=True):
665 """
666 Default Class Constructor.
667
668 Parameters:
669 - caption: the tab caption;
670 - imageindex: the tab image index based on the assigned (set) wx.ImageList (if any);
671 - tabangle: the tab angle (only on standard tabs, from 0 to 15 degrees);
672 - enabled: sets enabled or disabled the tab.
673 """
674
675 self._strCaption = caption
676 self._TabAngle = tabangle
677 self._ImageIndex = imageindex
678 self._bEnabled = enabled
679 self._pos = wx.Point(-1, -1)
680 self._size = wx.Size(-1, -1)
681 self._region = wx.Region()
682 self._xRect = wx.Rect()
683 self._color = None
684
685
686 def SetCaption(self, value):
687 """ Sets the tab caption. """
688
689 self._strCaption = value
690
691
692 def GetCaption(self):
693 """ Returns the tab caption. """
694
695 return self._strCaption
696
697
698 def SetPosition(self, value):
699 """ Sets the tab position. """
700
701 self._pos = value
702
703
704 def GetPosition(self):
705 """ Returns the tab position. """
706
707 return self._pos
708
709
710 def SetSize(self, value):
711 """ Sets the tab size. """
712
713 self._size = value
714
715
716 def GetSize(self):
717 """ Returns the tab size. """
718
719 return self._size
720
721
722 def SetTabAngle(self, value):
723 """ Sets the tab header angle (0 <= tab <= 15 degrees). """
724
725 self._TabAngle = min(45, value)
726
727
728 def GetTabAngle(self):
729 """ Returns the tab angle. """
730
731 return self._TabAngle
732
733
734 def SetImageIndex(self, value):
735 """ Sets the tab image index. """
736
737 self._ImageIndex = value
738
739
740 def GetImageIndex(self):
741 """ Returns the tab umage index. """
742
743 return self._ImageIndex
744
745
746 def GetEnabled(self):
747 """ Returns whether the tab is enabled or not. """
748
749 return self._bEnabled
750
751
752 def Enable(self, enabled):
753 """ Sets the tab enabled or disabled. """
754
755 self._bEnabled = enabled
756
757
758 def SetRegion(self, points=[]):
759 """ Sets the tab region. """
760
761 self._region = wx.RegionFromPoints(points)
762
763
764 def GetRegion(self):
765 """ Returns the tab region. """
766
767 return self._region
768
769
770 def SetXRect(self, xrect):
771 """ Sets the button 'X' area rect. """
772
773 self._xRect = xrect
774
775
776 def GetXRect(self):
777 """ Returns the button 'X' area rect. """
778
779 return self._xRect
780
781
782 def GetColor(self):
783 """ Returns the tab colour. """
784
785 return self._color
786
787
788 def SetColor(self, color):
789 """ Sets the tab colour. """
790
791 self._color = color
792
793
794 # ---------------------------------------------------------------------------- #
795 # Class FlatNotebookEvent
796 # ---------------------------------------------------------------------------- #
797
798 class FlatNotebookEvent(wx.PyCommandEvent):
799 """
800 This events will be sent when a EVT_FLATNOTEBOOK_PAGE_CHANGED,
801 EVT_FLATNOTEBOOK_PAGE_CHANGING And EVT_FLATNOTEBOOK_PAGE_CLOSING is mapped in
802 the parent.
803 """
804
805 def __init__(self, eventType, id=1, nSel=-1, nOldSel=-1):
806 """ Default class constructor. """
807
808 wx.PyCommandEvent.__init__(self, eventType, id)
809 self._eventType = eventType
810
811 self.notify = wx.NotifyEvent(eventType, id)
812
813
814 def GetNotifyEvent(self):
815 """Returns the actual wx.NotifyEvent."""
816
817 return self.notify
818
819
820 def IsAllowed(self):
821 """Returns whether the event is allowed or not."""
822
823 return self.notify.IsAllowed()
824
825
826 def Veto(self):
827 """Vetos the event."""
828
829 self.notify.Veto()
830
831
832 def Allow(self):
833 """The event is allowed."""
834
835 self.notify.Allow()
836
837
838 def SetSelection(self, nSel):
839 """ Sets event selection. """
840
841 self._selection = nSel
842
843
844 def SetOldSelection(self, nOldSel):
845 """ Sets old event selection. """
846
847 self._oldselection = nOldSel
848
849
850 def GetSelection(self):
851 """ Returns event selection. """
852
853 return self._selection
854
855
856 def GetOldSelection(self):
857 """ Returns old event selection """
858
859 return self._oldselection
860
861
862 # ---------------------------------------------------------------------------- #
863 # Class FlatNotebookBase
864 # ---------------------------------------------------------------------------- #
865
866 class FlatNotebookBase(wx.Panel):
867
868 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
869 style=0, name="FlatNotebook"):
870 """
871 Default class constructor.
872
873 All the parameters are as in wxPython class construction, except the
874 'style': this can be assigned to whatever combination of FNB_* styles.
875 """
876
877 self._bForceSelection = False
878 self._nPadding = 6
879 self._nFrom = 0
880 style |= wx.TAB_TRAVERSAL
881 self._pages = None
882 self._windows = []
883
884 wx.Panel.__init__(self, parent, id, pos, size, style)
885
886 self._pages = StyledTabsContainer(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, style)
887
888 self.Bind(wx.EVT_NAVIGATION_KEY, self.OnNavigationKey)
889
890 self._pages._colorBorder = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW)
891
892 self._mainSizer = wx.BoxSizer(wx.VERTICAL)
893 self.SetSizer(self._mainSizer)
894
895 # The child panels will inherit this bg color, so leave it at the default value
896 #self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_APPWORKSPACE))
897
898 # Add the tab container to the sizer
899 self._mainSizer.Insert(0, self._pages, 0, wx.EXPAND)
900
901 # Set default page height
902 dc = wx.ClientDC(self)
903 font = self.GetFont()
904 font.SetWeight(wx.FONTWEIGHT_BOLD)
905 dc.SetFont(font)
906 height = dc.GetCharHeight()
907 ##print height, font.Ok()
908
909 tabHeight = height + FNB_HEIGHT_SPACER # We use 8 pixels as padding
910 self._pages.SetSizeHints(-1, tabHeight)
911
912 self._mainSizer.Layout()
913
914 self._pages._nFrom = self._nFrom
915 self._pDropTarget = FNBDropTarget(self)
916 self.SetDropTarget(self._pDropTarget)
917
918
919 def CreatePageContainer(self):
920 """ Creates the page container for the tabs. """
921
922 return PageContainerBase(self, wx.ID_ANY)
923
924
925 def SetActiveTabTextColour(self, textColour):
926 """ Sets the text colour for the active tab. """
927
928 self._pages._activeTextColor = textColour
929
930
931 def OnDropTarget(self, x, y, nTabPage, wnd_oldContainer):
932 """ Handles the drop action from a DND operation. """
933
934 return self._pages.OnDropTarget(x, y, nTabPage, wnd_oldContainer)
935
936
937 def AddPage(self, window, caption, selected=True, imgindex=-1):
938 """
939 Add a page to the FlatNotebook.
940
941 Parameters:
942 - window: Specifies the new page.
943 - caption: Specifies the text for the new page.
944 - selected: Specifies whether the page should be selected.
945 - imgindex: Specifies the optional image index for the new page.
946
947 Return value:
948 True if successful, False otherwise.
949 """
950
951 # sanity check
952 if not window:
953 return False
954
955 # reparent the window to us
956 window.Reparent(self)
957
958 # Add tab
959 bSelected = selected or not self._windows
960 curSel = self._pages.GetSelection()
961
962 if not self._pages.IsShown():
963 self._pages.Show()
964
965 self._pages.AddPage(caption, bSelected, imgindex)
966 self._windows.append(window)
967
968 self.Freeze()
969
970 # Check if a new selection was made
971 if bSelected:
972
973 if curSel >= 0:
974
975 # Remove the window from the main sizer
976 self._mainSizer.Detach(self._windows[curSel])
977 self._windows[curSel].Hide()
978
979 if self.GetWindowStyleFlag() & FNB_BOTTOM:
980
981 self._mainSizer.Insert(0, window, 1, wx.EXPAND)
982
983 else:
984
985 # We leave a space of 1 pixel around the window
986 self._mainSizer.Add(window, 1, wx.EXPAND)
987
988 else:
989
990 # Hide the page
991 window.Hide()
992
993 self._mainSizer.Layout()
994 self.Thaw()
995 self.Refresh()
996
997 return True
998
999
1000 def SetImageList(self, imglist):
1001 """
1002 Sets the image list for the page control. It does not take ownership
1003 of the image list, you must delete it yourself.
1004 """
1005
1006 self._pages.SetImageList(imglist)
1007
1008
1009 def GetImageList(self):
1010 """ Returns the associated image list. """
1011
1012 return self._pages.GetImageList()
1013
1014
1015 def InsertPage(self, indx, page, text, select=True, imgindex=-1):
1016 """
1017 Inserts a new page at the specified position.
1018
1019 Parameters:
1020 - indx: Specifies the position of the new page.
1021 - page: Specifies the new page.
1022 - text: Specifies the text for the new page.
1023 - select: Specifies whether the page should be selected.
1024 - imgindex: Specifies the optional image index for the new page.
1025
1026 Return value:
1027 True if successful, False otherwise.
1028 """
1029
1030 # sanity check
1031 if not page:
1032 return False
1033
1034 # reparent the window to us
1035 page.Reparent(self)
1036
1037 if not self._windows:
1038
1039 self.AddPage(page, text, select, imgindex)
1040 return True
1041
1042 # Insert tab
1043 bSelected = select or not self._windows
1044 curSel = self._pages.GetSelection()
1045
1046 indx = max(0, min(indx, len(self._windows)))
1047
1048 if indx <= len(self._windows):
1049
1050 self._windows.insert(indx, page)
1051
1052 else:
1053
1054 self._windows.append(page)
1055
1056 self._pages.InsertPage(indx, text, bSelected, imgindex)
1057
1058 if indx <= curSel:
1059 curSel = curSel + 1
1060
1061 self.Freeze()
1062
1063 # Check if a new selection was made
1064 if bSelected:
1065
1066 if curSel >= 0:
1067
1068 # Remove the window from the main sizer
1069 self._mainSizer.Detach(self._windows[curSel])
1070 self._windows[curSel].Hide()
1071
1072 self._pages.SetSelection(indx)
1073
1074 else:
1075
1076 # Hide the page
1077 page.Hide()
1078
1079 self.Thaw()
1080 self._mainSizer.Layout()
1081 self.Refresh()
1082
1083 return True
1084
1085
1086 def SetSelection(self, page):
1087 """
1088 Sets the selection for the given page.
1089 The call to this function generates the page changing events
1090 """
1091
1092 if page >= len(self._windows) or not self._windows:
1093 return
1094
1095 # Support for disabed tabs
1096 if not self._pages.GetEnabled(page) and len(self._windows) > 1 and not self._bForceSelection:
1097 return
1098
1099 curSel = self._pages.GetSelection()
1100
1101 # program allows the page change
1102 self.Freeze()
1103 if curSel >= 0:
1104
1105 # Remove the window from the main sizer
1106 self._mainSizer.Detach(self._windows[curSel])
1107 self._windows[curSel].Hide()
1108
1109 if self.GetWindowStyleFlag() & FNB_BOTTOM:
1110
1111 self._mainSizer.Insert(0, self._windows[page], 1, wx.EXPAND)
1112
1113 else:
1114
1115 # We leave a space of 1 pixel around the window
1116 self._mainSizer.Add(self._windows[page], 1, wx.EXPAND)
1117
1118 self._windows[page].Show()
1119
1120 self._mainSizer.Layout()
1121 self._pages._iActivePage = page
1122
1123 self.Thaw()
1124
1125 self._pages.DoSetSelection(page)
1126
1127
1128 def DeletePage(self, page):
1129 """
1130 Deletes the specified page, and the associated window.
1131 The call to this function generates the page changing events.
1132 """
1133
1134 if page >= len(self._windows):
1135 return
1136
1137 # Fire a closing event
1138 event = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CLOSING, self.GetId())
1139 event.SetSelection(page)
1140 event.SetEventObject(self)
1141 self.GetEventHandler().ProcessEvent(event)
1142
1143 # The event handler allows it?
1144 if not event.IsAllowed():
1145 return
1146
1147 self.Freeze()
1148
1149 # Delete the requested page
1150 pageRemoved = self._windows[page]
1151
1152 # If the page is the current window, remove it from the sizer
1153 # as well
1154 if page == self._pages.GetSelection():
1155 self._mainSizer.Detach(pageRemoved)
1156
1157 # Remove it from the array as well
1158 self._windows.pop(page)
1159
1160 # Now we can destroy it in wxWidgets use Destroy instead of delete
1161 pageRemoved.Destroy()
1162
1163 self.Thaw()
1164
1165 self._pages.DoDeletePage(page)
1166 self.Refresh()
1167
1168 # Fire a closed event
1169 closedEvent = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CLOSED, self.GetId())
1170 closedEvent.SetSelection(page)
1171 closedEvent.SetEventObject(self)
1172 self.GetEventHandler().ProcessEvent(closedEvent)
1173
1174
1175 def DeleteAllPages(self):
1176 """ Deletes all the pages. """
1177
1178 if not self._windows:
1179 return False
1180
1181 self.Freeze()
1182
1183 for page in self._windows:
1184 page.Destroy()
1185
1186 self._windows = []
1187 self.Thaw()
1188
1189 # Clear the container of the tabs as well
1190 self._pages.DeleteAllPages()
1191 return True
1192
1193
1194 def GetCurrentPage(self):
1195 """ Returns the currently selected notebook page or None. """
1196
1197 sel = self._pages.GetSelection()
1198 if sel < 0:
1199 return None
1200
1201 return self._windows[sel]
1202
1203
1204 def GetPage(self, page):
1205 """ Returns the window at the given page position, or None. """
1206
1207 if page >= len(self._windows):
1208 return None
1209
1210 return self._windows[page]
1211
1212
1213 def GetPageIndex(self, win):
1214 """ Returns the index at which the window is found. """
1215
1216 try:
1217 return self._windows.index(win)
1218 except:
1219 return -1
1220
1221
1222 def GetSelection(self):
1223 """ Returns the currently selected page, or -1 if none was selected. """
1224
1225 return self._pages.GetSelection()
1226
1227
1228 def AdvanceSelection(self, bForward=True):
1229 """
1230 Cycles through the tabs.
1231 The call to this function generates the page changing events.
1232 """
1233
1234 self._pages.AdvanceSelection(bForward)
1235
1236
1237 def GetPageCount(self):
1238 """ Returns the number of pages in the FlatNotebook control. """
1239 return self._pages.GetPageCount()
1240
1241
1242 def OnNavigationKey(self, event):
1243 """ Handles the wx.EVT_NAVIGATION_KEY event for FlatNotebook. """
1244
1245 if event.IsWindowChange():
1246 # change pages
1247 self.AdvanceSelection(event.GetDirection())
1248 else:
1249 # pass to the parent
1250 if self.GetParent():
1251 event.SetCurrentFocus(self)
1252 self.GetParent().ProcessEvent(event)
1253
1254
1255 def GetPageShapeAngle(self, page_index):
1256 """ Returns the angle associated to a tab. """
1257
1258 if page_index < 0 or page_index >= len(self._pages._pagesInfoVec):
1259 return None, False
1260
1261 result = self._pages._pagesInfoVec[page_index].GetTabAngle()
1262 return result, True
1263
1264
1265 def SetPageShapeAngle(self, page_index, angle):
1266 """ Sets the angle associated to a tab. """
1267
1268 if page_index < 0 or page_index >= len(self._pages._pagesInfoVec):
1269 return
1270
1271 if angle > 15:
1272 return
1273
1274 self._pages._pagesInfoVec[page_index].SetTabAngle(angle)
1275
1276
1277 def SetAllPagesShapeAngle(self, angle):
1278 """ Sets the angle associated to all the tab. """
1279
1280 if angle > 15:
1281 return
1282
1283 for ii in xrange(len(self._pages._pagesInfoVec)):
1284 self._pages._pagesInfoVec[ii].SetTabAngle(angle)
1285
1286 self.Refresh()
1287
1288
1289 def GetPageBestSize(self):
1290 """ Return the page best size. """
1291
1292 return self._pages.GetClientSize()
1293
1294
1295 def SetPageText(self, page, text):
1296 """ Sets the text for the given page. """
1297
1298 bVal = self._pages.SetPageText(page, text)
1299 self._pages.Refresh()
1300
1301 return bVal
1302
1303
1304 def SetPadding(self, padding):
1305 """
1306 Sets the amount of space around each page's icon and label, in pixels.
1307 NB: only the horizontal padding is considered.
1308 """
1309
1310 self._nPadding = padding.GetWidth()
1311
1312
1313 def GetTabArea(self):
1314 """ Returns the associated page. """
1315
1316 return self._pages
1317
1318
1319 def GetPadding(self):
1320 """ Returns the amount of space around each page's icon and label, in pixels. """
1321
1322 return self._nPadding
1323
1324
1325 def SetWindowStyleFlag(self, style):
1326 """ Sets the FlatNotebook window style flags. """
1327
1328 wx.Panel.SetWindowStyleFlag(self, style)
1329
1330 if self._pages:
1331
1332 # For changing the tab position (i.e. placing them top/bottom)
1333 # refreshing the tab container is not enough
1334 self.SetSelection(self._pages._iActivePage)
1335
1336
1337 def RemovePage(self, page):
1338 """ Deletes the specified page, without deleting the associated window. """
1339
1340 if page >= len(self._windows):
1341 return False
1342
1343 # Fire a closing event
1344 event = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CLOSING, self.GetId())
1345 event.SetSelection(page)
1346 event.SetEventObject(self)
1347 self.GetEventHandler().ProcessEvent(event)
1348
1349 # The event handler allows it?
1350 if not event.IsAllowed():
1351 return False
1352
1353 self.Freeze()
1354
1355 # Remove the requested page
1356 pageRemoved = self._windows[page]
1357
1358 # If the page is the current window, remove it from the sizer
1359 # as well
1360 if page == self._pages.GetSelection():
1361 self._mainSizer.Detach(pageRemoved)
1362
1363 # Remove it from the array as well
1364 self._windows.pop(page)
1365 self.Thaw()
1366
1367 self._pages.DoDeletePage(page)
1368
1369 return True
1370
1371
1372 def SetRightClickMenu(self, menu):
1373 """ Sets the popup menu associated to a right click on a tab. """
1374
1375 self._pages._pRightClickMenu = menu
1376
1377
1378 def GetPageText(self, page):
1379 """ Returns the tab caption. """
1380
1381 return self._pages.GetPageText(page)
1382
1383
1384 def SetGradientColors(self, fr, to, border):
1385 """ Sets the gradient colours for the tab. """
1386
1387 self._pages._colorFrom = fr
1388 self._pages._colorTo = to
1389 self._pages._colorBorder = border
1390
1391
1392 def SetGradientColorFrom(self, fr):
1393 """ Sets the starting colour for the gradient. """
1394
1395 self._pages._colorFrom = fr
1396
1397
1398 def SetGradientColorTo(self, to):
1399 """ Sets the ending colour for the gradient. """
1400
1401 self._pages._colorTo = to
1402
1403
1404 def SetGradientColorBorder(self, border):
1405 """ Sets the tab border colour. """
1406
1407 self._pages._colorBorder = border
1408
1409
1410 def GetGradientColorFrom(self):
1411 """ Gets first gradient colour. """
1412
1413 return self._pages._colorFrom
1414
1415
1416 def GetGradientColorTo(self):
1417 """ Gets second gradient colour. """
1418
1419 return self._pages._colorTo
1420
1421
1422 def GetGradientColorBorder(self):
1423 """ Gets the tab border colour. """
1424
1425 return self._pages._colorBorder
1426
1427
1428 def GetActiveTabTextColour(self):
1429 """ Get the active tab text colour. """
1430
1431 return self._pages._activeTextColor
1432
1433
1434 def SetPageImage(self, page, imgindex):
1435 """
1436 Sets the image index for the given page. Image is an index into the
1437 image list which was set with SetImageList.
1438 """
1439
1440 self._pages.SetPageImage(page, imgindex)
1441
1442
1443 def GetPageImageIndex(self, page):
1444 """
1445 Returns the image index for the given page. Image is an index into the
1446 image list which was set with SetImageList.
1447 """
1448
1449 return self._pages.GetPageImageIndex(page)
1450
1451
1452 def GetEnabled(self, page):
1453 """ Returns whether a tab is enabled or not. """
1454
1455 return self._pages.GetEnabled(page)
1456
1457
1458 def Enable(self, page, enabled=True):
1459 """ Enables or disables a tab. """
1460
1461 if page >= len(self._windows):
1462 return
1463
1464 self._windows[page].Enable(enabled)
1465 self._pages.Enable(page, enabled)
1466
1467
1468 def GetNonActiveTabTextColour(self):
1469 """ Returns the non active tabs text colour. """
1470
1471 return self._pages._nonActiveTextColor
1472
1473
1474 def SetNonActiveTabTextColour(self, color):
1475 """ Sets the non active tabs text colour. """
1476
1477 self._pages._nonActiveTextColor = color
1478
1479
1480 def SetTabAreaColour(self, color):
1481 """ Sets the area behind the tabs colour. """
1482
1483 self._pages._tabAreaColor = color
1484
1485
1486 def GetTabAreaColour(self):
1487 """ Returns the area behind the tabs colour. """
1488
1489 return self._pages._tabAreaColor
1490
1491
1492 def SetActiveTabColour(self, color):
1493 """ Sets the active tab colour. """
1494
1495 self._pages._activeTabColor = color
1496
1497
1498 def GetActiveTabColour(self):
1499 """ Returns the active tab colour. """
1500
1501 return self._pages._activeTabColor
1502
1503
1504 # ---------------------------------------------------------------------------- #
1505 # Class PageContainerBase
1506 # Acts as a container for the pages you add to FlatNotebook
1507 # ---------------------------------------------------------------------------- #
1508
1509 class PageContainerBase(wx.Panel):
1510
1511 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
1512 size=wx.DefaultSize, style=0):
1513 """ Default class constructor. """
1514
1515 self._ImageList = None
1516 self._iActivePage = -1
1517 self._pDropTarget = None
1518 self._nLeftClickZone = FNB_NOWHERE
1519 self._tabXBgBmp = wx.EmptyBitmap(16, 16)
1520 self._xBgBmp = wx.EmptyBitmap(16, 14)
1521 self._leftBgBmp = wx.EmptyBitmap(16, 14)
1522 self._rightBgBmp = wx.EmptyBitmap(16, 14)
1523
1524 self._pRightClickMenu = None
1525 self._nXButtonStatus = FNB_BTN_NONE
1526 self._pParent = parent
1527 self._nRightButtonStatus = FNB_BTN_NONE
1528 self._nLeftButtonStatus = FNB_BTN_NONE
1529 self._nTabXButtonStatus = FNB_BTN_NONE
1530
1531 self._pagesInfoVec = []
1532
1533 self._colorTo = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
1534 self._colorFrom = wx.WHITE
1535 self._activeTabColor = wx.WHITE
1536 self._activeTextColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNTEXT)
1537 self._nonActiveTextColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW)
1538 self._tabAreaColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)
1539
1540 self._nFrom = 0
1541 self._isdragging = False
1542
1543 wx.Panel.__init__(self, parent, id, pos, size, style|wx.TAB_TRAVERSAL)
1544
1545 self._pDropTarget = FNBDropTarget(self)
1546 self.SetDropTarget(self._pDropTarget)
1547
1548 self.Bind(wx.EVT_PAINT, self.OnPaint)
1549 self.Bind(wx.EVT_SIZE, self.OnSize)
1550 self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
1551 self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
1552 self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
1553 self.Bind(wx.EVT_MIDDLE_DOWN, self.OnMiddleDown)
1554 self.Bind(wx.EVT_MOTION, self.OnMouseMove)
1555 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
1556 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
1557 self.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnterWindow)
1558 self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
1559
1560
1561 def GetButtonAreaWidth(self):
1562 """ Returns the width of the navigation button area. """
1563
1564 style = self.GetParent().GetWindowStyleFlag()
1565 btnareawidth = 2*self._pParent._nPadding
1566
1567 if style & FNB_NO_X_BUTTON == 0:
1568 btnareawidth += BUTTON_SPACE
1569
1570 if style & FNB_NO_NAV_BUTTONS == 0:
1571 btnareawidth += 2*BUTTON_SPACE
1572
1573 return btnareawidth
1574
1575
1576 def OnEraseBackground(self, event):
1577 """ Handles the wx.EVT_ERASE_BACKGROUND event for PageContainerBase (does nothing)."""
1578
1579 pass
1580
1581
1582 def OnPaint(self, event):
1583 """ Handles the wx.EVT_PAINT event for PageContainerBase."""
1584
1585 dc = wx.BufferedPaintDC(self)
1586
1587 if "__WXMAC__" in wx.PlatformInfo:
1588 # Works well on MSW & GTK, however this lines should be skipped on MAC
1589 if len(self._pagesInfoVec) == 0 or self._nFrom >= len(self._pagesInfoVec):
1590 self.Hide()
1591 event.Skip()
1592 return
1593
1594 # Get the text hight
1595 style = self.GetParent().GetWindowStyleFlag()
1596
1597 # For GTK it seems that we must do this steps in order
1598 # for the tabs will get the proper height on initialization
1599 # on MSW, preforming these steps yields wierd results
1600 normalFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
1601 boldFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
1602 boldFont.SetWeight(wx.FONTWEIGHT_BOLD)
1603 if "__WXGTK__" in wx.PlatformInfo:
1604 dc.SetFont(boldFont)
1605
1606 width, height = dc.GetTextExtent("Tp")
1607
1608 tabHeight = height + FNB_HEIGHT_SPACER # We use 8 pixels as padding
1609
1610 # Calculate the number of rows required for drawing the tabs
1611 rect = self.GetClientRect()
1612 clientWidth = rect.width
1613
1614 # Set the maximum client size
1615 self.SetSizeHints(self.GetButtonsAreaLength(), tabHeight)
1616 borderPen = wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW))
1617
1618 if style & FNB_VC71:
1619 backBrush = wx.Brush(wx.Colour(247, 243, 233))
1620 else:
1621 backBrush = wx.Brush(self._tabAreaColor)
1622
1623 noselBrush = wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
1624 selBrush = wx.Brush(self._activeTabColor)
1625
1626 size = self.GetSize()
1627
1628 # Background
1629 dc.SetTextBackground((style & FNB_VC71 and [wx.Colour(247, 243, 233)] or [self.GetBackgroundColour()])[0])
1630 dc.SetTextForeground(self._activeTextColor)
1631 dc.SetBrush(backBrush)
1632
1633 # If border style is set, set the pen to be border pen
1634 if style & FNB_TABS_BORDER_SIMPLE:
1635 dc.SetPen(borderPen)
1636 else:
1637 pc = (self.HasFlag(FNB_VC71) and [wx.Colour(247, 243, 233)] or [self.GetBackgroundColour()])[0]
1638 dc.SetPen(wx.Pen(pc))
1639
1640 dc.DrawRectangle(0, 0, size.x, size.y)
1641
1642 # Take 3 bitmaps for the background for the buttons
1643
1644 mem_dc = wx.MemoryDC()
1645
1646 #---------------------------------------
1647 # X button
1648 #---------------------------------------
1649 rect = wx.Rect(self.GetXPos(), 6, 16, 14)
1650 mem_dc.SelectObject(self._xBgBmp)
1651 mem_dc.Blit(0, 0, rect.width, rect.height, dc, rect.x, rect.y)
1652 mem_dc.SelectObject(wx.NullBitmap)
1653
1654 #---------------------------------------
1655 # Right button
1656 #---------------------------------------
1657 rect = wx.Rect(self.GetRightButtonPos(), 6, 16, 14)
1658 mem_dc.SelectObject(self._rightBgBmp)
1659 mem_dc.Blit(0, 0, rect.width, rect.height, dc, rect.x, rect.y)
1660 mem_dc.SelectObject(wx.NullBitmap)
1661
1662 #---------------------------------------
1663 # Left button
1664 #---------------------------------------
1665 rect = wx.Rect(self.GetLeftButtonPos(), 6, 16, 14)
1666 mem_dc.SelectObject(self._leftBgBmp)
1667 mem_dc.Blit(0, 0, rect.width, rect.height, dc, rect.x, rect.y)
1668 mem_dc.SelectObject(wx.NullBitmap)
1669
1670 # We always draw the bottom/upper line of the tabs
1671 # regradless the style
1672 dc.SetPen(borderPen)
1673 self.DrawTabsLine(dc)
1674
1675 # Restore the pen
1676 dc.SetPen(borderPen)
1677
1678 if self.HasFlag(FNB_VC71):
1679
1680 greyLineYVal = self.HasFlag((FNB_BOTTOM and [0] or [size.y - 2])[0])
1681 whiteLineYVal = self.HasFlag((FNB_BOTTOM and [3] or [size.y - 3])[0])
1682
1683 pen = wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE))
1684 dc.SetPen(pen)
1685
1686 # Draw thik grey line between the windows area and
1687 # the tab area
1688
1689 for num in xrange(3):
1690 dc.DrawLine(0, greyLineYVal + num, size.x, greyLineYVal + num)
1691
1692 wbPen = wx.Pen((self.HasFlag(FNB_BOTTOM) and [wx.BLACK] or [wx.WHITE])[0])
1693 dc.SetPen(wbPen)
1694 dc.DrawLine(1, whiteLineYVal, size.x - 1, whiteLineYVal)
1695
1696 # Restore the pen
1697 dc.SetPen(borderPen)
1698
1699 if "__WXMAC__" in wx.PlatformInfo:
1700 # On MAC, Add these lines so the tab background gets painted
1701 if len(self._pagesInfoVec) == 0 or self._nFrom >= len(self._pagesInfoVec):
1702 self.Hide()
1703 return
1704
1705 # Draw labels
1706 dc.SetFont(boldFont)
1707 posx = self._pParent.GetPadding()
1708
1709 # Update all the tabs from 0 to '_nFrom' to be non visible
1710 for ii in xrange(self._nFrom):
1711 self._pagesInfoVec[ii].SetPosition(wx.Point(-1, -1))
1712 self._pagesInfoVec[ii].GetRegion().Clear()
1713
1714 if style & FNB_VC71:
1715 tabHeight = ((style & FNB_BOTTOM) and [tabHeight - 4] or [tabHeight])[0]
1716 elif style & FNB_FANCY_TABS:
1717 tabHeight = ((style & FNB_BOTTOM) and [tabHeight - 2] or [tabHeight])[0]
1718
1719 #----------------------------------------------------------
1720 # Go over and draw the visible tabs
1721 #----------------------------------------------------------
1722
1723 count = self._nFrom
1724
1725 for ii in xrange(self._nFrom, len(self._pagesInfoVec)):
1726
1727 if style != FNB_VC71:
1728 shapePoints = int(tabHeight*math.tan(float(self._pagesInfoVec[ii].GetTabAngle())/180.0*math.pi))
1729 else:
1730 shapePoints = 0
1731
1732 dc.SetPen(borderPen)
1733 dc.SetBrush((ii==self.GetSelection() and [selBrush] or [noselBrush])[0])
1734
1735 # Calculate the text length using the bold font, so when selecting a tab
1736 # its width will not change
1737 dc.SetFont(boldFont)
1738 width, pom = dc.GetTextExtent(self.GetPageText(ii))
1739
1740 # Now set the font to the correct font
1741 dc.SetFont((ii==self.GetSelection() and [boldFont] or [normalFont])[0])
1742
1743 # Set a minimum size to a tab
1744 if width < 20:
1745 width = 20
1746
1747 # Add the padding to the tab width
1748 # Tab width:
1749 # +-----------------------------------------------------------+
1750 # | PADDING | IMG | IMG_PADDING | TEXT | PADDING | x |PADDING |
1751 # +-----------------------------------------------------------+
1752
1753 tabWidth = 2*self._pParent._nPadding + width
1754 imageYCoord = (self.HasFlag(FNB_BOTTOM) and [6] or [8])[0]
1755
1756 # Style to add a small 'x' button on the top right
1757 # of the tab
1758 if style & FNB_X_ON_TAB and ii == self.GetSelection():
1759
1760 # The xpm image that contains the 'x' button is 9 pixles
1761 tabWidth += self._pParent._nPadding + 9
1762
1763 if not (style & FNB_VC71) and not (style & FNB_FANCY_TABS):
1764 # Default style
1765 tabWidth += 2*shapePoints
1766
1767 hasImage = self._ImageList != None and self._pagesInfoVec[ii].GetImageIndex() != -1
1768
1769 # For VC71 style, we only add the icon size (16 pixels)
1770 if hasImage:
1771
1772 if style & FNB_VC71 or style & FNB_FANCY_TABS:
1773 tabWidth += 16 + self._pParent._nPadding
1774 else:
1775 # Default style
1776 tabWidth += 16 + self._pParent._nPadding + shapePoints/2
1777
1778 # Check if we can draw more
1779 if posx + tabWidth + self.GetButtonsAreaLength() >= clientWidth:
1780 break
1781
1782 count = count + 1
1783
1784 # By default we clean the tab region
1785 self._pagesInfoVec[ii].GetRegion().Clear()
1786
1787 # Clean the 'x' buttn on the tab
1788 # 'Clean' rectanlge is a rectangle with width or height
1789 # with values lower than or equal to 0
1790 self._pagesInfoVec[ii].GetXRect().SetSize(wx.Size(-1, -1))
1791
1792 # Draw the tab
1793 if style & FNB_FANCY_TABS:
1794 self.DrawFancyTab(dc, posx, ii, tabWidth, tabHeight)
1795 elif style & FNB_VC71:
1796 self.DrawVC71Tab(dc, posx, ii, tabWidth, tabHeight)
1797 else:
1798 self.DrawStandardTab(dc, posx, ii, tabWidth, tabHeight)
1799
1800 # The width of the images are 16 pixels
1801 if hasImage:
1802 textOffset = 2*self._pParent._nPadding + 16 + shapePoints/2
1803 else:
1804 textOffset = self._pParent._nPadding + shapePoints/2
1805
1806 # After several testing, it seems that we can draw
1807 # the text 2 pixles to the right - this is done only
1808 # for the standard tabs
1809
1810 if not self.HasFlag(FNB_FANCY_TABS):
1811 textOffset += 2
1812
1813 if ii != self.GetSelection():
1814 # Set the text background to be like the vertical lines
1815 dc.SetTextForeground(self._nonActiveTextColor)
1816
1817 if hasImage:
1818
1819 imageXOffset = textOffset - 16 - self._pParent._nPadding
1820 self._ImageList.Draw(self._pagesInfoVec[ii].GetImageIndex(), dc,
1821 posx + imageXOffset, imageYCoord,
1822 wx.IMAGELIST_DRAW_TRANSPARENT, True)
1823
1824 dc.DrawText(self.GetPageText(ii), posx + textOffset, imageYCoord)
1825
1826 # From version 1.2 - a style to add 'x' button
1827 # on a tab
1828
1829 if self.HasFlag(FNB_X_ON_TAB) and ii == self.GetSelection():
1830
1831 textWidth, textHeight = dc.GetTextExtent(self.GetPageText(ii))
1832 tabCloseButtonXCoord = posx + textOffset + textWidth + 1
1833
1834 # take a bitmap from the position of the 'x' button (the x on tab button)
1835 # this bitmap will be used later to delete old buttons
1836 tabCloseButtonYCoord = imageYCoord
1837 x_rect = wx.Rect(tabCloseButtonXCoord, tabCloseButtonYCoord, 16, 16)
1838 mem_dc = wx.MemoryDC()
1839 mem_dc.SelectObject(self._tabXBgBmp)
1840 mem_dc.Blit(0, 0, x_rect.width, x_rect.height, dc, x_rect.x, x_rect.y)
1841 mem_dc.SelectObject(wx.NullBitmap)
1842
1843 # Draw the tab
1844 self.DrawTabX(dc, x_rect, ii)
1845
1846 # Restore the text forground
1847 dc.SetTextForeground(self._activeTextColor)
1848
1849 # Update the tab position & size
1850 posy = (style & FNB_BOTTOM and [0] or [VERTICAL_BORDER_PADDING])[0]
1851
1852 self._pagesInfoVec[ii].SetPosition(wx.Point(posx, posy))
1853 self._pagesInfoVec[ii].SetSize(wx.Size(tabWidth, tabHeight))
1854 posx += tabWidth
1855
1856 # Update all tabs that can not fit into the screen as non-visible
1857 for ii in xrange(count, len(self._pagesInfoVec)):
1858
1859 self._pagesInfoVec[ii].SetPosition(wx.Point(-1, -1))
1860 self._pagesInfoVec[ii].GetRegion().Clear()
1861
1862 # Draw the left/right/close buttons
1863 # Left arrow
1864 self.DrawLeftArrow(dc)
1865 self.DrawRightArrow(dc)
1866 self.DrawX(dc)
1867
1868
1869 def DrawFancyTab(self, dc, posx, tabIdx, tabWidth, tabHeight):
1870 """
1871 Fancy tabs - like with VC71 but with the following differences:
1872 - The Selected tab is colored with gradient color
1873 """
1874
1875 borderPen = wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW))
1876 pen = (tabIdx==self.GetSelection() and [wx.Pen(self._colorBorder)] \
1877 or [wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE))])[0]
1878
1879 fnb_bottom = self.HasFlag(FNB_BOTTOM)
1880
1881 if tabIdx == self.GetSelection():
1882
1883 posy = (fnb_bottom and [2] or [VERTICAL_BORDER_PADDING])[0]
1884 th = (fnb_bottom and [tabHeight - 2] or [tabHeight - 5])[0]
1885
1886 rect = wx.Rect(posx, posy, tabWidth, th)
1887 self.FillGradientColor(dc, rect)
1888 dc.SetBrush(wx.TRANSPARENT_BRUSH)
1889 dc.SetPen(pen)
1890 dc.DrawRectangleRect(rect)
1891
1892 # erase the bottom/top line of the rectangle
1893 dc.SetPen(wx.Pen(self._colorFrom))
1894 if fnb_bottom:
1895 dc.DrawLine(rect.x, 2, rect.x + rect.width, 2)
1896 else:
1897 dc.DrawLine(rect.x, rect.y + rect.height - 1, rect.x + rect.width, rect.y + rect.height - 1)
1898
1899 else:
1900
1901 # We dont draw a rectangle for non selected tabs, but only
1902 # vertical line on the left
1903 dc.SetPen(borderPen)
1904 dc.DrawLine(posx + tabWidth, VERTICAL_BORDER_PADDING + 3, posx + tabWidth, tabHeight - 4)
1905
1906
1907 def DrawVC71Tab(self, dc, posx, tabIdx, tabWidth, tabHeight):
1908 """ Draws tabs with VC71 style. """
1909
1910 fnb_bottom = self.HasFlag(FNB_BOTTOM)
1911
1912 # Visual studio 7.1 style
1913 borderPen = wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW))
1914 dc.SetPen((tabIdx==self.GetSelection() and [wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE))] or [borderPen])[0])
1915 dc.SetBrush((tabIdx==self.GetSelection() and [wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE))] or \
1916 [wx.Brush(wx.Colour(247, 243, 233))])[0])
1917
1918 if tabIdx == self.GetSelection():
1919
1920 posy = (fnb_bottom and [0] or [VERTICAL_BORDER_PADDING])[0]
1921 dc.DrawRectangle(posx, posy, tabWidth, tabHeight - 1)
1922
1923 # Draw a black line on the left side of the
1924 # rectangle
1925 dc.SetPen(wx.BLACK_PEN)
1926
1927 blackLineY1 = VERTICAL_BORDER_PADDING
1928 blackLineY2 = (fnb_bottom and [self.GetSize().y - 5] or [self.GetSize().y - 3])[0]
1929 dc.DrawLine(posx + tabWidth, blackLineY1, posx + tabWidth, blackLineY2)
1930
1931 # To give the tab more 3D look we do the following
1932 # Incase the tab is on top,
1933 # Draw a thick white line on top of the rectangle
1934 # Otherwise, draw a thin (1 pixel) black line at the bottom
1935
1936 pen = wx.Pen((fnb_bottom and [wx.BLACK] or [wx.WHITE])[0])
1937 dc.SetPen(pen)
1938 whiteLinePosY = (fnb_bottom and [blackLineY2] or [VERTICAL_BORDER_PADDING])[0]
1939 dc.DrawLine(posx , whiteLinePosY, posx + tabWidth + 1, whiteLinePosY)
1940
1941 # Draw a white vertical line to the left of the tab
1942 dc.SetPen(wx.WHITE_PEN)
1943 if not fnb_bottom:
1944 blackLineY2 += 1
1945
1946 dc.DrawLine(posx, blackLineY1, posx, blackLineY2)
1947
1948 else:
1949
1950 # We dont draw a rectangle for non selected tabs, but only
1951 # vertical line on the left
1952
1953 blackLineY1 = (fnb_bottom and [VERTICAL_BORDER_PADDING + 2] or [VERTICAL_BORDER_PADDING + 1])[0]
1954 blackLineY2 = self.GetSize().y - 5
1955 dc.DrawLine(posx + tabWidth, blackLineY1, posx + tabWidth, blackLineY2)
1956
1957
1958 def DrawStandardTab(self, dc, posx, tabIdx, tabWidth, tabHeight):
1959 """ Draws tabs with standard style. """
1960
1961 fnb_bottom = self.HasFlag(FNB_BOTTOM)
1962
1963 # Default style
1964 borderPen = wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW))
1965
1966 tabPoints = [wx.Point() for ii in xrange(7)]
1967 tabPoints[0].x = posx
1968 tabPoints[0].y = (fnb_bottom and [2] or [tabHeight - 2])[0]
1969
1970 tabPoints[1].x = int(posx+(tabHeight-2)*math.tan(float(self._pagesInfoVec[tabIdx].GetTabAngle())/180.0*math.pi))
1971 tabPoints[1].y = (fnb_bottom and [tabHeight - (VERTICAL_BORDER_PADDING+2)] or [(VERTICAL_BORDER_PADDING+2)])[0]
1972
1973 tabPoints[2].x = tabPoints[1].x+2
1974 tabPoints[2].y = (fnb_bottom and [tabHeight - VERTICAL_BORDER_PADDING] or [VERTICAL_BORDER_PADDING])[0]
1975
1976 tabPoints[3].x = int(posx+tabWidth-(tabHeight-2)*math.tan(float(self._pagesInfoVec[tabIdx].GetTabAngle())/180.0*math.pi))-2
1977 tabPoints[3].y = (fnb_bottom and [tabHeight - VERTICAL_BORDER_PADDING] or [VERTICAL_BORDER_PADDING])[0]
1978
1979 tabPoints[4].x = tabPoints[3].x+2
1980 tabPoints[4].y = (fnb_bottom and [tabHeight - (VERTICAL_BORDER_PADDING+2)] or [(VERTICAL_BORDER_PADDING+2)])[0]
1981
1982 tabPoints[5].x = int(tabPoints[4].x+(tabHeight-2)*math.tan(float(self._pagesInfoVec[tabIdx].GetTabAngle())/180.0*math.pi))
1983 tabPoints[5].y = (fnb_bottom and [2] or [tabHeight - 2])[0]
1984
1985 tabPoints[6].x = tabPoints[0].x
1986 tabPoints[6].y = tabPoints[0].y
1987
1988 if tabIdx == self.GetSelection():
1989
1990 # Draw the tab as rounded rectangle
1991 dc.DrawPolygon(tabPoints)
1992
1993 else:
1994
1995 if tabIdx != self.GetSelection() - 1:
1996
1997 # Draw a vertical line to the right of the text
1998 pt1x = tabPoints[5].x
1999 pt1y = (fnb_bottom and [4] or [tabHeight - 6])[0]
2000 pt2x = tabPoints[5].x
2001 pt2y = (fnb_bottom and [tabHeight - 4] or [4])[0]
2002 dc.DrawLine(pt1x, pt1y, pt2x, pt2y)
2003
2004 if tabIdx == self.GetSelection():
2005
2006 savePen = dc.GetPen()
2007 whitePen = wx.Pen(wx.WHITE)
2008 whitePen.SetWidth(1)
2009 dc.SetPen(whitePen)
2010
2011 secPt = wx.Point(tabPoints[5].x + 1, tabPoints[5].y)
2012 dc.DrawLine(tabPoints[0].x, tabPoints[0].y, secPt.x, secPt.y)
2013
2014 # Restore the pen
2015 dc.SetPen(savePen)
2016
2017
2018 def AddPage(self, caption, selected=True, imgindex=-1):
2019 """
2020 Add a page to the FlatNotebook.
2021
2022 Parameters:
2023 - window: Specifies the new page.
2024 - caption: Specifies the text for the new page.
2025 - selected: Specifies whether the page should be selected.
2026 - imgindex: Specifies the optional image index for the new page.
2027
2028 Return value:
2029 True if successful, False otherwise.
2030 """
2031
2032 if selected:
2033
2034 self._iActivePage = len(self._pagesInfoVec)
2035
2036 # Create page info and add it to the vector
2037 pageInfo = PageInfo(caption, imgindex)
2038 self._pagesInfoVec.append(pageInfo)
2039 self.Refresh()
2040
2041
2042 def InsertPage(self, indx, text, selected=True, imgindex=-1):
2043 """
2044 Inserts a new page at the specified position.
2045
2046 Parameters:
2047 - indx: Specifies the position of the new page.
2048 - page: Specifies the new page.
2049 - text: Specifies the text for the new page.
2050 - select: Specifies whether the page should be selected.
2051 - imgindex: Specifies the optional image index for the new page.
2052
2053 Return value:
2054 True if successful, False otherwise.
2055 """
2056
2057 if selected:
2058
2059 self._iActivePage = len(self._pagesInfoVec)
2060
2061 self._pagesInfoVec.insert(indx, PageInfo(text, imgindex))
2062
2063 self.Refresh()
2064 return True
2065
2066
2067 def OnSize(self, event):
2068 """ Handles the wx.EVT_SIZE events for PageContainerBase. """
2069
2070 self.Refresh() # Call on paint
2071 event.Skip()
2072
2073
2074 def OnMiddleDown(self, event):
2075 """ Handles the wx.EVT_MIDDLE_DOWN events for PageContainerBase. """
2076
2077 # Test if this style is enabled
2078 style = self.GetParent().GetWindowStyleFlag()
2079
2080 if not style & FNB_MOUSE_MIDDLE_CLOSES_TABS:
2081 return
2082
2083 where, tabIdx = self.HitTest(event.GetPosition())
2084
2085 if where == FNB_TAB:
2086 self.DeletePage(tabIdx)
2087
2088 event.Skip()
2089
2090
2091 def OnRightDown(self, event):
2092 """ Handles the wx.EVT_RIGHT_DOWN events for PageContainerBase. """
2093
2094 if self._pRightClickMenu:
2095
2096 where, tabIdx = self.HitTest(event.GetPosition())
2097
2098 if where in [FNB_TAB, FNB_TAB_X]:
2099
2100 if self._pagesInfoVec[tabIdx].GetEnabled():
2101 # Set the current tab to be active
2102 self.SetSelection(tabIdx)
2103
2104 # If the owner has defined a context menu for the tabs,
2105 # popup the right click menu
2106 if self._pRightClickMenu:
2107 self.PopupMenu(self._pRightClickMenu)
2108 else:
2109 # send a message to popup a custom menu
2110 event = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CONTEXT_MENU, self.GetParent().GetId())
2111 event.SetSelection(tabIdx)
2112 event.SetOldSelection(self._iActivePage)
2113 event.SetEventObject(self.GetParent())
2114 self.GetParent().GetEventHandler().ProcessEvent(event)
2115
2116 event.Skip()
2117
2118
2119 def OnLeftDown(self, event):
2120 """ Handles the wx.EVT_LEFT_DOWN events for PageContainerBase. """
2121
2122 # Reset buttons status
2123 self._nXButtonStatus = FNB_BTN_NONE
2124 self._nLeftButtonStatus = FNB_BTN_NONE
2125 self._nRightButtonStatus = FNB_BTN_NONE
2126 self._nTabXButtonStatus = FNB_BTN_NONE
2127
2128 self._nLeftClickZone, tabIdx = self.HitTest(event.GetPosition())
2129
2130 if self._nLeftClickZone == FNB_LEFT_ARROW:
2131 self._nLeftButtonStatus = FNB_BTN_PRESSED
2132 self.Refresh()
2133 elif self._nLeftClickZone == FNB_RIGHT_ARROW:
2134 self._nRightButtonStatus = FNB_BTN_PRESSED
2135 self.Refresh()
2136 elif self._nLeftClickZone == FNB_X:
2137 self._nXButtonStatus = FNB_BTN_PRESSED
2138 self.Refresh()
2139 elif self._nLeftClickZone == FNB_TAB_X:
2140 self._nTabXButtonStatus = FNB_BTN_PRESSED
2141 self.Refresh()
2142
2143 elif self._nLeftClickZone == FNB_TAB:
2144
2145 if self._iActivePage != tabIdx:
2146
2147 # In case the tab is disabled, we dont allow to choose it
2148 if self._pagesInfoVec[tabIdx].GetEnabled():
2149
2150 oldSelection = self._iActivePage
2151
2152 event = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CHANGING, self.GetParent().GetId())
2153 event.SetSelection(tabIdx)
2154 event.SetOldSelection(oldSelection)
2155 event.SetEventObject(self.GetParent())
2156 if not self.GetParent().GetEventHandler().ProcessEvent(event) or event.IsAllowed():
2157
2158 self.SetSelection(tabIdx)
2159
2160 # Fire a wxEVT_TABBEDCTRL_PAGE_CHANGED event
2161 event.SetEventType(wxEVT_FLATNOTEBOOK_PAGE_CHANGED)
2162 event.SetOldSelection(oldSelection)
2163 self.GetParent().GetEventHandler().ProcessEvent(event)
2164
2165
2166 def OnLeftUp(self, event):
2167 """ Handles the wx.EVT_LEFT_UP events for PageContainerBase. """
2168
2169 # forget the zone that was initially clicked
2170 self._nLeftClickZone = FNB_NOWHERE
2171
2172 where, tabIdx = self.HitTest(event.GetPosition())
2173
2174 if where == FNB_LEFT_ARROW:
2175
2176 if self._nFrom == 0:
2177 return
2178
2179 # Make sure that the button was pressed before
2180 if self._nLeftButtonStatus != FNB_BTN_PRESSED:
2181 return
2182
2183 self._nLeftButtonStatus = FNB_BTN_HOVER
2184
2185 # We scroll left with bulks of 5
2186 scrollLeft = self.GetNumTabsCanScrollLeft()
2187
2188 self._nFrom -= scrollLeft
2189 if self._nFrom < 0:
2190 self._nFrom = 0
2191
2192 self.Refresh()
2193
2194 elif where == FNB_RIGHT_ARROW:
2195
2196 if self._nFrom >= len(self._pagesInfoVec) - 1:
2197 return
2198
2199 # Make sure that the button was pressed before
2200 if self._nRightButtonStatus != FNB_BTN_PRESSED:
2201 return
2202
2203 self._nRightButtonStatus = FNB_BTN_HOVER
2204
2205 # Check if the right most tab is visible, if it is
2206 # don't rotate right anymore
2207 if self._pagesInfoVec[-1].GetPosition() != wx.Point(-1, -1):
2208 return
2209
2210 lastVisibleTab = self.GetLastVisibleTab()
2211 if lastVisibleTab < 0:
2212 # Probably the screen is too small for displaying even a single
2213 # tab, in this case we do nothing
2214 return
2215
2216 self._nFrom += self.GetNumOfVisibleTabs()
2217 self.Refresh()
2218
2219 elif where == FNB_X:
2220
2221 # Make sure that the button was pressed before
2222 if self._nXButtonStatus != FNB_BTN_PRESSED:
2223 return
2224
2225 self._nXButtonStatus = FNB_BTN_HOVER
2226
2227 self.DeletePage(self._iActivePage)
2228
2229 elif where == FNB_TAB_X:
2230
2231 # Make sure that the button was pressed before
2232 if self._nTabXButtonStatus != FNB_BTN_PRESSED:
2233 return
2234
2235 self._nTabXButtonStatus = FNB_BTN_HOVER
2236
2237 self.DeletePage(self._iActivePage)
2238
2239
2240 def HitTest(self, pt):
2241 """
2242 HitTest method for PageContainerBase.
2243 Returns the flag (if any) and the hit page (if any).
2244 """
2245
2246 fullrect = self.GetClientRect()
2247 btnLeftPos = self.GetLeftButtonPos()
2248 btnRightPos = self.GetRightButtonPos()
2249 btnXPos = self.GetXPos()
2250 style = self.GetParent().GetWindowStyleFlag()
2251
2252 tabIdx = -1
2253
2254 if not self._pagesInfoVec:
2255 return FNB_NOWHERE, -1
2256
2257 rect = wx.Rect(btnXPos, 6, 16, 16)
2258 if rect.Contains(pt):
2259 return (style & FNB_NO_X_BUTTON and [FNB_NOWHERE] or [FNB_X])[0], -1
2260
2261 rect = wx.Rect(btnRightPos, 6, 16, 16)
2262 if rect.Contains(pt):
2263 return (style & FNB_NO_NAV_BUTTONS and [FNB_NOWHERE] or [FNB_RIGHT_ARROW])[0], -1
2264
2265 rect = wx.Rect(btnLeftPos, 6, 16, 16)
2266 if rect.Contains(pt):
2267 return (style & FNB_NO_NAV_BUTTONS and [FNB_NOWHERE] or [FNB_LEFT_ARROW])[0], -1
2268
2269 # Test whether a left click was made on a tab
2270 for cur in xrange(self._nFrom, len(self._pagesInfoVec)):
2271
2272 pgInfo = self._pagesInfoVec[cur]
2273
2274 if pgInfo.GetPosition() == wx.Point(-1, -1):
2275 continue
2276
2277 if style & FNB_X_ON_TAB and cur == self.GetSelection():
2278 # 'x' button exists on a tab
2279 if self._pagesInfoVec[cur].GetXRect().Contains(pt):
2280 return FNB_TAB_X, cur
2281
2282 tabRect = wx.Rect(pgInfo.GetPosition().x, pgInfo.GetPosition().y, pgInfo.GetSize().x, pgInfo.GetSize().y)
2283 if tabRect.Contains(pt):
2284 # We have a match
2285 return FNB_TAB, cur
2286
2287 if self._isdragging:
2288 # We are doing DND, so check also the region outside the tabs
2289
2290 # try before the first tab
2291 pgInfo = self._pagesInfoVec[0]
2292 tabRect = wx.Rect(0, pgInfo.GetPosition().y, pgInfo.GetPosition().x, self.GetParent().GetSize().y)
2293 if tabRect.Contains(pt):
2294 return FNB_TAB, 0
2295
2296 # try after the last tab
2297 pgInfo = self._pagesInfoVec[-1]
2298 startpos = pgInfo.GetPosition().x+pgInfo.GetSize().x
2299 tabRect = wx.Rect(startpos, pgInfo.GetPosition().y, fullrect.width-startpos, self.GetParent().GetSize().y)
2300
2301 if tabRect.Contains(pt):
2302 return FNB_TAB, len(self._pagesInfoVec)-1
2303
2304 # Default
2305 return FNB_NOWHERE, -1
2306
2307
2308 def SetSelection(self, page):
2309 """ Sets the selected page. """
2310
2311 book = self.GetParent()
2312 book.SetSelection(page)
2313 self.DoSetSelection(page)
2314
2315
2316 def DoSetSelection(self, page):
2317 """ Does the actual selection of a page. """
2318
2319 # Make sure that the selection is visible
2320 style = self.GetParent().GetWindowStyleFlag()
2321 if style & FNB_NO_NAV_BUTTONS:
2322 # Incase that we dont have navigation buttons,
2323 # there is no point of checking if the tab is visible
2324 # Just do the refresh
2325 self.Refresh()
2326 return
2327
2328 if page < len(self._pagesInfoVec):
2329 #! fix for tabfocus
2330 da_page = self._pParent.GetPage(page)
2331
2332 # THIS IS GIVING TROUBLES!!
2333 if da_page != None:
2334 da_page.SetFocus()
2335
2336 if not self.IsTabVisible(page):
2337
2338 if page == len(self._pagesInfoVec) - 1:
2339 # Incase the added tab is last,
2340 # the function IsTabVisible() will always return False
2341 # and thus will cause an evil behaviour that the new
2342 # tab will hide all other tabs, we need to check if the
2343 # new selected tab can fit to the current screen
2344 if not self.CanFitToScreen(page):
2345 self._nFrom = page
2346
2347 else:
2348
2349 if not self.CanFitToScreen(page):
2350 # Redraw the tabs starting from page
2351 self._nFrom = page
2352
2353 self.Refresh()
2354
2355
2356 def DeletePage(self, page):
2357 """ Delete the specified page from FlatNotebook. """
2358
2359 book = self.GetParent()
2360 book.DeletePage(page)
2361 book.Refresh()
2362
2363
2364 def IsTabVisible(self, page):
2365 """ Returns whether a tab is visible or not. """
2366
2367 iLastVisiblePage = self.GetLastVisibleTab()
2368 return page <= iLastVisiblePage and page >= self._nFrom
2369
2370
2371 def DoDeletePage(self, page):
2372 """ Does the actual page deletion. """
2373
2374 # Remove the page from the vector
2375 book = self.GetParent()
2376 self._pagesInfoVec.pop(page)
2377
2378 # Thanks to Yiaanis AKA Mandrav
2379 if self._iActivePage >= page:
2380 self._iActivePage = self._iActivePage - 1
2381
2382 # The delete page was the last first on the array,
2383 # but the book still has more pages, so we set the
2384 # active page to be the first one (0)
2385 if self._iActivePage < 0 and self._pagesInfoVec:
2386 self._iActivePage = 0
2387
2388 # Refresh the tabs
2389 if self._iActivePage >= 0:
2390
2391 book._bForceSelection = True
2392 book.SetSelection(self._iActivePage)
2393 book._bForceSelection = False
2394
2395 if not self._pagesInfoVec:
2396 # Erase the page container drawings
2397 dc = wx.ClientDC(self)
2398 dc.Clear()
2399
2400
2401 def DeleteAllPages(self):
2402 """ Deletes all the pages. """
2403
2404 self._iActivePage = -1
2405 self._nFrom = 0
2406 self._pagesInfoVec = []
2407
2408 # Erase the page container drawings
2409 dc = wx.ClientDC(self)
2410 dc.Clear()
2411
2412
2413 def DrawTabX(self, dc, rect, tabIdx):
2414 """ Draws the 'X' in the selected tab (VC8 style excluded). """
2415
2416 if not self.HasFlag(FNB_X_ON_TAB) or not self.CanDrawXOnTab():
2417 return
2418
2419 # We draw the 'x' on the active tab only
2420 if tabIdx != self.GetSelection() or tabIdx < 0:
2421 return
2422
2423 # Set the bitmap according to the button status
2424 if self._nTabXButtonStatus == FNB_BTN_HOVER:
2425 xBmp = wx.BitmapFromXPMData(x_button_hilite_xpm)
2426 elif self._nTabXButtonStatus == FNB_BTN_PRESSED:
2427 xBmp = wx.BitmapFromXPMData(x_button_pressed_xpm)
2428 else:
2429 xBmp = wx.BitmapFromXPMData(x_button_xpm)
2430
2431 # Set the masking
2432 xBmp.SetMask(wx.Mask(xBmp, MASK_COLOR))
2433
2434 # erase old button
2435 dc.DrawBitmap(self._tabXBgBmp, rect.x, rect.y)
2436
2437 # Draw the new bitmap
2438 dc.DrawBitmap(xBmp, rect.x, rect.y, True)
2439
2440 # Update the vectpr
2441 self._pagesInfoVec[tabIdx].SetXRect(rect)
2442
2443
2444 def DrawLeftArrow(self, dc):
2445 """ Draw the left navigation arrow. """
2446
2447 style = self.GetParent().GetWindowStyleFlag()
2448 if style & FNB_NO_NAV_BUTTONS:
2449 return
2450
2451 # Make sure that there are pages in the container
2452 if not self._pagesInfoVec:
2453 return
2454
2455 # Set the bitmap according to the button status
2456 if self._nLeftButtonStatus == FNB_BTN_HOVER:
2457 arrowBmp = wx.BitmapFromXPMData(left_arrow_hilite_xpm)
2458 elif self._nLeftButtonStatus == FNB_BTN_PRESSED:
2459 arrowBmp = wx.BitmapFromXPMData(left_arrow_pressed_xpm)
2460 else:
2461 arrowBmp = wx.BitmapFromXPMData(left_arrow_xpm)
2462
2463 if self._nFrom == 0:
2464 # Handle disabled arrow
2465 arrowBmp = wx.BitmapFromXPMData(left_arrow_disabled_xpm)
2466
2467 arrowBmp.SetMask(wx.Mask(arrowBmp, MASK_COLOR))
2468
2469 # Erase old bitmap
2470 posx = self.GetLeftButtonPos()
2471 dc.DrawBitmap(self._leftBgBmp, posx, 6)
2472
2473 # Draw the new bitmap
2474 dc.DrawBitmap(arrowBmp, posx, 6, True)
2475
2476
2477 def DrawRightArrow(self, dc):
2478 """ Draw the right navigation arrow. """
2479
2480 style = self.GetParent().GetWindowStyleFlag()
2481 if style & FNB_NO_NAV_BUTTONS:
2482 return
2483
2484 # Make sure that there are pages in the container
2485 if not self._pagesInfoVec:
2486 return
2487
2488 # Set the bitmap according to the button status
2489 if self._nRightButtonStatus == FNB_BTN_HOVER:
2490 arrowBmp = wx.BitmapFromXPMData(right_arrow_hilite_xpm)
2491 elif self._nRightButtonStatus == FNB_BTN_PRESSED:
2492 arrowBmp = wx.BitmapFromXPMData(right_arrow_pressed_xpm)
2493 else:
2494 arrowBmp = wx.BitmapFromXPMData(right_arrow_xpm)
2495
2496 # Check if the right most tab is visible, if it is
2497 # don't rotate right anymore
2498 if self._pagesInfoVec[-1].GetPosition() != wx.Point(-1, -1):
2499 arrowBmp = wx.BitmapFromXPMData(right_arrow_disabled_xpm)
2500
2501 arrowBmp.SetMask(wx.Mask(arrowBmp, MASK_COLOR))
2502
2503 # erase old bitmap
2504 posx = self.GetRightButtonPos()
2505 dc.DrawBitmap(self._rightBgBmp, posx, 6)
2506
2507 # Draw the new bitmap
2508 dc.DrawBitmap(arrowBmp, posx, 6, True)
2509
2510
2511 def DrawX(self, dc):
2512 """ Draw the 'X' navigation button in the navigation area. """
2513
2514 # Check if this style is enabled
2515 style = self.GetParent().GetWindowStyleFlag()
2516 if style & FNB_NO_X_BUTTON:
2517 return
2518
2519 # Make sure that there are pages in the container
2520 if not self._pagesInfoVec:
2521 return
2522
2523 # Set the bitmap according to the button status
2524 if self._nXButtonStatus == FNB_BTN_HOVER:
2525 xbmp = wx.BitmapFromXPMData(x_button_hilite_xpm)
2526 elif self._nXButtonStatus == FNB_BTN_PRESSED:
2527 xbmp = wx.BitmapFromXPMData(x_button_pressed_xpm)
2528 else:
2529 xbmp = wx.BitmapFromXPMData(x_button_xpm)
2530
2531 xbmp.SetMask(wx.Mask(xbmp, MASK_COLOR))
2532 # erase old bitmap
2533
2534 posx = self.GetXPos()
2535 dc.DrawBitmap(self._xBgBmp, posx, 6)
2536
2537 # Draw the new bitmap
2538 dc.DrawBitmap(xbmp, posx, 6, True)
2539
2540
2541 def OnMouseMove(self, event):
2542 """ Handles the wx.EVT_MOTION for PageContainerBase. """
2543
2544 if self._pagesInfoVec and self.IsShown():
2545
2546 xButtonStatus = self._nXButtonStatus
2547 xTabButtonStatus = self._nTabXButtonStatus
2548 rightButtonStatus = self._nRightButtonStatus
2549 leftButtonStatus = self._nLeftButtonStatus
2550 style = self.GetParent().GetWindowStyleFlag()
2551
2552 self._nXButtonStatus = FNB_BTN_NONE
2553 self._nRightButtonStatus = FNB_BTN_NONE
2554 self._nLeftButtonStatus = FNB_BTN_NONE
2555 self._nTabXButtonStatus = FNB_BTN_NONE
2556
2557 where, tabIdx = self.HitTest(event.GetPosition())
2558
2559 if where == FNB_X:
2560 if event.LeftIsDown():
2561
2562 self._nXButtonStatus = (self._nLeftClickZone==FNB_X and [FNB_BTN_PRESSED] or [FNB_BTN_NONE])[0]
2563
2564 else:
2565
2566 self._nXButtonStatus = FNB_BTN_HOVER
2567
2568 elif where == FNB_TAB_X:
2569 if event.LeftIsDown():
2570
2571 self._nTabXButtonStatus = (self._nLeftClickZone==FNB_TAB_X and [FNB_BTN_PRESSED] or [FNB_BTN_NONE])[0]
2572
2573 else:
2574
2575 self._nTabXButtonStatus = FNB_BTN_HOVER
2576
2577 elif where == FNB_RIGHT_ARROW:
2578 if event.LeftIsDown():
2579
2580 self._nRightButtonStatus = (self._nLeftClickZone==FNB_RIGHT_ARROW and [FNB_BTN_PRESSED] or [FNB_BTN_NONE])[0]
2581
2582 else:
2583
2584 self._nRightButtonStatus = FNB_BTN_HOVER
2585
2586 elif where == FNB_LEFT_ARROW:
2587 if event.LeftIsDown():
2588
2589 self._nLeftButtonStatus = (self._nLeftClickZone==FNB_LEFT_ARROW and [FNB_BTN_PRESSED] or [FNB_BTN_NONE])[0]
2590
2591 else:
2592
2593 self._nLeftButtonStatus = FNB_BTN_HOVER
2594
2595 elif where == FNB_TAB:
2596 # Call virtual method for showing tooltip
2597 self.ShowTabTooltip(tabIdx)
2598
2599 if not self.GetEnabled(tabIdx):
2600 # Set the cursor to be 'No-entry'
2601 wx.SetCursor(wx.StockCursor(wx.CURSOR_NO_ENTRY))
2602
2603 # Support for drag and drop
2604 if event.LeftIsDown() and not (style & FNB_NODRAG):
2605
2606 self._isdragging = True
2607 draginfo = FNBDragInfo(self, tabIdx)
2608 drginfo = cPickle.dumps(draginfo)
2609 dataobject = wx.CustomDataObject(wx.CustomDataFormat("FlatNotebook"))
2610 dataobject.SetData(drginfo)
2611 dragSource = wx.DropSource(self)
2612 dragSource.SetData(dataobject)
2613 dragSource.DoDragDrop(wx.Drag_DefaultMove)
2614
2615 bRedrawX = self._nXButtonStatus != xButtonStatus
2616 bRedrawRight = self._nRightButtonStatus != rightButtonStatus
2617 bRedrawLeft = self._nLeftButtonStatus != leftButtonStatus
2618 bRedrawTabX = self._nTabXButtonStatus != xTabButtonStatus
2619
2620 if (bRedrawX or bRedrawRight or bRedrawLeft or bRedrawTabX):
2621
2622 dc = wx.ClientDC(self)
2623 if bRedrawX:
2624
2625 self.DrawX(dc)
2626
2627 if bRedrawLeft:
2628
2629 self.DrawLeftArrow(dc)
2630
2631 if bRedrawRight:
2632
2633 self.DrawRightArrow(dc)
2634
2635 if bRedrawTabX:
2636
2637 self.DrawTabX(dc, self._pagesInfoVec[tabIdx].GetXRect(), tabIdx)
2638
2639 event.Skip()
2640
2641
2642 def GetLastVisibleTab(self):
2643 """ Returns the last visible tab. """
2644
2645 ii = 0
2646
2647 for ii in xrange(self._nFrom, len(self._pagesInfoVec)):
2648
2649 if self._pagesInfoVec[ii].GetPosition() == wx.Point(-1, -1):
2650 break
2651
2652 return ii-1
2653
2654
2655 def GetNumTabsCanScrollLeft(self):
2656 """ Returns the number of tabs than can be scrolled left. """
2657
2658 # Reserved area for the buttons (<>x)
2659 rect = self.GetClientRect()
2660 clientWidth = rect.width
2661 posx = self._pParent._nPadding
2662 numTabs = 0
2663 pom = 0
2664
2665 dc = wx.ClientDC(self)
2666
2667 # In case we have error prevent crash
2668 if self._nFrom < 0:
2669 return 0
2670
2671 style = self.GetParent().GetWindowStyleFlag()
2672
2673 for ii in xrange(self._nFrom, -1, -1):
2674
2675 boldFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
2676 boldFont.SetWeight(wx.FONTWEIGHT_BOLD)
2677 dc.SetFont(boldFont)
2678
2679 width, height = dc.GetTextExtent("Tp")
2680
2681 tabHeight = height + FNB_HEIGHT_SPACER # We use 6 pixels as padding
2682 if style & FNB_VC71:
2683 tabHeight = (style & FNB_BOTTOM and [tabHeight - 4] or [tabHeight])[0]
2684 elif style & FNB_FANCY_TABS:
2685 tabHeight = (style & FNB_BOTTOM and [tabHeight - 3] or [tabHeight])[0]
2686
2687 width, pom = dc.GetTextExtent(self.GetPageText(ii))
2688 if style != FNB_VC71:
2689 shapePoints = int(tabHeight*math.tan(float(self._pagesInfoVec[ii].GetTabAngle())/180.0*math.pi))
2690 else:
2691 shapePoints = 0
2692
2693 tabWidth = self._pParent._nPadding*2 + width
2694
2695 if not (style & FNB_VC71):
2696 # Default style
2697 tabWidth += 2*shapePoints
2698
2699 hasImage = self._ImageList != None and self._pagesInfoVec[ii].GetImageIndex() != -1
2700
2701 # For VC71 style, we only add the icon size (16 pixels)
2702 if hasImage:
2703
2704 if not self.IsDefaultTabs():
2705 tabWidth += 16 + self._pParent._nPadding
2706 else:
2707 # Default style
2708 tabWidth += 16 + self._pParent._nPadding + shapePoints/2
2709
2710 if posx + tabWidth + self.GetButtonsAreaLength() >= clientWidth:
2711 break
2712
2713 numTabs = numTabs + 1
2714 posx += tabWidth
2715
2716 return numTabs
2717
2718
2719 def IsDefaultTabs(self):
2720 """ Returns whether a tab has a default style. """
2721
2722 style = self.GetParent().GetWindowStyleFlag()
2723 res = (style & FNB_VC71) or (style & FNB_FANCY_TABS)
2724 return not res
2725
2726
2727 def AdvanceSelection(self, bForward=True):
2728 """
2729 Cycles through the tabs.
2730 The call to this function generates the page changing events.
2731 """
2732
2733 nSel = self.GetSelection()
2734
2735 if nSel < 0:
2736 return
2737
2738 nMax = self.GetPageCount() - 1
2739 if bForward:
2740 self.SetSelection((nSel == nMax and [0] or [nSel + 1])[0])
2741 else:
2742 self.SetSelection((nSel == 0 and [nMax] or [nSel - 1])[0])
2743
2744
2745 def OnMouseLeave(self, event):
2746 """ Handles the wx.EVT_LEAVE_WINDOW event for PageContainerBase. """
2747
2748 self._nLeftButtonStatus = FNB_BTN_NONE
2749 self._nXButtonStatus = FNB_BTN_NONE
2750 self._nRightButtonStatus = FNB_BTN_NONE
2751 self._nTabXButtonStatus = FNB_BTN_NONE
2752
2753 dc = wx.ClientDC(self)
2754 self.DrawX(dc)
2755 self.DrawLeftArrow(dc)
2756 self.DrawRightArrow(dc)
2757
2758 selection = self.GetSelection()
2759
2760 if selection == -1:
2761 event.Skip()
2762 return
2763
2764 if not self.IsTabVisible(selection):
2765 if selection == len(self._pagesInfoVec) - 1:
2766 if not self.CanFitToScreen(selection):
2767 event.Skip()
2768 return
2769 else:
2770 event.Skip()
2771 return
2772
2773 self.DrawTabX(dc, self._pagesInfoVec[selection].GetXRect(), selection)
2774
2775 event.Skip()
2776
2777
2778 def OnMouseEnterWindow(self, event):
2779 """ Handles the wx.EVT_ENTER_WINDOW event for PageContainerBase. """
2780
2781 self._nLeftButtonStatus = FNB_BTN_NONE
2782 self._nXButtonStatus = FNB_BTN_NONE
2783 self._nRightButtonStatus = FNB_BTN_NONE
2784 self._nLeftClickZone = FNB_BTN_NONE
2785
2786 event.Skip()
2787
2788
2789 def ShowTabTooltip(self, tabIdx):
2790 """ Shows a tab tooltip. """
2791
2792 pWindow = self._pParent.GetPage(tabIdx)
2793
2794 if pWindow:
2795 pToolTip = pWindow.GetToolTip()
2796 if pToolTip and pToolTip.GetWindow() == pWindow:
2797 self.SetToolTipString(pToolTip.GetTip())
2798
2799
2800 def FillGradientColor(self, dc, rect):
2801 """ Gradient fill from colour 1 to colour 2 with top to bottom. """
2802
2803 if rect.height < 1 or rect.width < 1:
2804 return
2805
2806 size = rect.height
2807
2808 # calculate gradient coefficients
2809 style = self.GetParent().GetWindowStyleFlag()
2810 col2 = ((style & FNB_BOTTOM) and [self._colorTo] or [self._colorFrom])[0]
2811 col1 = ((style & FNB_BOTTOM) and [self._colorFrom] or [self._colorTo])[0]
2812
2813 rf, gf, bf = 0, 0, 0
2814 rstep = float(col2.Red() - col1.Red())/float(size)
2815 gstep = float(col2.Green() - col1.Green())/float(size)
2816 bstep = float(col2.Blue() - col1.Blue())/float(size)
2817
2818 for y in xrange(rect.y, rect.y + size):
2819 currCol = wx.Colour(col1.Red() + rf, col1.Green() + gf, col1.Blue() + bf)
2820 dc.SetBrush(wx.Brush(currCol))
2821 dc.SetPen(wx.Pen(currCol))
2822 dc.DrawLine(rect.x, y, rect.x + rect.width, y)
2823 rf += rstep
2824 gf += gstep
2825 bf += bstep
2826
2827
2828 def SetPageImage(self, page, imgindex):
2829 """ Sets the image index associated to a page. """
2830
2831 if page < len(self._pagesInfoVec):
2832
2833 self._pagesInfoVec[page].SetImageIndex(imgindex)
2834 self.Refresh()
2835
2836
2837 def GetPageImageIndex(self, page):
2838 """ Returns the image index associated to a page. """
2839
2840 if page < len(self._pagesInfoVec):
2841
2842 return self._pagesInfoVec[page].GetImageIndex()
2843
2844 return -1
2845
2846
2847 def OnDropTarget(self, x, y, nTabPage, wnd_oldContainer):
2848 """ Handles the drop action from a DND operation. """
2849
2850 # Disable drag'n'drop for disabled tab
2851 if not wnd_oldContainer._pagesInfoVec[nTabPage].GetEnabled():
2852 return wx.DragCancel
2853
2854 self._isdragging = True
2855 oldContainer = wnd_oldContainer
2856 nIndex = -1
2857
2858 where, nIndex = self.HitTest(wx.Point(x, y))
2859
2860 oldNotebook = oldContainer.GetParent()
2861 newNotebook = self.GetParent()
2862
2863 if oldNotebook == newNotebook:
2864
2865 if nTabPage >= 0:
2866
2867 if where == FNB_TAB:
2868 self.MoveTabPage(nTabPage, nIndex)
2869
2870 else:
2871
2872 if wx.Platform in ["__WXMSW__", "__WXGTK__"]:
2873 if nTabPage >= 0:
2874
2875 window = oldNotebook.GetPage(nTabPage)
2876
2877 if window:
2878 where, nIndex = newNotebook._pages.HitTest(wx.Point(x, y))
2879 caption = oldContainer.GetPageText(nTabPage)
2880 imageindex = oldContainer.GetPageImageIndex(nTabPage)
2881 oldNotebook.RemovePage(nTabPage)
2882 window.Reparent(newNotebook)
2883
2884 newNotebook.InsertPage(nIndex, window, caption, True, imageindex)
2885
2886 self._isdragging = False
2887
2888 return wx.DragMove
2889
2890
2891 def MoveTabPage(self, nMove, nMoveTo):
2892 """ Moves a tab inside the same FlatNotebook. """
2893
2894 if nMove == nMoveTo:
2895 return
2896
2897 elif nMoveTo < len(self._pParent._windows):
2898 nMoveTo = nMoveTo + 1
2899
2900 self._pParent.Freeze()
2901
2902 # Remove the window from the main sizer
2903 nCurSel = self._pParent._pages.GetSelection()
2904 self._pParent._mainSizer.Detach(self._pParent._windows[nCurSel])
2905 self._pParent._windows[nCurSel].Hide()
2906
2907 pWindow = self._pParent._windows[nMove]
2908 self._pParent._windows.pop(nMove)
2909 self._pParent._windows.insert(nMoveTo-1, pWindow)
2910
2911 pgInfo = self._pagesInfoVec[nMove]
2912
2913 self._pagesInfoVec.pop(nMove)
2914 self._pagesInfoVec.insert(nMoveTo - 1, pgInfo)
2915
2916 # Add the page according to the style
2917 pSizer = self._pParent._mainSizer
2918 style = self.GetParent().GetWindowStyleFlag()
2919
2920 if style & FNB_BOTTOM:
2921
2922 pSizer.Insert(0, pWindow, 1, wx.EXPAND)
2923
2924 else:
2925
2926 # We leave a space of 1 pixel around the window
2927 pSizer.Add(pWindow, 1, wx.EXPAND)
2928
2929 pWindow.Show()
2930
2931 pSizer.Layout()
2932 self._iActivePage = nMoveTo - 1
2933 self.DoSetSelection(self._iActivePage)
2934 self.Refresh()
2935 self._pParent.Thaw()
2936
2937
2938 def CanFitToScreen(self, page):
2939 """ Returns whether all the tabs can fit in the available space. """
2940
2941 # Incase the from is greater than page,
2942 # we need to reset the self._nFrom, so in order
2943 # to force the caller to do so, we return False
2944 if self._nFrom > page:
2945 return False
2946
2947 # Calculate the tab width including borders and image if any
2948 dc = wx.ClientDC(self)
2949
2950 style = self.GetParent().GetWindowStyleFlag()
2951
2952 width, height = dc.GetTextExtent("Tp")
2953 width, pom = dc.GetTextExtent(self.GetPageText(page))
2954
2955 tabHeight = height + FNB_HEIGHT_SPACER # We use 6 pixels as padding
2956
2957 if style & FNB_VC71:
2958 tabHeight = (style & FNB_BOTTOM and [tabHeight - 4] or [tabHeight])[0]
2959 elif style & FNB_FANCY_TABS:
2960 tabHeight = (style & FNB_BOTTOM and [tabHeight - 2] or [tabHeight])[0]
2961
2962 tabWidth = self._pParent._nPadding * 2 + width
2963
2964 if not style & FNB_VC71:
2965 shapePoints = int(tabHeight*math.tan(float(self._pagesInfoVec[page].GetTabAngle())/180.0*math.pi))
2966 else:
2967 shapePoints = 0
2968
2969 if not style & FNB_VC71:
2970 # Default style
2971 tabWidth += 2*shapePoints
2972
2973 hasImage = self._ImageList != None
2974
2975 if hasImage:
2976 hasImage &= self._pagesInfoVec[page].GetImageIndex() != -1
2977
2978 # For VC71 style, we only add the icon size (16 pixels)
2979 if hasImage and (style & FNB_VC71 or style & FNB_FANCY_TABS):
2980 tabWidth += 16
2981 else:
2982 # Default style
2983 tabWidth += 16 + shapePoints/2
2984
2985 # Check if we can draw more
2986 posx = self._pParent._nPadding
2987
2988 if self._nFrom >= 0:
2989 for ii in xrange(self._nFrom, len(self._pagesInfoVec)):
2990 if self._pagesInfoVec[ii].GetPosition() == wx.Point(-1, -1):
2991 break
2992 posx += self._pagesInfoVec[ii].GetSize().x
2993
2994 rect = self.GetClientRect()
2995 clientWidth = rect.width
2996
2997 if posx + tabWidth + self.GetButtonsAreaLength() >= clientWidth:
2998 return False
2999
3000 return True
3001
3002
3003 def GetNumOfVisibleTabs(self):
3004 """ Returns the number of visible tabs. """
3005
3006 count = 0
3007 for ii in xrange(self._nFrom, len(self._pagesInfoVec)):
3008 if self._pagesInfoVec[ii].GetPosition() == wx.Point(-1, -1):
3009 break
3010 count = count + 1
3011
3012 return count
3013
3014
3015 def GetEnabled(self, page):
3016 """ Returns whether a tab is enabled or not. """
3017
3018 if page >= len(self._pagesInfoVec):
3019 return True # Seems strange, but this is the default
3020
3021 return self._pagesInfoVec[page].GetEnabled()
3022
3023
3024 def Enable(self, page, enabled=True):
3025 """ Enables or disables a tab. """
3026
3027 if page >= len(self._pagesInfoVec):
3028 return
3029
3030 self._pagesInfoVec[page].Enable(enabled)
3031
3032
3033 def GetLeftButtonPos(self):
3034 """ Returns the left button position in the navigation area. """
3035
3036 style = self.GetParent().GetWindowStyleFlag()
3037 rect = self.GetClientRect()
3038 clientWidth = rect.width
3039
3040 if style & FNB_NO_X_BUTTON:
3041 return clientWidth - 38
3042 else:
3043 return clientWidth - 54
3044
3045
3046 def GetRightButtonPos(self):
3047 """ Returns the right button position in the navigation area. """
3048
3049 style = self.GetParent().GetWindowStyleFlag()
3050 rect = self.GetClientRect()
3051 clientWidth = rect.width
3052
3053 if style & FNB_NO_X_BUTTON:
3054 return clientWidth - 22
3055 else:
3056 return clientWidth - 38
3057
3058
3059 def GetXPos(self):
3060 """ Returns the 'X' button position in the navigation area. """
3061
3062 style = self.GetParent().GetWindowStyleFlag()
3063 rect = self.GetClientRect()
3064 clientWidth = rect.width
3065
3066 if style & FNB_NO_X_BUTTON:
3067 return clientWidth
3068 else:
3069 return clientWidth - 22
3070
3071
3072 def GetButtonsAreaLength(self):
3073 """ Returns the navigation area width. """
3074
3075 style = self.GetParent().GetWindowStyleFlag()
3076
3077 if style & FNB_NO_NAV_BUTTONS and style & FNB_NO_X_BUTTON:
3078 return 0
3079 elif style & FNB_NO_NAV_BUTTONS and not style & FNB_NO_X_BUTTON:
3080 return 53 - 16
3081 elif not style & FNB_NO_NAV_BUTTONS and style & FNB_NO_X_BUTTON:
3082 return 53 - 16
3083 else:
3084 # All buttons
3085 return 53
3086
3087
3088 def GetSingleLineBorderColor(self):
3089
3090 if self.HasFlag(FNB_FANCY_TABS):
3091 return self._colorFrom
3092
3093 return wx.WHITE
3094
3095
3096 def DrawTabsLine(self, dc):
3097 """ Draws a line over the tabs. """
3098
3099 clntRect = self.GetClientRect()
3100 clientRect3 = wx.Rect(0, 0, clntRect.width, clntRect.height)
3101
3102 if self.HasFlag(FNB_BOTTOM):
3103
3104 clientRect = wx.Rect(0, 2, clntRect.width, clntRect.height - 2)
3105 clientRect2 = wx.Rect(0, 1, clntRect.width, clntRect.height - 1)
3106
3107 else:
3108
3109 clientRect = wx.Rect(0, 0, clntRect.width, clntRect.height - 2)
3110 clientRect2 = wx.Rect(0, 0, clntRect.width, clntRect.height - 1)
3111
3112 dc.SetBrush(wx.TRANSPARENT_BRUSH)
3113 dc.SetPen(wx.Pen(self.GetSingleLineBorderColor()))
3114 dc.DrawRectangleRect(clientRect2)
3115 dc.DrawRectangleRect(clientRect3)
3116
3117 dc.SetPen(wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW)))
3118 dc.DrawRectangleRect(clientRect)
3119
3120 if not self.HasFlag(FNB_TABS_BORDER_SIMPLE):
3121
3122 dc.SetPen(wx.Pen((self.HasFlag(FNB_VC71) and [wx.Colour(247, 243, 233)] or [self._tabAreaColor])[0]))
3123 dc.DrawLine(0, 0, 0, clientRect.height+1)
3124
3125 if self.HasFlag(FNB_BOTTOM):
3126
3127 dc.DrawLine(0, clientRect.height+1, clientRect.width, clientRect.height+1)
3128
3129 else:
3130 dc.DrawLine(0, 0, clientRect.width, 0)
3131
3132 dc.DrawLine(clientRect.width - 1, 0, clientRect.width - 1, clientRect.height+1)
3133
3134
3135 def HasFlag(self, flag):
3136 """ Returns whether a flag is present in the FlatNotebook style. """
3137
3138 style = self.GetParent().GetWindowStyleFlag()
3139 res = (style & flag and [True] or [False])[0]
3140 return res
3141
3142
3143 def ClearFlag(self, flag):
3144 """ Deletes a flag from the FlatNotebook style. """
3145
3146 style = self.GetParent().GetWindowStyleFlag()
3147 style &= ~flag
3148 self.SetWindowStyleFlag(style)
3149
3150
3151 def TabHasImage(self, tabIdx):
3152 """ Returns whether a tab has an associated image index or not. """
3153
3154 if self._ImageList:
3155 return self._pagesInfoVec[tabIdx].GetImageIndex() != -1
3156
3157 return False
3158
3159
3160 def OnLeftDClick(self, event):
3161 """ Handles the wx.EVT_LEFT_DCLICK event for PageContainerBase. """
3162
3163 if self.HasFlag(FNB_DCLICK_CLOSES_TABS):
3164
3165 where, tabIdx = self.HitTest(event.GetPosition())
3166
3167 if where == FNB_TAB:
3168 self.DeletePage(tabIdx)
3169
3170 else:
3171
3172 event.Skip()
3173
3174
3175 def SetImageList(self, imglist):
3176 """ Sets the image list for the page control. """
3177
3178 self._ImageList = imglist
3179
3180
3181 def GetImageList(self):
3182 """ Returns the image list for the page control. """
3183
3184 return self._ImageList
3185
3186
3187 def GetSelection(self):
3188 """ Returns the current selected page. """
3189
3190 return self._iActivePage
3191
3192
3193 def GetPageCount(self):
3194 """ Returns the number of tabs in the FlatNotebook control. """
3195
3196 return len(self._pagesInfoVec)
3197
3198
3199 def GetPageText(self, page):
3200 """ Returns the tab caption of the page. """
3201
3202 return self._pagesInfoVec[page].GetCaption()
3203
3204
3205 def SetPageText(self, page, text):
3206 """ Sets the tab caption of the page. """
3207
3208 self._pagesInfoVec[page].SetCaption(text)
3209 return True
3210
3211
3212 def CanDrawXOnTab(self):
3213 """ Returns whether an 'X' can be drawn on a tab (all styles except VC8. """
3214
3215 return True
3216
3217
3218 # ---------------------------------------------------------------------------- #
3219 # Class FlatNotebook
3220 # Simple super class based on PageContainerBase
3221 # ---------------------------------------------------------------------------- #
3222
3223 class FlatNotebook(FlatNotebookBase):
3224
3225 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
3226 style=0, name="FlatNotebook"):
3227 """
3228 Default class constructor.
3229
3230 It is better to use directly the StyledNotebook class (see below) and then
3231 assigning the style you wish instead of calling FlatNotebook.
3232 """
3233
3234 style |= wx.TAB_TRAVERSAL
3235
3236 FlatNotebookBase.__init__(self, parent, id, pos, size, style, name)
3237 self._pages = self.CreatePageContainer()
3238
3239
3240 def CreatePageContainer(self):
3241 """ Creates the page container. """
3242
3243 return FlatNotebookBase.CreatePageContainer(self)
3244
3245
3246 #--------------------------------------------------------------------
3247 # StyledNotebook - a notebook with look n feel of Visual Studio 2005
3248 #--------------------------------------------------------------------
3249
3250 class StyledNotebook(FlatNotebookBase):
3251
3252 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
3253 style=0, name="StyledNotebook"):
3254 """ Default class constructor.
3255
3256 It is better to use directly the StyledNotebook class and then
3257 assigning the style you wish instead of calling FlatNotebook.
3258 """
3259
3260 style |= wx.TAB_TRAVERSAL
3261
3262 FlatNotebookBase.__init__(self, parent, id, pos, size, style, name)
3263
3264 # Custom initialization of the tab area
3265 if style & FNB_VC8:
3266 # Initialise the default style colors
3267 self.SetNonActiveTabTextColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNTEXT))
3268
3269
3270 def CreatePageContainer(self):
3271 """ Creates the page container. """
3272
3273 return StyledTabsContainer(self, wx.ID_ANY)
3274
3275
3276 # ---------------------------------------------------------------------------- #
3277 # Class StyledTabsContainer
3278 # Acts as a container for the pages you add to FlatNotebook
3279 # A more generic and more powerful implementation of PageContainerBase, can
3280 # handle also VC8 tabs style
3281 # ---------------------------------------------------------------------------- #
3282
3283 class StyledTabsContainer(PageContainerBase):
3284
3285 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
3286 style=0):
3287 """ Default class constructor. """
3288
3289 self._factor = 1
3290
3291 self._colorTo = LightColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE), 0)
3292 self._colorFrom = LightColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE), 60)
3293
3294 PageContainerBase.__init__(self, parent, id, pos, size, style)
3295
3296 self.Bind(wx.EVT_PAINT, self.OnPaint)
3297
3298
3299 def NumberTabsCanFit(self, dc):
3300 """ Returns the number of tabs that can fit inside the available space. """
3301
3302 rect = self.GetClientRect()
3303 clientWidth = rect.width
3304
3305 # Empty results
3306 vTabInfo = []
3307
3308 # We take the maxmimum font size, this is
3309 # achieved by setting the font to be bold
3310 font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
3311 font.SetWeight(wx.FONTWEIGHT_BOLD)
3312 dc.SetFont(font)
3313
3314 width, height = dc.GetTextExtent("Tp")
3315
3316 tabHeight = height + FNB_HEIGHT_SPACER # We use 8 pixels
3317 # The drawing starts from posx
3318 posx = self._pParent.GetPadding()
3319
3320 for i in xrange(self._nFrom, len(self._pagesInfoVec)):
3321
3322 width, pom = dc.GetTextExtent(self.GetPageText(i))
3323
3324 # Set a minimum size to a tab
3325 if width < 20:
3326 width = 20
3327
3328 tabWidth = self._pParent.GetPadding() * 2 + width
3329
3330 # Add the image width if it exist
3331 if self.TabHasImage(i):
3332 tabWidth += 16 + self._pParent.GetPadding()
3333
3334 vc8glitch = tabHeight + FNB_HEIGHT_SPACER
3335 if posx + tabWidth + vc8glitch + self.GetButtonsAreaLength() >= clientWidth:
3336 break
3337
3338 # Add a result to the returned vector
3339 tabRect = wx.Rect(posx, VERTICAL_BORDER_PADDING, tabWidth , tabHeight)
3340 vTabInfo.append(tabRect)
3341
3342 # Advance posx
3343 posx += tabWidth + FNB_HEIGHT_SPACER
3344
3345 return vTabInfo
3346
3347
3348 def GetNumTabsCanScrollLeft(self):
3349 """ Returns the number of tabs than can be scrolled left. """
3350
3351 # Reserved area for the buttons (<>x)
3352 rect = self.GetClientRect()
3353 clientWidth = rect.width
3354 posx = self._pParent.GetPadding()
3355 numTabs = 0
3356 pom = 0
3357
3358 dc = wx.ClientDC(self)
3359
3360 # Incase we have error prevent crash
3361 if self._nFrom < 0:
3362 return 0
3363
3364 style = self.GetParent().GetWindowStyleFlag()
3365
3366 for i in xrange(self._nFrom, -1, -1):
3367
3368 boldFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
3369 boldFont.SetWeight(wx.FONTWEIGHT_BOLD)
3370 dc.SetFont(boldFont)
3371
3372 width, height = dc.GetTextExtent("Tp")
3373
3374 tabHeight = height + FNB_HEIGHT_SPACER # We use 6 pixels as padding
3375
3376 if style & FNB_VC71:
3377 tabHeight = (self.HasFlag(FNB_BOTTOM) and [tabHeight - 4] or [tabHeight])[0]
3378 elif style & FNB_FANCY_TABS:
3379 tabHeight = (self.HasFlag(FNB_BOTTOM) and [tabHeight - 3] or [tabHeight])[0]
3380
3381 width, pom = dc.GetTextExtent(self.GetPageText(i))
3382
3383 if style != FNB_VC71:
3384 shapePoints = int(tabHeight*math.tan(float(self._pagesInfoVec[i].GetTabAngle())/180.0*math.pi))
3385 else:
3386 shapePoints = 0
3387
3388 tabWidth = self._pParent.GetPadding() * 2 + width
3389 if not style & FNB_VC71:
3390 # Default style
3391 tabWidth += 2*shapePoints
3392
3393 # For VC71 style, we only add the icon size (16 pixels)
3394 if self.TabHasImage(i):
3395
3396 if not self.IsDefaultTabs():
3397 tabWidth += 16 + self._pParent.GetPadding()
3398 else:
3399 # Default style
3400 tabWidth += 16 + self._pParent.GetPadding() + shapePoints/2
3401
3402 vc8glitch = (style & FNB_VC8 and [tabHeight + FNB_HEIGHT_SPACER] or [0])[0]
3403
3404 if posx + tabWidth + vc8glitch + self.GetButtonsAreaLength() >= clientWidth:
3405 break
3406
3407 numTabs = numTabs + 1
3408 posx += tabWidth
3409
3410 return numTabs
3411
3412
3413 def CanDrawXOnTab(self):
3414 """ Returns whether an 'X' button can be drawn on a tab (not VC8 style). """
3415
3416 style = self.GetParent().GetWindowStyleFlag()
3417 isVC8 = (style & FNB_VC8 and [True] or [False])[0]
3418 return not isVC8
3419
3420
3421 def IsDefaultTabs(self):
3422 """ Returns whether a tab has a default style or not. """
3423
3424 style = self.GetParent().GetWindowStyleFlag()
3425 res = (style & FNB_VC71) or (style & FNB_FANCY_TABS) or (style & FNB_VC8)
3426 return not res
3427
3428
3429 def HitTest(self, pt):
3430 """ HitTest specific method for VC8 style. """
3431
3432 fullrect = self.GetClientRect()
3433 btnLeftPos = self.GetLeftButtonPos()
3434 btnRightPos = self.GetRightButtonPos()
3435 btnXPos = self.GetXPos()
3436 style = self.GetParent().GetWindowStyleFlag()
3437 tabIdx = -1
3438
3439 if not self._pagesInfoVec:
3440 return FNB_NOWHERE, -1
3441
3442 rect = wx.Rect(btnXPos, 8, 16, 16)
3443
3444 if rect.Contains(pt):
3445 return (style & FNB_NO_X_BUTTON and [FNB_NOWHERE] or [FNB_X])[0], -1
3446
3447 rect = wx.Rect(btnRightPos, 8, 16, 16)
3448
3449 if rect.Contains(pt):
3450 return (style & FNB_NO_NAV_BUTTONS and [FNB_NOWHERE] or [FNB_RIGHT_ARROW])[0], -1
3451
3452 rect = wx.Rect(btnLeftPos, 8, 16, 16)
3453
3454 if rect.Contains(pt):
3455 return (style & FNB_NO_NAV_BUTTONS and [FNB_NOWHERE] or [FNB_LEFT_ARROW])[0], -1
3456
3457 # Test whether a left click was made on a tab
3458 bFoundMatch = False
3459
3460 for cur in xrange(self._nFrom, len(self._pagesInfoVec)):
3461
3462 pgInfo = self._pagesInfoVec[cur]
3463
3464 if pgInfo.GetPosition() == wx.Point(-1, -1):
3465 continue
3466
3467 if style & FNB_VC8:
3468
3469 if self._pagesInfoVec[cur].GetRegion().Contains(pt.x, pt.y):
3470
3471 if bFoundMatch or cur == self.GetSelection():
3472
3473 return FNB_TAB, cur
3474
3475 tabIdx = cur
3476 bFoundMatch = True
3477
3478 else:
3479
3480 if style & FNB_X_ON_TAB and cur == self.GetSelection():
3481
3482 # 'x' button exists on a tab
3483 if self._pagesInfoVec[cur].GetXRect().Contains(pt):
3484 return FNB_TAB_X, cur
3485
3486 tabRect = wx.Rect(pgInfo.GetPosition().x, pgInfo.GetPosition().y, pgInfo.GetSize().x, pgInfo.GetSize().y)
3487
3488 if tabRect.Contains(pt):
3489 return FNB_TAB, cur
3490
3491 if bFoundMatch:
3492 return FNB_TAB, tabIdx
3493
3494 if self._isdragging:
3495 # We are doing DND, so check also the region outside the tabs
3496 # try before the first tab
3497 pgInfo = self._pagesInfoVec[0]
3498 tabRect = wx.Rect(0, pgInfo.GetPosition().y, pgInfo.GetPosition().x, self.GetParent().GetSize().y)
3499 if tabRect.Contains(pt):
3500 return FNB_TAB, 0
3501
3502 # try after the last tab
3503 pgInfo = self._pagesInfoVec[-1]
3504 startpos = pgInfo.GetPosition().x+pgInfo.GetSize().x
3505 tabRect = wx.Rect(startpos, pgInfo.GetPosition().y, fullrect.width-startpos, self.GetParent().GetSize().y)
3506
3507 if tabRect.Contains(pt):
3508 return FNB_TAB, len(self._pagesInfoVec)
3509
3510 # Default
3511 return FNB_NOWHERE, -1
3512
3513
3514 def OnPaint(self, event):
3515 """
3516 Handles the wx.EVT_PAINT event for StyledTabsContainer.
3517 Switches to PageContainerBase.OnPaint() method if the style is not VC8.
3518 """
3519
3520 if not self.HasFlag(FNB_VC8):
3521
3522 PageContainerBase.OnPaint(self, event)
3523 return
3524
3525 # Visual studio 8 style
3526 dc = wx.BufferedPaintDC(self)
3527
3528 if "__WXMAC__" in wx.PlatformInfo:
3529 # Works well on MSW & GTK, however this lines should be skipped on MAC
3530 if not self._pagesInfoVec or self._nFrom >= len(self._pagesInfoVec):
3531 self.Hide()
3532 event.Skip()
3533 return
3534
3535 # Set the font for measuring the tab height
3536 normalFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
3537 boldFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
3538 boldFont.SetWeight(wx.FONTWEIGHT_BOLD)
3539
3540 if "__WXGTK__" in wx.PlatformInfo:
3541 dc.SetFont(boldFont)
3542
3543 width, height = dc.GetTextExtent("Tp")
3544
3545 tabHeight = height + FNB_HEIGHT_SPACER # We use 8 pixels as padding
3546
3547 # Calculate the number of rows required for drawing the tabs
3548 rect = self.GetClientRect()
3549
3550 # Set the maximum client size
3551 self.SetSizeHints(self.GetButtonsAreaLength(), tabHeight)
3552 borderPen = wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW))
3553
3554 # Create brushes
3555 backBrush = wx.Brush(self._tabAreaColor)
3556 noselBrush = wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
3557 selBrush = wx.Brush(self._activeTabColor)
3558 size = self.GetSize()
3559
3560 # Background
3561 dc.SetTextBackground(self.GetBackgroundColour())
3562 dc.SetTextForeground(self._activeTextColor)
3563
3564 # If border style is set, set the pen to be border pen
3565 if self.HasFlag(FNB_TABS_BORDER_SIMPLE):
3566 dc.SetPen(borderPen)
3567 else:
3568 dc.SetPen(wx.TRANSPARENT_PEN)
3569
3570 lightFactor = (self.HasFlag(FNB_BACKGROUND_GRADIENT) and [70] or [0])[0]
3571 # For VC8 style, we color the tab area in gradient coloring
3572 PaintStraightGradientBox(dc, self.GetClientRect(), self._tabAreaColor, LightColour(self._tabAreaColor, lightFactor))
3573
3574 dc.SetBrush(wx.TRANSPARENT_BRUSH)
3575 dc.DrawRectangle(0, 0, size.x, size.y)
3576
3577 # Take 3 bitmaps for the background for the buttons
3578
3579 mem_dc = wx.MemoryDC()
3580
3581 #---------------------------------------
3582 # X button
3583 #---------------------------------------
3584 rect = wx.Rect(self.GetXPos(), 6, 16, 14)
3585 mem_dc.SelectObject(self._xBgBmp)
3586 mem_dc.Blit(0, 0, rect.width, rect.height, dc, rect.x, rect.y)
3587 mem_dc.SelectObject(wx.NullBitmap)
3588
3589 #---------------------------------------
3590 # Right button
3591 #---------------------------------------
3592 rect = wx.Rect(self.GetRightButtonPos(), 6, 16, 14)
3593 mem_dc.SelectObject(self._rightBgBmp)
3594 mem_dc.Blit(0, 0, rect.width, rect.height, dc, rect.x, rect.y)
3595 mem_dc.SelectObject(wx.NullBitmap)
3596
3597 #---------------------------------------
3598 # Left button
3599 #---------------------------------------
3600 rect = wx.Rect(self.GetLeftButtonPos(), 6, 16, 14)
3601 mem_dc.SelectObject(self._leftBgBmp)
3602 mem_dc.Blit(0, 0, rect.width, rect.height, dc, rect.x, rect.y)
3603 mem_dc.SelectObject(wx.NullBitmap)
3604
3605 # We always draw the bottom/upper line of the tabs
3606 # regradless the style
3607 dc.SetPen(borderPen)
3608 self.DrawTabsLine(dc)
3609
3610 # Restore the pen
3611 dc.SetPen(borderPen)
3612
3613 # Draw labels
3614 dc.SetFont(boldFont)
3615 activeTabPosx = 0
3616
3617 # Update all the tabs from 0 to 'self._nFrom' to be non visible
3618 for i in xrange(self._nFrom):
3619
3620 self._pagesInfoVec[i].SetPosition(wx.Point(-1, -1))
3621 self._pagesInfoVec[i].GetRegion().Clear()
3622
3623 # Draw the visible tabs, in VC8 style, we draw them from right to left
3624 vTabsInfo = self.NumberTabsCanFit(dc)
3625
3626 for cur in xrange(len(vTabsInfo) - 1, -1, -1):
3627
3628 # 'i' points to the index of the currently drawn tab
3629 # in self._pagesInfoVec vector
3630 i = self._nFrom + cur
3631 dc.SetPen(borderPen)
3632 dc.SetBrush((i==self.GetSelection() and [selBrush] or [noselBrush])[0])
3633
3634 # Calculate the text length using the bold font, so when selecting a tab
3635 # its width will not change
3636 dc.SetFont(boldFont)
3637 width, pom = dc.GetTextExtent(self.GetPageText(i))
3638
3639 # Now set the font to the correct font
3640 dc.SetFont((i==self.GetSelection() and [boldFont] or [normalFont])[0])
3641
3642 # Set a minimum size to a tab
3643 if width < 20:
3644 width = 20
3645
3646 # Add the padding to the tab width
3647 # Tab width:
3648 # +-----------------------------------------------------------+
3649 # | PADDING | IMG | IMG_PADDING | TEXT | PADDING | x |PADDING |
3650 # +-----------------------------------------------------------+
3651
3652 tabWidth = self._pParent.GetPadding() * 2 + width
3653 imageYCoord = (self.HasFlag(FNB_BOTTOM) and [6] or [8])[0]
3654
3655 if self.TabHasImage(i):
3656 tabWidth += 16 + self._pParent.GetPadding()
3657
3658 posx = vTabsInfo[cur].x
3659
3660 # By default we clean the tab region
3661 # incase we use the VC8 style which requires
3662 # the region, it will be filled by the function
3663 # drawVc8Tab
3664 self._pagesInfoVec[i].GetRegion().Clear()
3665
3666 # Clean the 'x' buttn on the tab
3667 # 'Clean' rectanlge is a rectangle with width or height
3668 # with values lower than or equal to 0
3669 self._pagesInfoVec[i].GetXRect().SetSize(wx.Size(-1, -1))
3670
3671 # Draw the tab
3672 # Incase we are drawing the active tab
3673 # we need to redraw so it will appear on top
3674 # of all other tabs
3675 if i == self.GetSelection():
3676
3677 activeTabPosx = posx
3678
3679 else:
3680
3681 self.DrawVC8Tab(dc, posx, i, tabWidth, tabHeight)
3682
3683 # Text drawing offset from the left border of the
3684 # rectangle
3685 # The width of the images are 16 pixels
3686 vc8ShapeLen = tabHeight
3687
3688 if self.TabHasImage(i):
3689 textOffset = self._pParent.GetPadding() * 2 + 16 + vc8ShapeLen
3690 else:
3691 textOffset = self._pParent.GetPadding() + vc8ShapeLen
3692
3693 # Set the non-active text color
3694 if i != self.GetSelection():
3695 dc.SetTextForeground(self._nonActiveTextColor)
3696
3697 if self.TabHasImage(i):
3698 imageXOffset = textOffset - 16 - self._pParent.GetPadding()
3699 self._ImageList.Draw(self._pagesInfoVec[i].GetImageIndex(), dc,
3700 posx + imageXOffset, imageYCoord,
3701 wx.IMAGELIST_DRAW_TRANSPARENT, True)
3702
3703 dc.DrawText(self.GetPageText(i), posx + textOffset, imageYCoord)
3704
3705 textWidth, textHeight = dc.GetTextExtent(self.GetPageText(i))
3706
3707 # Restore the text forground
3708 dc.SetTextForeground(self._activeTextColor)
3709
3710 # Update the tab position & size
3711 self._pagesInfoVec[i].SetPosition(wx.Point(posx, VERTICAL_BORDER_PADDING))
3712 self._pagesInfoVec[i].SetSize(wx.Size(tabWidth, tabHeight))
3713
3714 # Incase we are in VC8 style, redraw the active tab (incase it is visible)
3715 if self.GetSelection() >= self._nFrom and self.GetSelection() < self._nFrom + len(vTabsInfo):
3716
3717 hasImage = self.TabHasImage(self.GetSelection())
3718
3719 dc.SetFont(boldFont)
3720 width, pom = dc.GetTextExtent(self.GetPageText(self.GetSelection()))
3721
3722 tabWidth = self._pParent.GetPadding() * 2 + width
3723
3724 if hasImage:
3725 tabWidth += 16 + self._pParent.GetPadding()
3726
3727 # Set the active tab font, pen brush and font-color
3728 dc.SetPen(borderPen)
3729 dc.SetBrush(selBrush)
3730 dc.SetFont(boldFont)
3731 dc.SetTextForeground(self._activeTextColor)
3732 self.DrawVC8Tab(dc, activeTabPosx, self.GetSelection(), tabWidth, tabHeight)
3733
3734 # Text drawing offset from the left border of the
3735 # rectangle
3736 # The width of the images are 16 pixels
3737 vc8ShapeLen = tabHeight - VERTICAL_BORDER_PADDING - 2
3738 if hasImage:
3739 textOffset = self._pParent.GetPadding() * 2 + 16 + vc8ShapeLen
3740 else:
3741 textOffset = self._pParent.GetPadding() + vc8ShapeLen
3742
3743 # Draw the image for the tab if any
3744 imageYCoord = (self.HasFlag(FNB_BOTTOM) and [6] or [8])[0]
3745
3746 if hasImage:
3747 imageXOffset = textOffset - 16 - self._pParent.GetPadding()
3748 self._ImageList.Draw(self._pagesInfoVec[self.GetSelection()].GetImageIndex(), dc,
3749 activeTabPosx + imageXOffset, imageYCoord,
3750 wx.IMAGELIST_DRAW_TRANSPARENT, True)
3751
3752 dc.DrawText(self.GetPageText(self.GetSelection()), activeTabPosx + textOffset, imageYCoord)
3753
3754 # Update all tabs that can not fit into the screen as non-visible
3755 for xx in xrange(self._nFrom + len(vTabsInfo), len(self._pagesInfoVec)):
3756
3757 self._pagesInfoVec[xx].SetPosition(wx.Point(-1, -1))
3758 self._pagesInfoVec[xx].GetRegion().Clear()
3759
3760 # Draw the left/right/close buttons
3761 # Left arrow
3762 self.DrawLeftArrow(dc)
3763 self.DrawRightArrow(dc)
3764 self.DrawX(dc)
3765
3766
3767 def DrawVC8Tab(self, dc, posx, tabIdx, tabWidth, tabHeight):
3768 """ Draws the VC8 style tabs. """
3769
3770 borderPen = wx.Pen(self._colorBorder)
3771 tabPoints = [wx.Point() for ii in xrange(8)]
3772
3773 # If we draw the first tab or the active tab,
3774 # we draw a full tab, else we draw a truncated tab
3775 #
3776 # X(2) X(3)
3777 # X(1) X(4)
3778 #
3779 # X(5)
3780 #
3781 # X(0),(7) X(6)
3782 #
3783 #
3784
3785 tabPoints[0].x = (self.HasFlag(FNB_BOTTOM) and [posx] or [posx+self._factor])[0]
3786 tabPoints[0].y = (self.HasFlag(FNB_BOTTOM) and [2] or [tabHeight - 3])[0]
3787
3788 tabPoints[1].x = tabPoints[0].x + tabHeight - VERTICAL_BORDER_PADDING - 3 - self._factor
3789 tabPoints[1].y = (self.HasFlag(FNB_BOTTOM) and [tabHeight - (VERTICAL_BORDER_PADDING+2)] or [(VERTICAL_BORDER_PADDING+2)])[0]
3790
3791 tabPoints[2].x = tabPoints[1].x + 4
3792 tabPoints[2].y = (self.HasFlag(FNB_BOTTOM) and [tabHeight - VERTICAL_BORDER_PADDING] or [VERTICAL_BORDER_PADDING])[0]
3793
3794 tabPoints[3].x = tabPoints[2].x + tabWidth - 2
3795 tabPoints[3].y = (self.HasFlag(FNB_BOTTOM) and [tabHeight - VERTICAL_BORDER_PADDING] or [VERTICAL_BORDER_PADDING])[0]
3796
3797 tabPoints[4].x = tabPoints[3].x + 1
3798 tabPoints[4].y = (self.HasFlag(FNB_BOTTOM) and [tabPoints[3].y - 1] or [tabPoints[3].y + 1])[0]
3799
3800 tabPoints[5].x = tabPoints[4].x + 1
3801 tabPoints[5].y = (self.HasFlag(FNB_BOTTOM) and [(tabPoints[4].y - 1)] or [tabPoints[4].y + 1])[0]
3802
3803 tabPoints[6].x = tabPoints[2].x + tabWidth
3804 tabPoints[6].y = tabPoints[0].y
3805
3806 tabPoints[7].x = tabPoints[0].x
3807 tabPoints[7].y = tabPoints[0].y
3808
3809 self._pagesInfoVec[tabIdx].SetRegion(tabPoints)
3810
3811 # Draw the polygon
3812 br = dc.GetBrush()
3813 dc.SetBrush(wx.Brush((tabIdx == self.GetSelection() and [self._activeTabColor] or [self._colorTo])[0]))
3814 dc.SetPen(wx.Pen((tabIdx == self.GetSelection() and [wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW)] or [self._colorBorder])[0]))
3815 dc.DrawPolygon(tabPoints)
3816
3817 # Restore the brush
3818 dc.SetBrush(br)
3819
3820 rect = self.GetClientRect()
3821
3822 if tabIdx != self.GetSelection() and not self.HasFlag(FNB_BOTTOM):
3823
3824 # Top default tabs
3825 dc.SetPen(wx.Pen(self._colorBorder))
3826 lineY = rect.height
3827 curPen = dc.GetPen()
3828 curPen.SetWidth(1)
3829 dc.SetPen(curPen)
3830 dc.DrawLine(posx, lineY, posx+rect.width, lineY)
3831
3832 # In case we are drawing the selected tab, we draw the border of it as well
3833 # but without the bottom (upper line incase of wxBOTTOM)
3834 if tabIdx == self.GetSelection():
3835
3836 borderPen = wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW))
3837 brush = wx.TRANSPARENT_BRUSH
3838 dc.SetPen(borderPen)
3839 dc.SetBrush(brush)
3840 dc.DrawPolygon(tabPoints)
3841
3842 # Delete the bottom line (or the upper one, incase we use wxBOTTOM)
3843 dc.SetPen(wx.WHITE_PEN)
3844 dc.DrawLine(tabPoints[0].x, tabPoints[0].y, tabPoints[6].x, tabPoints[6].y)
3845
3846 self.FillVC8GradientColor(dc, tabPoints, tabIdx == self.GetSelection(), tabIdx)
3847
3848 # Draw a thin line to the right of the non-selected tab
3849 if tabIdx != self.GetSelection():
3850
3851 dc.SetPen(wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE)))
3852 dc.DrawLine(tabPoints[4].x-1, tabPoints[4].y, tabPoints[5].x-1, tabPoints[5].y)
3853 dc.DrawLine(tabPoints[5].x-1, tabPoints[5].y, tabPoints[6].x-1, tabPoints[6].y)
3854
3855
3856 def FillVC8GradientColor(self, dc, tabPoints, bSelectedTab, tabIdx):
3857 """ Fills a tab with a gradient colour. """
3858
3859 # calculate gradient coefficients
3860 col2 = (self.HasFlag(FNB_BOTTOM) and [self._colorTo] or [self._colorFrom])[0]
3861 col1 = (self.HasFlag(FNB_BOTTOM) and [self._colorFrom] or [self._colorTo])[0]
3862
3863 # If colorful tabs style is set, override the tab color
3864 if self.HasFlag(FNB_COLORFUL_TABS):
3865
3866 if not self._pagesInfoVec[tabIdx].GetColor():
3867
3868 # First time, generate color, and keep it in the vector
3869 tabColor = self.GenTabColour()
3870 self._pagesInfoVec[tabIdx].SetColor(tabColor)
3871
3872 if self.HasFlag(FNB_BOTTOM):
3873
3874 col2 = LightColour(self._pagesInfoVec[tabIdx].GetColor(), 50 )
3875 col1 = LightColour(self._pagesInfoVec[tabIdx].GetColor(), 80 )
3876
3877 else:
3878
3879 col1 = LightColour(self._pagesInfoVec[tabIdx].GetColor(), 50 )
3880 col2 = LightColour(self._pagesInfoVec[tabIdx].GetColor(), 80 )
3881
3882 size = abs(tabPoints[2].y - tabPoints[0].y) - 1
3883
3884 rf, gf, bf = 0, 0, 0
3885 rstep = float(col2.Red() - col1.Red())/float(size)
3886 gstep = float(col2.Green() - col1.Green())/float(size)
3887 bstep = float(col2.Blue() - col1.Blue())/float(size)
3888
3889 y = tabPoints[0].y
3890
3891 # If we are drawing the selected tab, we need also to draw a line
3892 # from 0.tabPoints[0].x and tabPoints[6].x . end, we achieve this
3893 # by drawing the rectangle with transparent brush
3894 # the line under the selected tab will be deleted by the drwaing loop
3895 if bSelectedTab:
3896 self.DrawTabsLine(dc)
3897
3898 while 1:
3899
3900 if self.HasFlag(FNB_BOTTOM):
3901
3902 if y > tabPoints[0].y + size:
3903 break
3904
3905 else:
3906
3907 if y < tabPoints[0].y - size:
3908 break
3909
3910 currCol = wx.Colour(col1.Red() + rf, col1.Green() + gf, col1.Blue() + bf)
3911
3912 dc.SetPen((bSelectedTab and [wx.Pen(self._activeTabColor)] or [wx.Pen(currCol)])[0])
3913 startX = self.GetStartX(tabPoints, y)
3914 endX = self.GetEndX(tabPoints, y)
3915 dc.DrawLine(startX, y, endX, y)
3916
3917 # Draw the border using the 'edge' point
3918 dc.SetPen(wx.Pen((bSelectedTab and [wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW)] or [self._colorBorder])[0]))
3919
3920 dc.DrawPoint(startX, y)
3921 dc.DrawPoint(endX, y)
3922
3923 # Progress the color
3924 rf += rstep
3925 gf += gstep
3926 bf += bstep
3927
3928 if self.HasFlag(FNB_BOTTOM):
3929 y = y + 1
3930 else:
3931 y = y - 1
3932
3933
3934 def GetStartX(self, tabPoints, y):
3935 """ Returns the x start position of a tab. """
3936
3937 x1, x2, y1, y2 = 0.0, 0.0, 0.0, 0.0
3938
3939 # We check the 3 points to the left
3940 style = self.GetParent().GetWindowStyleFlag()
3941 bBottomStyle = (style & FNB_BOTTOM and [True] or [False])[0]
3942 match = False
3943
3944 if bBottomStyle:
3945
3946 for i in xrange(3):
3947
3948 if y >= tabPoints[i].y and y < tabPoints[i+1].y:
3949
3950 x1 = tabPoints[i].x
3951 x2 = tabPoints[i+1].x
3952 y1 = tabPoints[i].y
3953 y2 = tabPoints[i+1].y
3954 match = True
3955 break
3956
3957 else:
3958
3959 for i in xrange(3):
3960
3961 if y <= tabPoints[i].y and y > tabPoints[i+1].y:
3962
3963 x1 = tabPoints[i].x
3964 x2 = tabPoints[i+1].x
3965 y1 = tabPoints[i].y
3966 y2 = tabPoints[i+1].y
3967 match = True
3968 break
3969
3970 if not match:
3971 return tabPoints[2].x
3972
3973 # According to the equation y = ax + b => x = (y-b)/a
3974 # We know the first 2 points
3975
3976 if x2 == x1:
3977 return x2
3978 else:
3979 a = (y2 - y1)/(x2 - x1)
3980
3981 b = y1 - ((y2 - y1)/(x2 - x1))*x1
3982
3983 if a == 0:
3984 return int(x1)
3985
3986 x = (y - b)/a
3987
3988 return int(x)
3989
3990
3991 def GetEndX(self, tabPoints, y):
3992 """ Returns the x end position of a tab. """
3993
3994 x1, x2, y1, y2 = 0.0, 0.0, 0.0, 0.0
3995
3996 # We check the 3 points to the left
3997 style = self.GetParent().GetWindowStyleFlag()
3998 bBottomStyle = (style & FNB_BOTTOM and [True] or [False])[0]
3999 match = False
4000
4001 if bBottomStyle:
4002
4003 for i in xrange(7, 3, -1):
4004
4005 if y >= tabPoints[i].y and y < tabPoints[i-1].y:
4006
4007 x1 = tabPoints[i].x
4008 x2 = tabPoints[i-1].x
4009 y1 = tabPoints[i].y
4010 y2 = tabPoints[i-1].y
4011 match = True
4012 break
4013
4014 else:
4015
4016 for i in xrange(7, 3, -1):
4017
4018 if y <= tabPoints[i].y and y > tabPoints[i-1].y:
4019
4020 x1 = tabPoints[i].x
4021 x2 = tabPoints[i-1].x
4022 y1 = tabPoints[i].y
4023 y2 = tabPoints[i-1].y
4024 match = True
4025 break
4026
4027 if not match:
4028 return tabPoints[3].x
4029
4030 # According to the equation y = ax + b => x = (y-b)/a
4031 # We know the first 2 points
4032
4033 # Vertical line
4034 if x1 == x2:
4035 return int(x1)
4036
4037 a = (y2 - y1)/(x2 - x1)
4038 b = y1 - ((y2 - y1)/(x2 - x1)) * x1
4039
4040 if a == 0:
4041 return int(x1)
4042
4043 x = (y - b)/a
4044
4045 return int(x)
4046
4047
4048 def GenTabColour(self):
4049 """ Generates a random soft pleasant colour for a tab. """
4050
4051 return RandomColor()
4052
4053
4054 def GetSingleLineBorderColor(self):
4055
4056 if self.HasFlag(FNB_VC8):
4057 return self._activeTabColor
4058 else:
4059 return PageContainerBase.GetSingleLineBorderColor(self)
4060
4061
4062 def SetFactor(self, factor):
4063 """ Sets the brighten colour factor. """
4064
4065 self._factor = factor
4066 self.Refresh()
4067
4068
4069 def GetFactor(self):
4070 """ Returns the brighten colour factor. """
4071
4072 return self._factor
4073
4074
4075