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