# --------------------------------------------------------------------------- # # FLATNOTEBOOK Widget wxPython IMPLEMENTATION # # Original C++ Code From Eran. You Can Find It At: # # http://wxforum.shadonet.com/viewtopic.php?t=5761&start=0 # # License: wxWidgets license # # # Python Code By: # # Andrea Gavana, @ 02 Oct 2006 # Latest Revision: 10 Oct 2006, 21.00 GMT # # # For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please # Write To Me At: # # andrea.gavana@gmail.com # gavana@kpo.kz # # Or, Obviously, To The wxPython Mailing List!!! # # # End Of Comments # --------------------------------------------------------------------------- # """ The FlatNotebook is a full implementation of the wx.Notebook, and designed to be a drop-in replacement for wx.Notebook. The API functions are similar so one can expect the function to behave in the same way. Some features: - The buttons are highlighted a la Firefox style - The scrolling is done for bulks of tabs (so, the scrolling is faster and better) - The buttons area is never overdrawn by tabs (unlike many other implementations I saw) - It is a generic control - Currently there are 4 differnt styles - VC8, VC 71, Standard and Fancy - Mouse middle click can be used to close tabs - A function to add right click menu for tabs (simple as SetRightClickMenu) - All styles has bottom style as well (they can be drawn in the bottom of screen) - An option to hide 'X' button or navigation buttons (separately) - Gradient coloring of the selected tabs and border - Support for drag 'n' drop of tabs, both in the same notebook or to another notebook - Possibility to have closing button on the active tab directly - Support for disabled tabs - Colours for active/inactive tabs, and captions - Background of tab area can be painted in gradient (VC8 style only) - Colourful tabs - a random gentle colour is generated for each new tab (very cool, VC8 style only) And much more. License And Version: FlatNotebook Is Freeware And Distributed Under The wxPython License. Latest Revision: Andrea Gavana @ 10 Oct 2006, 21.00 GMT Version 2.0. @undocumented: FNB_HEIGHT_SPACER, VERTICAL_BORDER_PADDING, VC8_SHAPE_LEN, wxEVT*, left_arrow_*, right_arrow*, x_button*, down_arrow*, FNBDragInfo, FNBDropTarget, GetMondrian* """ __docformat__ = "epytext" #---------------------------------------------------------------------- # Beginning Of FLATNOTEBOOK wxPython Code #---------------------------------------------------------------------- import wx import random import math import weakref import cPickle # Check for the new method in 2.7 (not present in 2.6.3.3) if wx.VERSION_STRING < "2.7": wx.Rect.Contains = lambda self, point: wx.Rect.Inside(self, point) FNB_HEIGHT_SPACER = 10 # Use Visual Studio 2003 (VC7.1) style for tabs FNB_VC71 = 1 """Use Visual Studio 2003 (VC7.1) style for tabs""" # Use fancy style - square tabs filled with gradient coloring FNB_FANCY_TABS = 2 """Use fancy style - square tabs filled with gradient coloring""" # Draw thin border around the page FNB_TABS_BORDER_SIMPLE = 4 """Draw thin border around the page""" # Do not display the 'X' button FNB_NO_X_BUTTON = 8 """Do not display the 'X' button""" # Do not display the Right / Left arrows FNB_NO_NAV_BUTTONS = 16 """Do not display the right/left arrows""" # Use the mouse middle button for cloing tabs FNB_MOUSE_MIDDLE_CLOSES_TABS = 32 """Use the mouse middle button for cloing tabs""" # Place tabs at bottom - the default is to place them # at top FNB_BOTTOM = 64 """Place tabs at bottom - the default is to place them at top""" # Disable dragging of tabs FNB_NODRAG = 128 """Disable dragging of tabs""" # Use Visual Studio 2005 (VC8) style for tabs FNB_VC8 = 256 """Use Visual Studio 2005 (VC8) style for tabs""" # Place 'X' on a tab FNB_X_ON_TAB = 512 """Place 'X' close button on the active tab""" FNB_BACKGROUND_GRADIENT = 1024 """Use gradients to paint the tabs background""" FNB_COLORFUL_TABS = 2048 """Use colourful tabs (VC8 style only)""" # Style to close tab using double click - styles 1024, 2048 are reserved FNB_DCLICK_CLOSES_TABS = 4096 """Style to close tab using double click""" FNB_SMART_TABS = 8192 """Use Smart Tabbing, like Alt+Tab on Windows""" FNB_DROPDOWN_TABS_LIST = 16384 """Use a dropdown menu on the left in place of the arrows""" FNB_ALLOW_FOREIGN_DND = 32768 """Allows drag 'n' drop operations between different L{FlatNotebook}s""" VERTICAL_BORDER_PADDING = 4 # Button size is a 16x16 xpm bitmap BUTTON_SPACE = 16 """Button size is a 16x16 xpm bitmap""" VC8_SHAPE_LEN = 16 MASK_COLOR = wx.Colour(0, 128, 128) """Mask colour for the arrow bitmaps""" # Button status FNB_BTN_PRESSED = 2 """Navigation button is pressed""" FNB_BTN_HOVER = 1 """Navigation button is hovered""" FNB_BTN_NONE = 0 """No navigation""" # Hit Test results FNB_TAB = 1 # On a tab """Indicates mouse coordinates inside a tab""" FNB_X = 2 # On the X button """Indicates mouse coordinates inside the I{X} region""" FNB_TAB_X = 3 # On the 'X' button (tab's X button) """Indicates mouse coordinates inside the I{X} region in a tab""" FNB_LEFT_ARROW = 4 # On the rotate left arrow button """Indicates mouse coordinates inside the left arrow region""" FNB_RIGHT_ARROW = 5 # On the rotate right arrow button """Indicates mouse coordinates inside the right arrow region""" FNB_DROP_DOWN_ARROW = 6 # On the drop down arrow button """Indicates mouse coordinates inside the drop down arrow region""" FNB_NOWHERE = 0 # Anywhere else """Indicates mouse coordinates not on any tab of the notebook""" FNB_DEFAULT_STYLE = FNB_MOUSE_MIDDLE_CLOSES_TABS """L{FlatNotebook} default style""" # FlatNotebook Events: # wxEVT_FLATNOTEBOOK_PAGE_CHANGED: Event Fired When You Switch Page; # wxEVT_FLATNOTEBOOK_PAGE_CHANGING: Event Fired When You Are About To Switch # Pages, But You Can Still "Veto" The Page Changing By Avoiding To Call # event.Skip() In Your Event Handler; # wxEVT_FLATNOTEBOOK_PAGE_CLOSING: Event Fired When A Page Is Closing, But # You Can Still "Veto" The Page Changing By Avoiding To Call event.Skip() # In Your Event Handler; # wxEVT_FLATNOTEBOOK_PAGE_CLOSED: Event Fired When A Page Is Closed. # wxEVT_FLATNOTEBOOK_PAGE_CONTEXT_MENU: Event Fired When A Menu Pops-up In A Tab. wxEVT_FLATNOTEBOOK_PAGE_CHANGED = wx.NewEventType() wxEVT_FLATNOTEBOOK_PAGE_CHANGING = wx.NewEventType() wxEVT_FLATNOTEBOOK_PAGE_CLOSING = wx.NewEventType() wxEVT_FLATNOTEBOOK_PAGE_CLOSED = wx.NewEventType() wxEVT_FLATNOTEBOOK_PAGE_CONTEXT_MENU = wx.NewEventType() #-----------------------------------# # FlatNotebookEvent #-----------------------------------# EVT_FLATNOTEBOOK_PAGE_CHANGED = wx.PyEventBinder(wxEVT_FLATNOTEBOOK_PAGE_CHANGED, 1) """Notify client objects when the active page in L{FlatNotebook} has changed.""" EVT_FLATNOTEBOOK_PAGE_CHANGING = wx.PyEventBinder(wxEVT_FLATNOTEBOOK_PAGE_CHANGING, 1) """Notify client objects when the active page in L{FlatNotebook} is about to change.""" EVT_FLATNOTEBOOK_PAGE_CLOSING = wx.PyEventBinder(wxEVT_FLATNOTEBOOK_PAGE_CLOSING, 1) """Notify client objects when a page in L{FlatNotebook} is closing.""" EVT_FLATNOTEBOOK_PAGE_CLOSED = wx.PyEventBinder(wxEVT_FLATNOTEBOOK_PAGE_CLOSED, 1) """Notify client objects when a page in L{FlatNotebook} has been closed.""" EVT_FLATNOTEBOOK_PAGE_CONTEXT_MENU = wx.PyEventBinder(wxEVT_FLATNOTEBOOK_PAGE_CONTEXT_MENU, 1) """Notify client objects when a pop-up menu should appear next to a tab.""" # Some icons in XPM format left_arrow_disabled_xpm = [ " 16 16 8 1", "` c #008080", ". c #555555", "# c #000000", "a c #000000", "b c #000000", "c c #000000", "d c #000000", "e c #000000", "````````````````", "````````````````", "````````````````", "````````.```````", "```````..```````", "``````.`.```````", "`````.``.```````", "````.```.```````", "`````.``.```````", "``````.`.```````", "```````..```````", "````````.```````", "````````````````", "````````````````", "````````````````", "````````````````" ] x_button_pressed_xpm = [ " 16 16 8 1", "` c #008080", ". c #4766e0", "# c #9e9ede", "a c #000000", "b c #000000", "c c #000000", "d c #000000", "e c #000000", "````````````````", "`..............`", "`.############.`", "`.############.`", "`.############.`", "`.###aa####aa#.`", "`.####aa##aa##.`", "`.#####aaaa###.`", "`.######aa####.`", "`.#####aaaa###.`", "`.####aa##aa##.`", "`.###aa####aa#.`", "`.############.`", "`..............`", "````````````````", "````````````````" ] left_arrow_xpm = [ " 16 16 8 1", "` c #008080", ". c #555555", "# c #000000", "a c #000000", "b c #000000", "c c #000000", "d c #000000", "e c #000000", "````````````````", "````````````````", "````````````````", "````````.```````", "```````..```````", "``````...```````", "`````....```````", "````.....```````", "`````....```````", "``````...```````", "```````..```````", "````````.```````", "````````````````", "````````````````", "````````````````", "````````````````" ] x_button_hilite_xpm = [ " 16 16 8 1", "` c #008080", ". c #4766e0", "# c #c9dafb", "a c #000000", "b c #000000", "c c #000000", "d c #000000", "e c #000000", "````````````````", "`..............`", "`.############.`", "`.############.`", "`.##aa####aa##.`", "`.###aa##aa###.`", "`.####aaaa####.`", "`.#####aa#####.`", "`.####aaaa####.`", "`.###aa##aa###.`", "`.##aa####aa##.`", "`.############.`", "`.############.`", "`..............`", "````````````````", "````````````````" ] x_button_xpm = [ " 16 16 8 1", "` c #008080", ". c #555555", "# c #000000", "a c #000000", "b c #000000", "c c #000000", "d c #000000", "e c #000000", "````````````````", "````````````````", "````````````````", "````````````````", "````..````..````", "`````..``..`````", "``````....``````", "```````..```````", "``````....``````", "`````..``..`````", "````..````..````", "````````````````", "````````````````", "````````````````", "````````````````", "````````````````" ] left_arrow_pressed_xpm = [ " 16 16 8 1", "` c #008080", ". c #4766e0", "# c #9e9ede", "a c #000000", "b c #000000", "c c #000000", "d c #000000", "e c #000000", "````````````````", "`..............`", "`.############.`", "`.############.`", "`.#######a####.`", "`.######aa####.`", "`.#####aaa####.`", "`.####aaaa####.`", "`.###aaaaa####.`", "`.####aaaa####.`", "`.#####aaa####.`", "`.######aa####.`", "`.#######a####.`", "`..............`", "````````````````", "````````````````" ] left_arrow_hilite_xpm = [ " 16 16 8 1", "` c #008080", ". c #4766e0", "# c #c9dafb", "a c #000000", "b c #000000", "c c #000000", "d c #000000", "e c #000000", "````````````````", "`..............`", "`.############.`", "`.######a#####.`", "`.#####aa#####.`", "`.####aaa#####.`", "`.###aaaa#####.`", "`.##aaaaa#####.`", "`.###aaaa#####.`", "`.####aaa#####.`", "`.#####aa#####.`", "`.######a#####.`", "`.############.`", "`..............`", "````````````````", "````````````````" ] right_arrow_disabled_xpm = [ " 16 16 8 1", "` c #008080", ". c #555555", "# c #000000", "a c #000000", "b c #000000", "c c #000000", "d c #000000", "e c #000000", "````````````````", "````````````````", "````````````````", "```````.````````", "```````..```````", "```````.`.``````", "```````.``.`````", "```````.```.````", "```````.``.`````", "```````.`.``````", "```````..```````", "```````.````````", "````````````````", "````````````````", "````````````````", "````````````````" ] right_arrow_hilite_xpm = [ " 16 16 8 1", "` c #008080", ". c #4766e0", "# c #c9dafb", "a c #000000", "b c #000000", "c c #000000", "d c #000000", "e c #000000", "````````````````", "`..............`", "`.############.`", "`.####a#######.`", "`.####aa######.`", "`.####aaa#####.`", "`.####aaaa####.`", "`.####aaaaa###.`", "`.####aaaa####.`", "`.####aaa#####.`", "`.####aa######.`", "`.####a#######.`", "`.############.`", "`..............`", "````````````````", "````````````````" ] right_arrow_pressed_xpm = [ " 16 16 8 1", "` c #008080", ". c #4766e0", "# c #9e9ede", "a c #000000", "b c #000000", "c c #000000", "d c #000000", "e c #000000", "````````````````", "`..............`", "`.############.`", "`.############.`", "`.#####a######.`", "`.#####aa#####.`", "`.#####aaa####.`", "`.#####aaaa###.`", "`.#####aaaaa##.`", "`.#####aaaa###.`", "`.#####aaa####.`", "`.#####aa#####.`", "`.#####a######.`", "`..............`", "````````````````", "````````````````" ] right_arrow_xpm = [ " 16 16 8 1", "` c #008080", ". c #555555", "# c #000000", "a c #000000", "b c #000000", "c c #000000", "d c #000000", "e c #000000", "````````````````", "````````````````", "````````````````", "```````.````````", "```````..```````", "```````...``````", "```````....`````", "```````.....````", "```````....`````", "```````...``````", "```````..```````", "```````.````````", "````````````````", "````````````````", "````````````````", "````````````````" ] down_arrow_hilite_xpm = [ " 16 16 8 1", "` c #008080", ". c #4766e0", "# c #c9dafb", "a c #000000", "b c #000000", "c c #000000", "d c #000000", "e c #000000", "````````````````", "``.............`", "``.###########.`", "``.###########.`", "``.###########.`", "``.#aaaaaaaaa#.`", "``.##aaaaaaa##.`", "``.###aaaaa###.`", "``.####aaa####.`", "``.#####a#####.`", "``.###########.`", "``.###########.`", "``.###########.`", "``.............`", "````````````````", "````````````````" ] down_arrow_pressed_xpm = [ " 16 16 8 1", "` c #008080", ". c #4766e0", "# c #9e9ede", "a c #000000", "b c #000000", "c c #000000", "d c #000000", "e c #000000", "````````````````", "``.............`", "``.###########.`", "``.###########.`", "``.###########.`", "``.###########.`", "``.###########.`", "``.#aaaaaaaaa#.`", "``.##aaaaaaa##.`", "``.###aaaaa###.`", "``.####aaa####.`", "``.#####a#####.`", "``.###########.`", "``.............`", "````````````````", "````````````````" ] down_arrow_xpm = [ " 16 16 8 1", "` c #008080", ". c #000000", "# c #000000", "a c #000000", "b c #000000", "c c #000000", "d c #000000", "e c #000000", "````````````````", "````````````````", "````````````````", "````````````````", "````````````````", "````````````````", "````.........```", "`````.......````", "``````.....`````", "```````...``````", "````````.```````", "````````````````", "````````````````", "````````````````", "````````````````", "````````````````" ] #---------------------------------------------------------------------- def GetMondrianData(): return \ '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\x00\x00 \x08\x06\x00\ \x00\x00szz\xf4\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\x00\x00\x00qID\ ATX\x85\xed\xd6;\n\x800\x10E\xd1{\xc5\x8d\xb9r\x97\x16\x0b\xad$\x8a\x82:\x16\ o\xda\x84pB2\x1f\x81Fa\x8c\x9c\x08\x04Z{\xcf\xa72\xbcv\xfa\xc5\x08 \x80r\x80\ \xfc\xa2\x0e\x1c\xe4\xba\xfaX\x1d\xd0\xde]S\x07\x02\xd8>\xe1wa-`\x9fQ\xe9\ \x86\x01\x04\x10\x00\\(Dk\x1b-\x04\xdc\x1d\x07\x14\x98;\x0bS\x7f\x7f\xf9\x13\ \x04\x10@\xf9X\xbe\x00\xc9 \x14K\xc1<={\x00\x00\x00\x00IEND\xaeB`\x82' def GetMondrianBitmap(): return wx.BitmapFromImage(GetMondrianImage().Scale(16, 16)) def GetMondrianImage(): import cStringIO stream = cStringIO.StringIO(GetMondrianData()) return wx.ImageFromStream(stream) def GetMondrianIcon(): icon = wx.EmptyIcon() icon.CopyFromBitmap(GetMondrianBitmap()) return icon #---------------------------------------------------------------------- def LightColour(color, percent): """ Brighten input colour by percent. """ end_color = wx.WHITE rd = end_color.Red() - color.Red() gd = end_color.Green() - color.Green() bd = end_color.Blue() - color.Blue() high = 100 # We take the percent way of the color from color -. white i = percent r = color.Red() + ((i*rd*100)/high)/100 g = color.Green() + ((i*gd*100)/high)/100 b = color.Blue() + ((i*bd*100)/high)/100 return wx.Colour(r, g, b) def RandomColour(): """ Creates a random colour. """ r = random.randint(0, 255) # Random value betweem 0-255 g = random.randint(0, 255) # Random value betweem 0-255 b = random.randint(0, 255) # Random value betweem 0-255 return wx.Colour(r, g, b) def PaintStraightGradientBox(dc, rect, startColor, endColor, vertical=True): """ Draws a gradient colored box from startColor to endColor. """ rd = endColor.Red() - startColor.Red() gd = endColor.Green() - startColor.Green() bd = endColor.Blue() - startColor.Blue() # Save the current pen and brush savedPen = dc.GetPen() savedBrush = dc.GetBrush() if vertical: high = rect.GetHeight()-1 else: high = rect.GetWidth()-1 if high < 1: return for i in xrange(high+1): r = startColor.Red() + ((i*rd*100)/high)/100 g = startColor.Green() + ((i*gd*100)/high)/100 b = startColor.Blue() + ((i*bd*100)/high)/100 p = wx.Pen(wx.Colour(r, g, b)) dc.SetPen(p) if vertical: dc.DrawLine(rect.x, rect.y+i, rect.x+rect.width, rect.y+i) else: dc.DrawLine(rect.x+i, rect.y, rect.x+i, rect.y+rect.height) # Restore the pen and brush dc.SetPen(savedPen) dc.SetBrush(savedBrush) # ---------------------------------------------------------------------------- # # Class FNBDragInfo # Stores All The Information To Allow Drag And Drop Between Different # FlatNotebooks. # ---------------------------------------------------------------------------- # class FNBDragInfo: _map = weakref.WeakValueDictionary() def __init__(self, container, pageindex): """ Default class constructor. """ self._id = id(container) FNBDragInfo._map[self._id] = container self._pageindex = pageindex def GetContainer(self): """ Returns the L{FlatNotebook} page (usually a panel). """ return FNBDragInfo._map.get(self._id, None) def GetPageIndex(self): """ Returns the page index associated with a page. """ return self._pageindex # ---------------------------------------------------------------------------- # # Class FNBDropTarget # Simply Used To Handle The OnDrop() Method When Dragging And Dropping Between # Different FlatNotebooks. # ---------------------------------------------------------------------------- # class FNBDropTarget(wx.DropTarget): def __init__(self, parent): """ Default class constructor. """ wx.DropTarget.__init__(self) self._parent = parent self._dataobject = wx.CustomDataObject(wx.CustomDataFormat("FlatNotebook")) self.SetDataObject(self._dataobject) def OnData(self, x, y, dragres): """ Handles the OnData() method to call the real DnD routine. """ if not self.GetData(): return wx.DragNone draginfo = self._dataobject.GetData() drginfo = cPickle.loads(draginfo) return self._parent.OnDropTarget(x, y, drginfo.GetPageIndex(), drginfo.GetContainer()) # ---------------------------------------------------------------------------- # # Class PageInfo # Contains parameters for every FlatNotebook page # ---------------------------------------------------------------------------- # class PageInfo: """ This class holds all the information (caption, image, etc...) belonging to a single tab in L{FlatNotebook}. """ def __init__(self, caption="", imageindex=-1, tabangle=0, enabled=True): """ Default Class Constructor. Parameters: @param caption: the tab caption; @param imageindex: the tab image index based on the assigned (set) wx.ImageList (if any); @param tabangle: the tab angle (only on standard tabs, from 0 to 15 degrees); @param enabled: sets enabled or disabled the tab. """ self._strCaption = caption self._TabAngle = tabangle self._ImageIndex = imageindex self._bEnabled = enabled self._pos = wx.Point(-1, -1) self._size = wx.Size(-1, -1) self._region = wx.Region() self._xRect = wx.Rect() self._color = None def SetCaption(self, value): """ Sets the tab caption. """ self._strCaption = value def GetCaption(self): """ Returns the tab caption. """ return self._strCaption def SetPosition(self, value): """ Sets the tab position. """ self._pos = value def GetPosition(self): """ Returns the tab position. """ return self._pos def SetSize(self, value): """ Sets the tab size. """ self._size = value def GetSize(self): """ Returns the tab size. """ return self._size def SetTabAngle(self, value): """ Sets the tab header angle (0 <= tab <= 15 degrees). """ self._TabAngle = min(45, value) def GetTabAngle(self): """ Returns the tab angle. """ return self._TabAngle def SetImageIndex(self, value): """ Sets the tab image index. """ self._ImageIndex = value def GetImageIndex(self): """ Returns the tab umage index. """ return self._ImageIndex def GetEnabled(self): """ Returns whether the tab is enabled or not. """ return self._bEnabled def Enable(self, enabled): """ Sets the tab enabled or disabled. """ self._bEnabled = enabled def SetRegion(self, points=[]): """ Sets the tab region. """ self._region = wx.RegionFromPoints(points) def GetRegion(self): """ Returns the tab region. """ return self._region def SetXRect(self, xrect): """ Sets the button 'X' area rect. """ self._xRect = xrect def GetXRect(self): """ Returns the button 'X' area rect. """ return self._xRect def GetColour(self): """ Returns the tab colour. """ return self._color def SetColour(self, color): """ Sets the tab colour. """ self._color = color # ---------------------------------------------------------------------------- # # Class FlatNotebookEvent # ---------------------------------------------------------------------------- # class FlatNotebookEvent(wx.PyCommandEvent): """ This events will be sent when a EVT_FLATNOTEBOOK_PAGE_CHANGED, EVT_FLATNOTEBOOK_PAGE_CHANGING, EVT_FLATNOTEBOOK_PAGE_CLOSING, EVT_FLATNOTEBOOK_PAGE_CLOSED and EVT_FLATNOTEBOOK_PAGE_CONTEXT_MENU is mapped in the parent. """ def __init__(self, eventType, id=1, nSel=-1, nOldSel=-1): """ Default class constructor. """ wx.PyCommandEvent.__init__(self, eventType, id) self._eventType = eventType self.notify = wx.NotifyEvent(eventType, id) def GetNotifyEvent(self): """Returns the actual wx.NotifyEvent.""" return self.notify def IsAllowed(self): """Returns whether the event is allowed or not.""" return self.notify.IsAllowed() def Veto(self): """Vetos the event.""" self.notify.Veto() def Allow(self): """The event is allowed.""" self.notify.Allow() def SetSelection(self, nSel): """ Sets event selection. """ self._selection = nSel def SetOldSelection(self, nOldSel): """ Sets old event selection. """ self._oldselection = nOldSel def GetSelection(self): """ Returns event selection. """ return self._selection def GetOldSelection(self): """ Returns old event selection """ return self._oldselection # ---------------------------------------------------------------------------- # # Class TabNavigatorWindow # ---------------------------------------------------------------------------- # class TabNavigatorWindow(wx.Dialog): """ This class is used to create a modal dialog that enables "Smart Tabbing", similar to what you would get by hitting Alt+Tab on Windows. """ def __init__(self, parent=None): """ Default class constructor. Used internally.""" wx.Dialog.__init__(self, parent, wx.ID_ANY, "", style=0) self._selectedItem = -1 self._indexMap = [] self._bmp = GetMondrianBitmap() sz = wx.BoxSizer(wx.VERTICAL) self._listBox = wx.ListBox(self, wx.ID_ANY, wx.DefaultPosition, wx.Size(200, 150), [], wx.LB_SINGLE | wx.NO_BORDER) mem_dc = wx.MemoryDC() mem_dc.SelectObject(wx.EmptyBitmap(10,10)) font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) font.SetWeight(wx.BOLD) mem_dc.SetFont(font) panelHeight = mem_dc.GetCharHeight() panelHeight += 4 # Place a spacer of 2 pixels # Out signpost bitmap is 24 pixels if panelHeight < 24: panelHeight = 24 self._panel = wx.Panel(self, wx.ID_ANY, wx.DefaultPosition, wx.Size(200, panelHeight)) sz.Add(self._panel) sz.Add(self._listBox, 1, wx.EXPAND) self.SetSizer(sz) # Connect events to the list box self._listBox.Bind(wx.EVT_KEY_UP, self.OnKeyUp) self._listBox.Bind(wx.EVT_NAVIGATION_KEY, self.OnNavigationKey) self._listBox.Bind(wx.EVT_LISTBOX_DCLICK, self.OnItemSelected) # Connect paint event to the panel self._panel.Bind(wx.EVT_PAINT, self.OnPanelPaint) self._panel.Bind(wx.EVT_ERASE_BACKGROUND, self.OnPanelEraseBg) self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE)) self._listBox.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE)) self.PopulateListControl(parent) self.GetSizer().Fit(self) self.GetSizer().SetSizeHints(self) self.GetSizer().Layout() self.Centre() def OnKeyUp(self, event): """Handles the wx.EVT_KEY_UP for the L{TabNavigatorWindow}.""" if event.GetKeyCode() == wx.WXK_CONTROL: self.CloseDialog() def OnNavigationKey(self, event): """Handles the wx.EVT_NAVIGATION_KEY for the L{TabNavigatorWindow}. """ selected = self._listBox.GetSelection() bk = self.GetParent() maxItems = bk.GetPageCount() if event.GetDirection(): # Select next page if selected == maxItems - 1: itemToSelect = 0 else: itemToSelect = selected + 1 else: # Previous page if selected == 0: itemToSelect = maxItems - 1 else: itemToSelect = selected - 1 self._listBox.SetSelection(itemToSelect) def PopulateListControl(self, book): """Populates the L{TabNavigatorWindow} listbox with a list of tabs.""" selection = book.GetSelection() count = book.GetPageCount() self._listBox.Append(book.GetPageText(selection)) self._indexMap.append(selection) prevSel = book.GetPreviousSelection() if prevSel != wx.NOT_FOUND: # Insert the previous selection as second entry self._listBox.Append(book.GetPageText(prevSel)) self._indexMap.append(prevSel) for c in xrange(count): # Skip selected page if c == selection: continue # Skip previous selected page as well if c == prevSel: continue self._listBox.Append(book.GetPageText(c)) self._indexMap.append(c) # Select the next entry after the current selection self._listBox.SetSelection(0) dummy = wx.NavigationKeyEvent() dummy.SetDirection(True) self.OnNavigationKey(dummy) def OnItemSelected(self, event): """Handles the wx.EVT_LISTBOX_DCLICK event for the wx.ListBox inside L{TabNavigatorWindow}. """ self.CloseDialog() def CloseDialog(self): """Closes the L{TabNavigatorWindow} dialog, setting selection in L{FlatNotebook}.""" bk = self.GetParent() self._selectedItem = self._listBox.GetSelection() iter = self._indexMap[self._selectedItem] bk.SetSelection(iter) self.EndModal(wx.ID_OK) def OnPanelPaint(self, event): """Handles the wx.EVT_PAINT event for L{TabNavigatorWindow} top panel. """ dc = wx.PaintDC(self._panel) rect = self._panel.GetClientRect() bmp = wx.EmptyBitmap(rect.width, rect.height) mem_dc = wx.MemoryDC() mem_dc.SelectObject(bmp) endColour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW) startColour = LightColour(endColour, 50) PaintStraightGradientBox(mem_dc, rect, startColour, endColour) # Draw the caption title and place the bitmap # get the bitmap optimal position, and draw it bmpPt, txtPt = wx.Point(), wx.Point() bmpPt.y = (rect.height - self._bmp.GetHeight())/2 bmpPt.x = 3 mem_dc.DrawBitmap(self._bmp, bmpPt.x, bmpPt.y, True) # get the text position, and draw it font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) font.SetWeight(wx.BOLD) mem_dc.SetFont(font) fontHeight = mem_dc.GetCharHeight() txtPt.x = bmpPt.x + self._bmp.GetWidth() + 4 txtPt.y = (rect.height - fontHeight)/2 mem_dc.SetTextForeground(wx.WHITE) mem_dc.DrawText("Opened tabs:", txtPt.x, txtPt.y) mem_dc.SelectObject(wx.NullBitmap) dc.DrawBitmap(bmp, 0, 0) def OnPanelEraseBg(self, event): """Handles the wx.EVT_ERASE_BACKGROUND event for L{TabNavigatorWindow} top panel. """ pass # ---------------------------------------------------------------------------- # # Class FNBRenderer # ---------------------------------------------------------------------------- # class FNBRenderer: """ Parent class for the 4 renderers defined: I{Standard}, I{VC71}, I{Fancy} and I{VC8}. This class implements the common methods of all 4 renderers. @undocumented: _GetBitmap* """ def __init__(self): """Default class constructor. """ self._tabXBgBmp = wx.EmptyBitmap(16, 16) self._xBgBmp = wx.EmptyBitmap(16, 14) self._leftBgBmp = wx.EmptyBitmap(16, 14) self._rightBgBmp = wx.EmptyBitmap(16, 14) def GetLeftButtonPos(self, pageContainer): """ Returns the left button position in the navigation area. """ pc = pageContainer style = pc.GetParent().GetWindowStyleFlag() rect = pc.GetClientRect() clientWidth = rect.width if style & FNB_NO_X_BUTTON: return clientWidth - 38 else: return clientWidth - 54 def GetRightButtonPos(self, pageContainer): """ Returns the right button position in the navigation area. """ pc = pageContainer style = pc.GetParent().GetWindowStyleFlag() rect = pc.GetClientRect() clientWidth = rect.width if style & FNB_NO_X_BUTTON: return clientWidth - 22 else: return clientWidth - 38 def GetDropArrowButtonPos(self, pageContainer): """ Returns the drop down button position in the navigation area. """ return self.GetRightButtonPos(pageContainer) def GetXPos(self, pageContainer): """ Returns the 'X' button position in the navigation area. """ pc = pageContainer style = pc.GetParent().GetWindowStyleFlag() rect = pc.GetClientRect() clientWidth = rect.width if style & FNB_NO_X_BUTTON: return clientWidth else: return clientWidth - 22 def GetButtonsAreaLength(self, pageContainer): """ Returns the navigation area width. """ pc = pageContainer style = pc.GetParent().GetWindowStyleFlag() # '' if style & FNB_NO_NAV_BUTTONS and style & FNB_NO_X_BUTTON and not style & FNB_DROPDOWN_TABS_LIST: return 0 # 'x' elif style & FNB_NO_NAV_BUTTONS and not style & FNB_NO_X_BUTTON and not style & FNB_DROPDOWN_TABS_LIST: return 22 # '<>' if not style & FNB_NO_NAV_BUTTONS and style & FNB_NO_X_BUTTON and not style & FNB_DROPDOWN_TABS_LIST: return 53 - 16 # 'vx' if style & FNB_DROPDOWN_TABS_LIST and not style & FNB_NO_X_BUTTON: return 22 + 16 # 'v' if style & FNB_DROPDOWN_TABS_LIST and style & FNB_NO_X_BUTTON: return 22 # '<>x' return 53 def DrawLeftArrow(self, pageContainer, dc): """ Draw the left navigation arrow. """ pc = pageContainer style = pc.GetParent().GetWindowStyleFlag() if style & FNB_NO_NAV_BUTTONS: return # Make sure that there are pages in the container if not pc._pagesInfoVec: return # Set the bitmap according to the button status if pc._nLeftButtonStatus == FNB_BTN_HOVER: arrowBmp = wx.BitmapFromXPMData(left_arrow_hilite_xpm) elif pc._nLeftButtonStatus == FNB_BTN_PRESSED: arrowBmp = wx.BitmapFromXPMData(left_arrow_pressed_xpm) else: arrowBmp = wx.BitmapFromXPMData(left_arrow_xpm) if pc._nFrom == 0: # Handle disabled arrow arrowBmp = wx.BitmapFromXPMData(left_arrow_disabled_xpm) arrowBmp.SetMask(wx.Mask(arrowBmp, MASK_COLOR)) # Erase old bitmap posx = self.GetLeftButtonPos(pc) dc.DrawBitmap(self._leftBgBmp, posx, 6) # Draw the new bitmap dc.DrawBitmap(arrowBmp, posx, 6, True) def DrawRightArrow(self, pageContainer, dc): """ Draw the right navigation arrow. """ pc = pageContainer style = pc.GetParent().GetWindowStyleFlag() if style & FNB_NO_NAV_BUTTONS: return # Make sure that there are pages in the container if not pc._pagesInfoVec: return # Set the bitmap according to the button status if pc._nRightButtonStatus == FNB_BTN_HOVER: arrowBmp = wx.BitmapFromXPMData(right_arrow_hilite_xpm) elif pc._nRightButtonStatus == FNB_BTN_PRESSED: arrowBmp = wx.BitmapFromXPMData(right_arrow_pressed_xpm) else: arrowBmp = wx.BitmapFromXPMData(right_arrow_xpm) # Check if the right most tab is visible, if it is # don't rotate right anymore if pc._pagesInfoVec[-1].GetPosition() != wx.Point(-1, -1): arrowBmp = wx.BitmapFromXPMData(right_arrow_disabled_xpm) arrowBmp.SetMask(wx.Mask(arrowBmp, MASK_COLOR)) # erase old bitmap posx = self.GetRightButtonPos(pc) dc.DrawBitmap(self._rightBgBmp, posx, 6) # Draw the new bitmap dc.DrawBitmap(arrowBmp, posx, 6, True) def DrawDropDownArrow(self, pageContainer, dc): """ Draws the drop-down arrow in the navigation area. """ pc = pageContainer # Check if this style is enabled style = pc.GetParent().GetWindowStyleFlag() if not style & FNB_DROPDOWN_TABS_LIST: return # Make sure that there are pages in the container if not pc._pagesInfoVec: return if pc._nArrowDownButtonStatus == FNB_BTN_HOVER: downBmp = wx.BitmapFromXPMData(down_arrow_hilite_xpm) elif pc._nArrowDownButtonStatus == FNB_BTN_PRESSED: downBmp = wx.BitmapFromXPMData(down_arrow_pressed_xpm) else: downBmp = wx.BitmapFromXPMData(down_arrow_xpm) downBmp.SetMask(wx.Mask(downBmp, MASK_COLOR)) # erase old bitmap posx = self.GetDropArrowButtonPos(pc) dc.DrawBitmap(self._xBgBmp, posx, 6) # Draw the new bitmap dc.DrawBitmap(downBmp, posx, 6, True) def DrawX(self, pageContainer, dc): """ Draw the 'X' navigation button in the navigation area. """ pc = pageContainer # Check if this style is enabled style = pc.GetParent().GetWindowStyleFlag() if style & FNB_NO_X_BUTTON: return # Make sure that there are pages in the container if not pc._pagesInfoVec: return # Set the bitmap according to the button status if pc._nXButtonStatus == FNB_BTN_HOVER: xbmp = wx.BitmapFromXPMData(x_button_hilite_xpm) elif pc._nXButtonStatus == FNB_BTN_PRESSED: xbmp = wx.BitmapFromXPMData(x_button_pressed_xpm) else: xbmp = wx.BitmapFromXPMData(x_button_xpm) xbmp.SetMask(wx.Mask(xbmp, MASK_COLOR)) # erase old bitmap posx = self.GetXPos(pc) dc.DrawBitmap(self._xBgBmp, posx, 6) # Draw the new bitmap dc.DrawBitmap(xbmp, posx, 6, True) def DrawTabX(self, pageContainer, dc, rect, tabIdx, btnStatus): """ Draws the 'X' in the selected tab. """ pc = pageContainer if not pc.HasFlag(FNB_X_ON_TAB): return # We draw the 'x' on the active tab only if tabIdx != pc.GetSelection() or tabIdx < 0: return # Set the bitmap according to the button status if btnStatus == FNB_BTN_HOVER: xBmp = wx.BitmapFromXPMData(x_button_hilite_xpm) elif btnStatus == FNB_BTN_PRESSED: xBmp = wx.BitmapFromXPMData(x_button_pressed_xpm) else: xBmp = wx.BitmapFromXPMData(x_button_xpm) # Set the masking xBmp.SetMask(wx.Mask(xBmp, MASK_COLOR)) # erase old button dc.DrawBitmap(self._tabXBgBmp, rect.x, rect.y) # Draw the new bitmap dc.DrawBitmap(xBmp, rect.x, rect.y, True) # Update the vector rr = wx.Rect(rect.x, rect.y, 14, 13) pc._pagesInfoVec[tabIdx].SetXRect(rr) def _GetBitmap(self, dc, rect, bmp): mem_dc = wx.MemoryDC() mem_dc.SelectObject(bmp) mem_dc.Blit(0, 0, rect.width, rect.height, dc, rect.x, rect.y) mem_dc.SelectObject(wx.NullBitmap) return bmp def DrawTabsLine(self, pageContainer, dc): """ Draws a line over the tabs. """ pc = pageContainer clntRect = pc.GetClientRect() clientRect3 = wx.Rect(0, 0, clntRect.width, clntRect.height) if pc.HasFlag(FNB_BOTTOM): clientRect = wx.Rect(0, 2, clntRect.width, clntRect.height - 2) clientRect2 = wx.Rect(0, 1, clntRect.width, clntRect.height - 1) else: clientRect = wx.Rect(0, 0, clntRect.width, clntRect.height - 2) clientRect2 = wx.Rect(0, 0, clntRect.width, clntRect.height - 1) dc.SetBrush(wx.TRANSPARENT_BRUSH) dc.SetPen(wx.Pen(pc.GetSingleLineBorderColour())) dc.DrawRectangleRect(clientRect2) dc.DrawRectangleRect(clientRect3) dc.SetPen(wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW))) dc.DrawRectangleRect(clientRect) if not pc.HasFlag(FNB_TABS_BORDER_SIMPLE): dc.SetPen(wx.Pen((pc.HasFlag(FNB_VC71) and [wx.Colour(247, 243, 233)] or [pc._tabAreaColor])[0])) dc.DrawLine(0, 0, 0, clientRect.height+1) if pc.HasFlag(FNB_BOTTOM): dc.DrawLine(0, clientRect.height+1, clientRect.width, clientRect.height+1) else: dc.DrawLine(0, 0, clientRect.width, 0) dc.DrawLine(clientRect.width - 1, 0, clientRect.width - 1, clientRect.height+1) def CalcTabWidth(self, pageContainer, tabIdx, tabHeight): """ Calculates the width of the input tab. """ pc = pageContainer dc = wx.MemoryDC() dc.SelectObject(wx.EmptyBitmap(10,10)) boldFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) boldFont.SetWeight(wx.FONTWEIGHT_BOLD) if pc.IsDefaultTabs(): shapePoints = int(tabHeight*math.tan(float(pc._pagesInfoVec[tabIdx].GetTabAngle())/180.0*math.pi)) # Calculate the text length using the bold font, so when selecting a tab # its width will not change dc.SetFont(boldFont) width, pom = dc.GetTextExtent(pc.GetPageText(tabIdx)) # Set a minimum size to a tab if width < 20: width = 20 tabWidth = 2*pc._pParent.GetPadding() + width # Style to add a small 'x' button on the top right # of the tab if pc.HasFlag(FNB_X_ON_TAB) and tabIdx == pc.GetSelection(): # The xpm image that contains the 'x' button is 9 pixels spacer = 9 if pc.HasFlag(FNB_VC8): spacer = 4 tabWidth += pc._pParent.GetPadding() + spacer if pc.IsDefaultTabs(): # Default style tabWidth += 2*shapePoints hasImage = pc._ImageList != None and pc._pagesInfoVec[tabIdx].GetImageIndex() != -1 # For VC71 style, we only add the icon size (16 pixels) if hasImage: if not pc.IsDefaultTabs(): tabWidth += 16 + pc._pParent.GetPadding() else: # Default style tabWidth += 16 + pc._pParent.GetPadding() + shapePoints/2 return tabWidth def CalcTabHeight(self, pageContainer): """ Calculates the height of the input tab. """ pc = pageContainer dc = wx.MemoryDC() dc.SelectObject(wx.EmptyBitmap(10,10)) # For GTK it seems that we must do this steps in order # for the tabs will get the proper height on initialization # on MSW, preforming these steps yields wierd results normalFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) boldFont = normalFont boldFont.SetWeight(wx.FONTWEIGHT_BOLD) dc.SetFont(boldFont) height = dc.GetCharHeight() tabHeight = height + FNB_HEIGHT_SPACER # We use 8 pixels as padding if "__WXGTK__" in wx.PlatformInfo: # On GTK the tabs are should be larger tabHeight += 6 if pc.HasFlag(FNB_VC71): tabHeight = (pc.HasFlag(FNB_BOTTOM) and [tabHeight - 4] or [tabHeight])[0] elif pc.HasFlag(FNB_FANCY_TABS): tabHeight = (pc.HasFlag(FNB_BOTTOM) and [tabHeight - 2] or [tabHeight])[0] return tabHeight def DrawTabs(self, pageContainer, dc): """ Actually draws the tabs in L{FlatNotebook}.""" pc = pageContainer if "__WXMAC__" in wx.PlatformInfo: # Works well on MSW & GTK, however this lines should be skipped on MAC if not pc._pagesInfoVec or pc._nFrom >= len(pc._pagesInfoVec): pc.Hide() return # Get the text hight tabHeight = self.CalcTabHeight(pageContainer) style = pc.GetParent().GetWindowStyleFlag() # Calculate the number of rows required for drawing the tabs rect = pc.GetClientRect() clientWidth = rect.width # Set the maximum client size pc.SetSizeHints(self.GetButtonsAreaLength(pc), tabHeight) borderPen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)) if style & FNB_VC71: backBrush = wx.Brush(wx.Colour(247, 243, 233)) else: backBrush = wx.Brush(pc._tabAreaColor) noselBrush = wx.Brush(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNFACE)) selBrush = wx.Brush(pc._activeTabColor) size = pc.GetSize() # Background dc.SetTextBackground((style & FNB_VC71 and [wx.Colour(247, 243, 233)] or [pc.GetBackgroundColour()])[0]) dc.SetTextForeground(pc._activeTextColor) dc.SetBrush(backBrush) # If border style is set, set the pen to be border pen if pc.HasFlag(FNB_TABS_BORDER_SIMPLE): dc.SetPen(borderPen) else: colr = (pc.HasFlag(FNB_VC71) and [wx.Colour(247, 243, 233)] or [pc.GetBackgroundColour()])[0] dc.SetPen(wx.Pen(colr)) dc.DrawRectangle(0, 0, size.x, size.y) # Take 3 bitmaps for the background for the buttons mem_dc = wx.MemoryDC() #--------------------------------------- # X button #--------------------------------------- rect = wx.Rect(self.GetXPos(pc), 6, 16, 14) mem_dc.SelectObject(self._xBgBmp) mem_dc.Blit(0, 0, rect.width, rect.height, dc, rect.x, rect.y) mem_dc.SelectObject(wx.NullBitmap) #--------------------------------------- # Right button #--------------------------------------- rect = wx.Rect(self.GetRightButtonPos(pc), 6, 16, 14) mem_dc.SelectObject(self._rightBgBmp) mem_dc.Blit(0, 0, rect.width, rect.height, dc, rect.x, rect.y) mem_dc.SelectObject(wx.NullBitmap) #--------------------------------------- # Left button #--------------------------------------- rect = wx.Rect(self.GetLeftButtonPos(pc), 6, 16, 14) mem_dc.SelectObject(self._leftBgBmp) mem_dc.Blit(0, 0, rect.width, rect.height, dc, rect.x, rect.y) mem_dc.SelectObject(wx.NullBitmap) # We always draw the bottom/upper line of the tabs # regradless the style dc.SetPen(borderPen) self.DrawTabsLine(pc, dc) # Restore the pen dc.SetPen(borderPen) if pc.HasFlag(FNB_VC71): greyLineYVal = (pc.HasFlag(FNB_BOTTOM) and [0] or [size.y - 2])[0] whiteLineYVal = (pc.HasFlag(FNB_BOTTOM) and [3] or [size.y - 3])[0] pen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE)) dc.SetPen(pen) # Draw thik grey line between the windows area and # the tab area for num in xrange(3): dc.DrawLine(0, greyLineYVal + num, size.x, greyLineYVal + num) wbPen = (pc.HasFlag(FNB_BOTTOM) and [wx.BLACK_PEN] or [wx.WHITE_PEN])[0] dc.SetPen(wbPen) dc.DrawLine(1, whiteLineYVal, size.x - 1, whiteLineYVal) # Restore the pen dc.SetPen(borderPen) # Draw labels normalFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) boldFont = normalFont boldFont.SetWeight(wx.FONTWEIGHT_BOLD) dc.SetFont(boldFont) posx = pc._pParent.GetPadding() # Update all the tabs from 0 to 'pc.self._nFrom' to be non visible for i in xrange(pc._nFrom): pc._pagesInfoVec[i].SetPosition(wx.Point(-1, -1)) pc._pagesInfoVec[i].GetRegion().Clear() count = pc._nFrom #---------------------------------------------------------- # Go over and draw the visible tabs #---------------------------------------------------------- for i in xrange(pc._nFrom, len(pc._pagesInfoVec)): dc.SetPen(borderPen) dc.SetBrush((i==pc.GetSelection() and [selBrush] or [noselBrush])[0]) # Now set the font to the correct font dc.SetFont((i==pc.GetSelection() and [boldFont] or [normalFont])[0]) # Add the padding to the tab width # Tab width: # +-----------------------------------------------------------+ # | PADDING | IMG | IMG_PADDING | TEXT | PADDING | x |PADDING | # +-----------------------------------------------------------+ tabWidth = self.CalcTabWidth(pageContainer, i, tabHeight) # Check if we can draw more if posx + tabWidth + self.GetButtonsAreaLength(pc) >= clientWidth: break count = count + 1 # By default we clean the tab region pc._pagesInfoVec[i].GetRegion().Clear() # Clean the 'x' buttn on the tab. # A 'Clean' rectangle, is a rectangle with width or height # with values lower than or equal to 0 pc._pagesInfoVec[i].GetXRect().SetSize(wx.Size(-1, -1)) # Draw the tab (border, text, image & 'x' on tab) self.DrawTab(pc, dc, posx, i, tabWidth, tabHeight, pc._nTabXButtonStatus) # Restore the text forground dc.SetTextForeground(pc._activeTextColor) # Update the tab position & size posy = (pc.HasFlag(FNB_BOTTOM) and [0] or [VERTICAL_BORDER_PADDING])[0] pc._pagesInfoVec[i].SetPosition(wx.Point(posx, posy)) pc._pagesInfoVec[i].SetSize(wx.Size(tabWidth, tabHeight)) posx += tabWidth # Update all tabs that can not fit into the screen as non-visible for i in xrange(count, len(pc._pagesInfoVec)): pc._pagesInfoVec[i].SetPosition(wx.Point(-1, -1)) pc._pagesInfoVec[i].GetRegion().Clear() # Draw the left/right/close buttons # Left arrow self.DrawLeftArrow(pc, dc) self.DrawRightArrow(pc, dc) self.DrawX(pc, dc) self.DrawDropDownArrow(pc, dc) # ---------------------------------------------------------------------------- # # Class FNBRendererMgr # A manager that handles all the renderers defined below and calls the # appropriate one when drawing is needed # ---------------------------------------------------------------------------- # class FNBRendererMgr: """ This class represents a manager that handles all the 4 renderers defined and calls the appropriate one when drawing is needed. """ def __init__(self): """ Default class constructor. """ # register renderers self._renderers = {} self._renderers.update({-1: FNBRendererDefault()}) self._renderers.update({FNB_VC71: FNBRendererVC71()}) self._renderers.update({FNB_FANCY_TABS: FNBRendererFancy()}) self._renderers.update({FNB_VC8: FNBRendererVC8()}) def GetRenderer(self, style): """ Returns the current renderer based on the style selected. """ # since we dont have a style for default tabs, we # test for all others - FIXME: add style for default tabs if not style & FNB_VC71 and not style & FNB_VC8 and not style & FNB_FANCY_TABS: return self._renderers[-1] if style & FNB_VC71: return self._renderers[FNB_VC71] if style & FNB_FANCY_TABS: return self._renderers[FNB_FANCY_TABS] if style & FNB_VC8: return self._renderers[FNB_VC8] # the default is to return the default renderer return self._renderers[-1] #------------------------------------------ # Default renderer #------------------------------------------ class FNBRendererDefault(FNBRenderer): """ This class handles the drawing of tabs using the I{Standard} renderer. """ def __init__(self): """ Default class constructor. """ FNBRenderer.__init__(self) def DrawTab(self, pageContainer, dc, posx, tabIdx, tabWidth, tabHeight, btnStatus): """ Draws a tab using the I{Standard} style. """ # Default style borderPen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)) pc = pageContainer tabPoints = [wx.Point() for ii in xrange(7)] tabPoints[0].x = posx tabPoints[0].y = (pc.HasFlag(FNB_BOTTOM) and [2] or [tabHeight - 2])[0] tabPoints[1].x = int(posx+(tabHeight-2)*math.tan(float(pc._pagesInfoVec[tabIdx].GetTabAngle())/180.0*math.pi)) tabPoints[1].y = (pc.HasFlag(FNB_BOTTOM) and [tabHeight - (VERTICAL_BORDER_PADDING+2)] or [(VERTICAL_BORDER_PADDING+2)])[0] tabPoints[2].x = tabPoints[1].x+2 tabPoints[2].y = (pc.HasFlag(FNB_BOTTOM) and [tabHeight - VERTICAL_BORDER_PADDING] or [VERTICAL_BORDER_PADDING])[0] tabPoints[3].x = int(posx+tabWidth-(tabHeight-2)*math.tan(float(pc._pagesInfoVec[tabIdx].GetTabAngle())/180.0*math.pi))-2 tabPoints[3].y = (pc.HasFlag(FNB_BOTTOM) and [tabHeight - VERTICAL_BORDER_PADDING] or [VERTICAL_BORDER_PADDING])[0] tabPoints[4].x = tabPoints[3].x+2 tabPoints[4].y = (pc.HasFlag(FNB_BOTTOM) and [tabHeight - (VERTICAL_BORDER_PADDING+2)] or [(VERTICAL_BORDER_PADDING+2)])[0] tabPoints[5].x = int(tabPoints[4].x+(tabHeight-2)*math.tan(float(pc._pagesInfoVec[tabIdx].GetTabAngle())/180.0*math.pi)) tabPoints[5].y = (pc.HasFlag(FNB_BOTTOM) and [2] or [tabHeight - 2])[0] tabPoints[6].x = tabPoints[0].x tabPoints[6].y = tabPoints[0].y if tabIdx == pc.GetSelection(): # Draw the tab as rounded rectangle dc.DrawPolygon(tabPoints) else: if tabIdx != pc.GetSelection() - 1: # Draw a vertical line to the right of the text pt1x = tabPoints[5].x pt1y = (pc.HasFlag(FNB_BOTTOM) and [4] or [tabHeight - 6])[0] pt2x = tabPoints[5].x pt2y = (pc.HasFlag(FNB_BOTTOM) and [tabHeight - 4] or [4])[0] dc.DrawLine(pt1x, pt1y, pt2x, pt2y) if tabIdx == pc.GetSelection(): savePen = dc.GetPen() whitePen = wx.Pen(wx.WHITE) whitePen.SetWidth(1) dc.SetPen(whitePen) secPt = wx.Point(tabPoints[5].x + 1, tabPoints[5].y) dc.DrawLine(tabPoints[0].x, tabPoints[0].y, secPt.x, secPt.y) # Restore the pen dc.SetPen(savePen) # ----------------------------------- # Text and image drawing # ----------------------------------- # Text drawing offset from the left border of the # rectangle # The width of the images are 16 pixels padding = pc.GetParent().GetPadding() shapePoints = int(tabHeight*math.tan(float(pc._pagesInfoVec[tabIdx].GetTabAngle())/180.0*math.pi)) hasImage = pc._pagesInfoVec[tabIdx].GetImageIndex() != -1 imageYCoord = (pc.HasFlag(FNB_BOTTOM) and [6] or [8])[0] if hasImage: textOffset = 2*pc._pParent._nPadding + 16 + shapePoints/2 else: textOffset = pc._pParent._nPadding + shapePoints/2 textOffset += 2 if tabIdx != pc.GetSelection(): # Set the text background to be like the vertical lines dc.SetTextForeground(pc._pParent.GetNonActiveTabTextColour()) if hasImage: imageXOffset = textOffset - 16 - padding pc._ImageList.Draw(pc._pagesInfoVec[tabIdx].GetImageIndex(), dc, posx + imageXOffset, imageYCoord, wx.IMAGELIST_DRAW_TRANSPARENT, True) dc.DrawText(pc.GetPageText(tabIdx), posx + textOffset, imageYCoord) # draw 'x' on tab (if enabled) if pc.HasFlag(FNB_X_ON_TAB) and tabIdx == pc.GetSelection(): textWidth, textHeight = dc.GetTextExtent(pc.GetPageText(tabIdx)) tabCloseButtonXCoord = posx + textOffset + textWidth + 1 # take a bitmap from the position of the 'x' button (the x on tab button) # this bitmap will be used later to delete old buttons tabCloseButtonYCoord = imageYCoord x_rect = wx.Rect(tabCloseButtonXCoord, tabCloseButtonYCoord, 16, 16) self._tabXBgBmp = self._GetBitmap(dc, x_rect, self._tabXBgBmp) # Draw the tab self.DrawTabX(pc, dc, x_rect, tabIdx, btnStatus) #------------------------------------------------------------------ # Visual studio 7.1 #------------------------------------------------------------------ class FNBRendererVC71(FNBRenderer): """ This class handles the drawing of tabs using the I{VC71} renderer. """ def __init__(self): """ Default class constructor. """ FNBRenderer.__init__(self) def DrawTab(self, pageContainer, dc, posx, tabIdx, tabWidth, tabHeight, btnStatus): """ Draws a tab using the I{VC71} style. """ # Visual studio 7.1 style borderPen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)) pc = pageContainer dc.SetPen((tabIdx == pc.GetSelection() and [wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE))] or [borderPen])[0]) dc.SetBrush((tabIdx == pc.GetSelection() and [wx.Brush(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE))] or [wx.Brush(wx.Colour(247, 243, 233))])[0]) if tabIdx == pc.GetSelection(): posy = (pc.HasFlag(FNB_BOTTOM) and [0] or [VERTICAL_BORDER_PADDING])[0] dc.DrawRectangle(posx, posy, tabWidth, tabHeight - 1) # Draw a black line on the left side of the # rectangle dc.SetPen(wx.BLACK_PEN) blackLineY1 = VERTICAL_BORDER_PADDING blackLineY2 = (pc.HasFlag(FNB_BOTTOM) and [pc.GetSize().y - 5] or [pc.GetSize().y - 3])[0] dc.DrawLine(posx + tabWidth, blackLineY1, posx + tabWidth, blackLineY2) # To give the tab more 3D look we do the following # Incase the tab is on top, # Draw a thik white line on topof the rectangle # Otherwise, draw a thin (1 pixel) black line at the bottom pen = wx.Pen((pc.HasFlag(FNB_BOTTOM) and [wx.BLACK] or [wx.WHITE])[0]) dc.SetPen(pen) whiteLinePosY = (pc.HasFlag(FNB_BOTTOM) and [blackLineY2] or [VERTICAL_BORDER_PADDING ])[0] dc.DrawLine(posx , whiteLinePosY, posx + tabWidth + 1, whiteLinePosY) # Draw a white vertical line to the left of the tab dc.SetPen(wx.WHITE_PEN) if not pc.HasFlag(FNB_BOTTOM): blackLineY2 += 1 dc.DrawLine(posx, blackLineY1, posx, blackLineY2) else: # We dont draw a rectangle for non selected tabs, but only # vertical line on the left blackLineY1 = (pc.HasFlag(FNB_BOTTOM) and [VERTICAL_BORDER_PADDING + 2] or [VERTICAL_BORDER_PADDING + 1])[0] blackLineY2 = pc.GetSize().y - 5 dc.DrawLine(posx + tabWidth, blackLineY1, posx + tabWidth, blackLineY2) # ----------------------------------- # Text and image drawing # ----------------------------------- # Text drawing offset from the left border of the # rectangle # The width of the images are 16 pixels padding = pc.GetParent().GetPadding() hasImage = pc._pagesInfoVec[tabIdx].GetImageIndex() != -1 imageYCoord = (pc.HasFlag(FNB_BOTTOM) and [6] or [8])[0] if hasImage: textOffset = 2*pc._pParent._nPadding + 16 else: textOffset = pc._pParent._nPadding if tabIdx != pc.GetSelection(): # Set the text background to be like the vertical lines dc.SetTextForeground(pc._pParent.GetNonActiveTabTextColour()) if hasImage: imageXOffset = textOffset - 16 - padding pc._ImageList.Draw(pc._pagesInfoVec[tabIdx].GetImageIndex(), dc, posx + imageXOffset, imageYCoord, wx.IMAGELIST_DRAW_TRANSPARENT, True) dc.DrawText(pc.GetPageText(tabIdx), posx + textOffset, imageYCoord) # draw 'x' on tab (if enabled) if pc.HasFlag(FNB_X_ON_TAB) and tabIdx == pc.GetSelection(): textWidth, textHeight = dc.GetTextExtent(pc.GetPageText(tabIdx)) tabCloseButtonXCoord = posx + textOffset + textWidth + 1 # take a bitmap from the position of the 'x' button (the x on tab button) # this bitmap will be used later to delete old buttons tabCloseButtonYCoord = imageYCoord x_rect = wx.Rect(tabCloseButtonXCoord, tabCloseButtonYCoord, 16, 16) self._tabXBgBmp = self._GetBitmap(dc, x_rect, self._tabXBgBmp) # Draw the tab self.DrawTabX(pc, dc, x_rect, tabIdx, btnStatus) #------------------------------------------------------------------ # Fancy style #------------------------------------------------------------------ class FNBRendererFancy(FNBRenderer): """ This class handles the drawing of tabs using the I{Fancy} renderer. """ def __init__(self): """ Default class constructor. """ FNBRenderer.__init__(self) def DrawTab(self, pageContainer, dc, posx, tabIdx, tabWidth, tabHeight, btnStatus): """ Draws a tab using the I{Fancy} style, similar to VC71 but with gradients. """ # Fancy tabs - like with VC71 but with the following differences: # - The Selected tab is colored with gradient color borderPen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)) pc = pageContainer pen = (tabIdx == pc.GetSelection() and [wx.Pen(pc._pParent.GetBorderColour())] or [wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE))])[0] if tabIdx == pc.GetSelection(): posy = (pc.HasFlag(FNB_BOTTOM) and [2] or [VERTICAL_BORDER_PADDING])[0] th = (pc.HasFlag(FNB_BOTTOM) and [tabHeight - 2] or [tabHeight - 5])[0] rect = wx.Rect(posx, posy, tabWidth, th) col2 = (pc.HasFlag(FNB_BOTTOM) and [pc._pParent.GetGradientColourTo()] or [pc._pParent.GetGradientColourFrom()])[0] col1 = (pc.HasFlag(FNB_BOTTOM) and [pc._pParent.GetGradientColourFrom()] or [pc._pParent.GetGradientColourTo()])[0] PaintStraightGradientBox(dc, rect, col1, col2) dc.SetBrush(wx.TRANSPARENT_BRUSH) dc.SetPen(pen) dc.DrawRectangleRect(rect) # erase the bottom/top line of the rectangle dc.SetPen(wx.Pen(pc._pParent.GetGradientColourFrom())) if pc.HasFlag(FNB_BOTTOM): dc.DrawLine(rect.x, 2, rect.x + rect.width, 2) else: dc.DrawLine(rect.x, rect.y + rect.height - 1, rect.x + rect.width, rect.y + rect.height - 1) else: # We dont draw a rectangle for non selected tabs, but only # vertical line on the left dc.SetPen(borderPen) dc.DrawLine(posx + tabWidth, VERTICAL_BORDER_PADDING + 3, posx + tabWidth, tabHeight - 4) # ----------------------------------- # Text and image drawing # ----------------------------------- # Text drawing offset from the left border of the # rectangle # The width of the images are 16 pixels padding = pc.GetParent().GetPadding() hasImage = pc._pagesInfoVec[tabIdx].GetImageIndex() != -1 imageYCoord = (pc.HasFlag(FNB_BOTTOM) and [6] or [8])[0] if hasImage: textOffset = 2*pc._pParent._nPadding + 16 else: textOffset = pc._pParent._nPadding textOffset += 2 if tabIdx != pc.GetSelection(): # Set the text background to be like the vertical lines dc.SetTextForeground(pc._pParent.GetNonActiveTabTextColour()) if hasImage: imageXOffset = textOffset - 16 - padding pc._ImageList.Draw(pc._pagesInfoVec[tabIdx].GetImageIndex(), dc, posx + imageXOffset, imageYCoord, wx.IMAGELIST_DRAW_TRANSPARENT, True) dc.DrawText(pc.GetPageText(tabIdx), posx + textOffset, imageYCoord) # draw 'x' on tab (if enabled) if pc.HasFlag(FNB_X_ON_TAB) and tabIdx == pc.GetSelection(): textWidth, textHeight = dc.GetTextExtent(pc.GetPageText(tabIdx)) tabCloseButtonXCoord = posx + textOffset + textWidth + 1 # take a bitmap from the position of the 'x' button (the x on tab button) # this bitmap will be used later to delete old buttons tabCloseButtonYCoord = imageYCoord x_rect = wx.Rect(tabCloseButtonXCoord, tabCloseButtonYCoord, 16, 16) self._tabXBgBmp = self._GetBitmap(dc, x_rect, self._tabXBgBmp) # Draw the tab self.DrawTabX(pc, dc, x_rect, tabIdx, btnStatus) #------------------------------------------------------------------ # Visual studio 2005 (VS8) #------------------------------------------------------------------ class FNBRendererVC8(FNBRenderer): """ This class handles the drawing of tabs using the I{VC8} renderer. """ def __init__(self): """ Default class constructor. """ FNBRenderer.__init__(self) self._first = True self._factor = 1 def DrawTabs(self, pageContainer, dc): """ Draws all the tabs using VC8 style. Overloads The DrawTabs method in parent class. """ pc = pageContainer if "__WXMAC__" in wx.PlatformInfo: # Works well on MSW & GTK, however this lines should be skipped on MAC if not pc._pagesInfoVec or pc._nFrom >= len(pc._pagesInfoVec): pc.Hide() return # Get the text hight tabHeight = self.CalcTabHeight(pageContainer) # Set the font for measuring the tab height normalFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) boldFont = normalFont boldFont.SetWeight(wx.FONTWEIGHT_BOLD) # Calculate the number of rows required for drawing the tabs rect = pc.GetClientRect() # Set the maximum client size pc.SetSizeHints(self.GetButtonsAreaLength(pc), tabHeight) borderPen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)) # Create brushes backBrush = wx.Brush(pc._tabAreaColor) noselBrush = wx.Brush(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNFACE)) selBrush = wx.Brush(pc._activeTabColor) size = pc.GetSize() # Background dc.SetTextBackground(pc.GetBackgroundColour()) dc.SetTextForeground(pc._activeTextColor) # If border style is set, set the pen to be border pen if pc.HasFlag(FNB_TABS_BORDER_SIMPLE): dc.SetPen(borderPen) else: dc.SetPen(wx.TRANSPARENT_PEN) lightFactor = (pc.HasFlag(FNB_BACKGROUND_GRADIENT) and [70] or [0])[0] # For VC8 style, we color the tab area in gradient coloring lightcolour = LightColour(pc._tabAreaColor, lightFactor) PaintStraightGradientBox(dc, pc.GetClientRect(), pc._tabAreaColor, lightcolour) dc.SetBrush(wx.TRANSPARENT_BRUSH) dc.DrawRectangle(0, 0, size.x, size.y) # Take 3 bitmaps for the background for the buttons mem_dc = wx.MemoryDC() #--------------------------------------- # X button #--------------------------------------- rect = wx.Rect(self.GetXPos(pc), 6, 16, 14) mem_dc.SelectObject(self._xBgBmp) mem_dc.Blit(0, 0, rect.width, rect.height, dc, rect.x, rect.y) mem_dc.SelectObject(wx.NullBitmap) #--------------------------------------- # Right button #--------------------------------------- rect = wx.Rect(self.GetRightButtonPos(pc), 6, 16, 14) mem_dc.SelectObject(self._rightBgBmp) mem_dc.Blit(0, 0, rect.width, rect.height, dc, rect.x, rect.y) mem_dc.SelectObject(wx.NullBitmap) #--------------------------------------- # Left button #--------------------------------------- rect = wx.Rect(self.GetLeftButtonPos(pc), 6, 16, 14) mem_dc.SelectObject(self._leftBgBmp) mem_dc.Blit(0, 0, rect.width, rect.height, dc, rect.x, rect.y) mem_dc.SelectObject(wx.NullBitmap) # We always draw the bottom/upper line of the tabs # regradless the style dc.SetPen(borderPen) self.DrawTabsLine(pc, dc) # Restore the pen dc.SetPen(borderPen) # Draw labels dc.SetFont(boldFont) # Update all the tabs from 0 to 'pc.self._nFrom' to be non visible for i in xrange(pc._nFrom): pc._pagesInfoVec[i].SetPosition(wx.Point(-1, -1)) pc._pagesInfoVec[i].GetRegion().Clear() # Draw the visible tabs, in VC8 style, we draw them from right to left vTabsInfo = self.NumberTabsCanFit(pc) activeTabPosx = 0 activeTabWidth = 0 activeTabHeight = 0 for cur in xrange(len(vTabsInfo)-1, -1, -1): # 'i' points to the index of the currently drawn tab # in pc.GetPageInfoVector() vector i = pc._nFrom + cur dc.SetPen(borderPen) dc.SetBrush((i==pc.GetSelection() and [selBrush] or [noselBrush])[0]) # Now set the font to the correct font dc.SetFont((i==pc.GetSelection() and [boldFont] or [normalFont])[0]) # Add the padding to the tab width # Tab width: # +-----------------------------------------------------------+ # | PADDING | IMG | IMG_PADDING | TEXT | PADDING | x |PADDING | # +-----------------------------------------------------------+ tabWidth = self.CalcTabWidth(pageContainer, i, tabHeight) posx = vTabsInfo[cur].x # By default we clean the tab region # incase we use the VC8 style which requires # the region, it will be filled by the function # drawVc8Tab pc._pagesInfoVec[i].GetRegion().Clear() # Clean the 'x' buttn on the tab # 'Clean' rectanlge is a rectangle with width or height # with values lower than or equal to 0 pc._pagesInfoVec[i].GetXRect().SetSize(wx.Size(-1, -1)) # Draw the tab # Incase we are drawing the active tab # we need to redraw so it will appear on top # of all other tabs # when using the vc8 style, we keep the position of the active tab so we will draw it again later if i == pc.GetSelection() and pc.HasFlag(FNB_VC8): activeTabPosx = posx activeTabWidth = tabWidth activeTabHeight = tabHeight else: self.DrawTab(pc, dc, posx, i, tabWidth, tabHeight, pc._nTabXButtonStatus) # Restore the text forground dc.SetTextForeground(pc._activeTextColor) # Update the tab position & size pc._pagesInfoVec[i].SetPosition(wx.Point(posx, VERTICAL_BORDER_PADDING)) pc._pagesInfoVec[i].SetSize(wx.Size(tabWidth, tabHeight)) # Incase we are in VC8 style, redraw the active tab (incase it is visible) if pc.GetSelection() >= pc._nFrom and pc.GetSelection() < pc._nFrom + len(vTabsInfo): self.DrawTab(pc, dc, activeTabPosx, pc.GetSelection(), activeTabWidth, activeTabHeight, pc._nTabXButtonStatus) # Update all tabs that can not fit into the screen as non-visible for xx in xrange(pc._nFrom + len(vTabsInfo), len(pc._pagesInfoVec)): pc._pagesInfoVec[xx].SetPosition(wx.Point(-1, -1)) pc._pagesInfoVec[xx].GetRegion().Clear() # Draw the left/right/close buttons # Left arrow self.DrawLeftArrow(pc, dc) self.DrawRightArrow(pc, dc) self.DrawX(pc, dc) self.DrawDropDownArrow(pc, dc) def DrawTab(self, pageContainer, dc, posx, tabIdx, tabWidth, tabHeight, btnStatus): """ Draws a tab using VC8 style. """ pc = pageContainer borderPen = wx.Pen(pc._pParent.GetBorderColour()) tabPoints = [wx.Point() for ii in xrange(8)] # If we draw the first tab or the active tab, # we draw a full tab, else we draw a truncated tab # # X(2) X(3) # X(1) X(4) # # X(5) # # X(0),(7) X(6) # # tabPoints[0].x = (pc.HasFlag(FNB_BOTTOM) and [posx] or [posx+self._factor])[0] tabPoints[0].y = (pc.HasFlag(FNB_BOTTOM) and [2] or [tabHeight - 3])[0] tabPoints[1].x = tabPoints[0].x + tabHeight - VERTICAL_BORDER_PADDING - 3 - self._factor tabPoints[1].y = (pc.HasFlag(FNB_BOTTOM) and [tabHeight - (VERTICAL_BORDER_PADDING+2)] or [(VERTICAL_BORDER_PADDING+2)])[0] tabPoints[2].x = tabPoints[1].x + 4 tabPoints[2].y = (pc.HasFlag(FNB_BOTTOM) and [tabHeight - VERTICAL_BORDER_PADDING] or [VERTICAL_BORDER_PADDING])[0] tabPoints[3].x = tabPoints[2].x + tabWidth - 2 tabPoints[3].y = (pc.HasFlag(FNB_BOTTOM) and [tabHeight - VERTICAL_BORDER_PADDING] or [VERTICAL_BORDER_PADDING])[0] tabPoints[4].x = tabPoints[3].x + 1 tabPoints[4].y = (pc.HasFlag(FNB_BOTTOM) and [tabPoints[3].y - 1] or [tabPoints[3].y + 1])[0] tabPoints[5].x = tabPoints[4].x + 1 tabPoints[5].y = (pc.HasFlag(FNB_BOTTOM) and [(tabPoints[4].y - 1)] or [tabPoints[4].y + 1])[0] tabPoints[6].x = tabPoints[2].x + tabWidth tabPoints[6].y = tabPoints[0].y tabPoints[7].x = tabPoints[0].x tabPoints[7].y = tabPoints[0].y pc._pagesInfoVec[tabIdx].SetRegion(tabPoints) # Draw the polygon br = dc.GetBrush() dc.SetBrush(wx.Brush((tabIdx == pc.GetSelection() and [pc._activeTabColor] or [pc._colorTo])[0])) dc.SetPen(wx.Pen((tabIdx == pc.GetSelection() and [wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)] or [pc._colorBorder])[0])) dc.DrawPolygon(tabPoints) # Restore the brush dc.SetBrush(br) rect = pc.GetClientRect() if tabIdx != pc.GetSelection() and not pc.HasFlag(FNB_BOTTOM): # Top default tabs dc.SetPen(wx.Pen(pc._pParent.GetBorderColour())) lineY = rect.height curPen = dc.GetPen() curPen.SetWidth(1) dc.SetPen(curPen) dc.DrawLine(posx, lineY, posx+rect.width, lineY) # Incase we are drawing the selected tab, we draw the border of it as well # but without the bottom (upper line incase of wxBOTTOM) if tabIdx == pc.GetSelection(): borderPen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)) dc.SetPen(borderPen) dc.SetBrush(wx.TRANSPARENT_BRUSH) dc.DrawPolygon(tabPoints) # Delete the bottom line (or the upper one, incase we use wxBOTTOM) dc.SetPen(wx.WHITE_PEN) dc.DrawLine(tabPoints[0].x, tabPoints[0].y, tabPoints[6].x, tabPoints[6].y) self.FillVC8GradientColour(pc, dc, tabPoints, tabIdx == pc.GetSelection(), tabIdx) # Draw a thin line to the right of the non-selected tab if tabIdx != pc.GetSelection(): dc.SetPen(wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE))) dc.DrawLine(tabPoints[4].x-1, tabPoints[4].y, tabPoints[5].x-1, tabPoints[5].y) dc.DrawLine(tabPoints[5].x-1, tabPoints[5].y, tabPoints[6].x-1, tabPoints[6].y) # Text drawing offset from the left border of the # rectangle # The width of the images are 16 pixels vc8ShapeLen = tabHeight - VERTICAL_BORDER_PADDING - 2 if pc.TabHasImage(tabIdx): textOffset = 2*pc._pParent.GetPadding() + 16 + vc8ShapeLen else: textOffset = pc._pParent.GetPadding() + vc8ShapeLen # Draw the image for the tab if any imageYCoord = (pc.HasFlag(FNB_BOTTOM) and [6] or [8])[0] if pc.TabHasImage(tabIdx): imageXOffset = textOffset - 16 - pc._pParent.GetPadding() pc._ImageList.Draw(pc._pagesInfoVec[tabIdx].GetImageIndex(), dc, posx + imageXOffset, imageYCoord, wx.IMAGELIST_DRAW_TRANSPARENT, True) boldFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) # if selected tab, draw text in bold if tabIdx == pc.GetSelection(): boldFont.SetWeight(wx.FONTWEIGHT_BOLD) dc.SetFont(boldFont) dc.DrawText(pc.GetPageText(tabIdx), posx + textOffset, imageYCoord) # draw 'x' on tab (if enabled) if pc.HasFlag(FNB_X_ON_TAB) and tabIdx == pc.GetSelection(): textWidth, textHeight = dc.GetTextExtent(pc.GetPageText(tabIdx)) tabCloseButtonXCoord = posx + textOffset + textWidth + 1 # take a bitmap from the position of the 'x' button (the x on tab button) # this bitmap will be used later to delete old buttons tabCloseButtonYCoord = imageYCoord x_rect = wx.Rect(tabCloseButtonXCoord, tabCloseButtonYCoord, 16, 16) self._tabXBgBmp = self._GetBitmap(dc, x_rect, self._tabXBgBmp) # Draw the tab self.DrawTabX(pc, dc, x_rect, tabIdx, btnStatus) def FillVC8GradientColour(self, pageContainer, dc, tabPoints, bSelectedTab, tabIdx): """ Fills a tab with a gradient shading. """ # calculate gradient coefficients pc = pageContainer if self._first: self._first = False pc._colorTo = LightColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE), 0) pc._colorFrom = LightColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE), 60) col2 = (pc.HasFlag(FNB_BOTTOM) and [pc._pParent.GetGradientColourTo()] or [pc._pParent.GetGradientColourFrom()])[0] col1 = (pc.HasFlag(FNB_BOTTOM) and [pc._pParent.GetGradientColourFrom()] or [pc._pParent.GetGradientColourTo()])[0] # If colorful tabs style is set, override the tab color if pc.HasFlag(FNB_COLORFUL_TABS): if not pc._pagesInfoVec[tabIdx].GetColour(): # First time, generate color, and keep it in the vector tabColor = RandomColour() pc._pagesInfoVec[tabIdx].SetColour(tabColor) if pc.HasFlag(FNB_BOTTOM): col2 = LightColour(pc._pagesInfoVec[tabIdx].GetColour(), 50) col1 = LightColour(pc._pagesInfoVec[tabIdx].GetColour(), 80) else: col1 = LightColour(pc._pagesInfoVec[tabIdx].GetColour(), 50) col2 = LightColour(pc._pagesInfoVec[tabIdx].GetColour(), 80) size = abs(tabPoints[2].y - tabPoints[0].y) - 1 rf, gf, bf = 0, 0, 0 rstep = float(col2.Red() - col1.Red())/float(size) gstep = float(col2.Green() - col1.Green())/float(size) bstep = float(col2.Blue() - col1.Blue())/float(size) y = tabPoints[0].y # If we are drawing the selected tab, we need also to draw a line # from 0.tabPoints[0].x and tabPoints[6].x . end, we achieve this # by drawing the rectangle with transparent brush # the line under the selected tab will be deleted by the drwaing loop if bSelectedTab: self.DrawTabsLine(pc, dc) while 1: if pc.HasFlag(FNB_BOTTOM): if y > tabPoints[0].y + size: break else: if y < tabPoints[0].y - size: break currCol = wx.Colour(col1.Red() + rf, col1.Green() + gf, col1.Blue() + bf) dc.SetPen((bSelectedTab and [wx.Pen(pc._activeTabColor)] or [wx.Pen(currCol)])[0]) startX = self.GetStartX(tabPoints, y, pc.GetParent().GetWindowStyleFlag()) endX = self.GetEndX(tabPoints, y, pc.GetParent().GetWindowStyleFlag()) dc.DrawLine(startX, y, endX, y) # Draw the border using the 'edge' point dc.SetPen(wx.Pen((bSelectedTab and [wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)] or [pc._colorBorder])[0])) dc.DrawPoint(startX, y) dc.DrawPoint(endX, y) # Progress the color rf += rstep gf += gstep bf += bstep if pc.HasFlag(FNB_BOTTOM): y = y + 1 else: y = y - 1 def GetStartX(self, tabPoints, y, style): """ Returns the x start position of a tab. """ x1, x2, y1, y2 = 0.0, 0.0, 0.0, 0.0 # We check the 3 points to the left bBottomStyle = (style & FNB_BOTTOM and [True] or [False])[0] match = False if bBottomStyle: for i in xrange(3): if y >= tabPoints[i].y and y < tabPoints[i+1].y: x1 = tabPoints[i].x x2 = tabPoints[i+1].x y1 = tabPoints[i].y y2 = tabPoints[i+1].y match = True break else: for i in xrange(3): if y <= tabPoints[i].y and y > tabPoints[i+1].y: x1 = tabPoints[i].x x2 = tabPoints[i+1].x y1 = tabPoints[i].y y2 = tabPoints[i+1].y match = True break if not match: return tabPoints[2].x # According to the equation y = ax + b => x = (y-b)/a # We know the first 2 points if x2 == x1: return x2 else: a = (y2 - y1)/(x2 - x1) b = y1 - ((y2 - y1)/(x2 - x1))*x1 if a == 0: return int(x1) x = (y - b)/a return int(x) def GetEndX(self, tabPoints, y, style): """ Returns the x end position of a tab. """ x1, x2, y1, y2 = 0.0, 0.0, 0.0, 0.0 # We check the 3 points to the left bBottomStyle = (style & FNB_BOTTOM and [True] or [False])[0] match = False if bBottomStyle: for i in xrange(7, 3, -1): if y >= tabPoints[i].y and y < tabPoints[i-1].y: x1 = tabPoints[i].x x2 = tabPoints[i-1].x y1 = tabPoints[i].y y2 = tabPoints[i-1].y match = True break else: for i in xrange(7, 3, -1): if y <= tabPoints[i].y and y > tabPoints[i-1].y: x1 = tabPoints[i].x x2 = tabPoints[i-1].x y1 = tabPoints[i].y y2 = tabPoints[i-1].y match = True break if not match: return tabPoints[3].x # According to the equation y = ax + b => x = (y-b)/a # We know the first 2 points # Vertical line if x1 == x2: return int(x1) a = (y2 - y1)/(x2 - x1) b = y1 - ((y2 - y1)/(x2 - x1))*x1 if a == 0: return int(x1) x = (y - b)/a return int(x) def NumberTabsCanFit(self, pageContainer, fr=-1): """ Returns the number of tabs that can fit in the visible area. """ pc = pageContainer rect = pc.GetClientRect() clientWidth = rect.width # Empty results vTabInfo = [] tabHeight = self.CalcTabHeight(pageContainer) # The drawing starts from posx posx = pc._pParent.GetPadding() if fr < 0: fr = pc._nFrom for i in xrange(fr, len(pc._pagesInfoVec)): vc8glitch = tabHeight + FNB_HEIGHT_SPACER tabWidth = self.CalcTabWidth(pageContainer, i, tabHeight) if posx + tabWidth + vc8glitch + self.GetButtonsAreaLength(pc) >= clientWidth: break # Add a result to the returned vector tabRect = wx.Rect(posx, VERTICAL_BORDER_PADDING, tabWidth, tabHeight) vTabInfo.append(tabRect) # Advance posx posx += tabWidth + FNB_HEIGHT_SPACER return vTabInfo # ---------------------------------------------------------------------------- # # Class FlatNotebook # ---------------------------------------------------------------------------- # class FlatNotebook(wx.Panel): """ Display one or more windows in a notebook. B{Events}: - B{EVT_FLATNOTEBOOK_PAGE_CHANGING}: sent when the active page in the notebook is changing - B{EVT_FLATNOTEBOOK_PAGE_CHANGED}: sent when the active page in the notebook has changed - B{EVT_FLATNOTEBOOK_PAGE_CLOSING}: sent when a page in the notebook is closing - B{EVT_FLATNOTEBOOK_PAGE_CLOSED}: sent when a page in the notebook has been closed - B{EVT_FLATNOTEBOOK_PAGE_CONTEXT_MENU}: sent when the user clicks a tab in the notebook with the right mouse button """ def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, name="FlatNotebook"): """ Default class constructor. All the parameters are as in wxPython class construction, except the 'style': this can be assigned to whatever combination of FNB_* styles. """ self._bForceSelection = False self._nPadding = 6 self._nFrom = 0 style |= wx.TAB_TRAVERSAL self._pages = None self._windows = [] self._popupWin = None wx.Panel.__init__(self, parent, id, pos, size, style) self._pages = PageContainer(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, style) self.Bind(wx.EVT_NAVIGATION_KEY, self.OnNavigationKey) self.Init() def Init(self): """ Initializes all the class attributes. """ self._pages._colorBorder = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW) self._mainSizer = wx.BoxSizer(wx.VERTICAL) self.SetSizer(self._mainSizer) # The child panels will inherit this bg color, so leave it at the default value #self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_APPWORKSPACE)) # Set default page height dc = wx.ClientDC(self) font = self.GetFont() font.SetWeight(wx.FONTWEIGHT_BOLD) dc.SetFont(font) height = dc.GetCharHeight() tabHeight = height + FNB_HEIGHT_SPACER # We use 8 pixels as padding if "__WXGTK__" in wx.PlatformInfo: tabHeight += 6 self._pages.SetSizeHints(-1, tabHeight) # Add the tab container to the sizer self._mainSizer.Insert(0, self._pages, 0, wx.EXPAND) self._mainSizer.Layout() self._pages._nFrom = self._nFrom self._pDropTarget = FNBDropTarget(self) self.SetDropTarget(self._pDropTarget) def SetActiveTabTextColour(self, textColour): """ Sets the text colour for the active tab. """ self._pages._activeTextColor = textColour def OnDropTarget(self, x, y, nTabPage, wnd_oldContainer): """ Handles the drop action from a DND operation. """ return self._pages.OnDropTarget(x, y, nTabPage, wnd_oldContainer) def GetPreviousSelection(self): """ Returns the previous selection. """ return self._pages._iPreviousActivePage def AddPage(self, page, text, select=True, imageId=-1): """ Add a page to the L{FlatNotebook}. @param page: Specifies the new page. @param text: Specifies the text for the new page. @param select: Specifies whether the page should be selected. @param imageId: Specifies the optional image index for the new page. Return value: True if successful, False otherwise. """ # sanity check if not page: return False # reparent the window to us page.Reparent(self) # Add tab bSelected = select or len(self._windows) == 0 if bSelected: bSelected = False # Check for selection and send events oldSelection = self._pages._iActivePage tabIdx = len(self._windows) event = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CHANGING, self.GetId()) event.SetSelection(tabIdx) event.SetOldSelection(oldSelection) event.SetEventObject(self) if not self.GetEventHandler().ProcessEvent(event) or event.IsAllowed(): bSelected = True curSel = self._pages.GetSelection() if not self._pages.IsShown(): self._pages.Show() self._pages.AddPage(text, bSelected, imageId) self._windows.append(page) self.Freeze() # Check if a new selection was made if bSelected: if curSel >= 0: # Remove the window from the main sizer self._mainSizer.Detach(self._windows[curSel]) self._windows[curSel].Hide() if self.GetWindowStyleFlag() & FNB_BOTTOM: self._mainSizer.Insert(0, page, 1, wx.EXPAND) else: # We leave a space of 1 pixel around the window self._mainSizer.Add(page, 1, wx.EXPAND) # Fire a wxEVT_FLATNOTEBOOK_PAGE_CHANGED event event.SetEventType(wxEVT_FLATNOTEBOOK_PAGE_CHANGED) event.SetOldSelection(oldSelection) self.GetEventHandler().ProcessEvent(event) else: # Hide the page page.Hide() self._mainSizer.Layout() self.Thaw() self.Refresh() return True def SetImageList(self, imglist): """ Sets the image list for the page control. It does not take ownership of the image list, you must delete it yourself. """ self._pages.SetImageList(imglist) def GetImageList(self): """ Returns the associated image list. """ return self._pages.GetImageList() def InsertPage(self, indx, page, text, select=True, imageId=-1): """ Inserts a new page at the specified position. @param indx: Specifies the position of the new page. @param page: Specifies the new page. @param text: Specifies the text for the new page. @param select: Specifies whether the page should be selected. @param imageId: Specifies the optional image index for the new page. Return value: True if successful, False otherwise. """ # sanity check if not page: return False # reparent the window to us page.Reparent(self) if not self._windows: self.AddPage(page, text, select, imageId) return True # Insert tab bSelected = select or not self._windows curSel = self._pages.GetSelection() indx = max(0, min(indx, len(self._windows))) if indx <= len(self._windows): self._windows.insert(indx, page) else: self._windows.append(page) if bSelected: bSelected = False # Check for selection and send events oldSelection = self._pages._iActivePage event = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CHANGING, self.GetId()) event.SetSelection(indx) event.SetOldSelection(oldSelection) event.SetEventObject(self) if not self.GetEventHandler().ProcessEvent(event) or event.IsAllowed(): bSelected = True self._pages.InsertPage(indx, text, bSelected, imageId) if indx <= curSel: curSel = curSel + 1 self.Freeze() # Check if a new selection was made if bSelected: if curSel >= 0: # Remove the window from the main sizer self._mainSizer.Detach(self._windows[curSel]) self._windows[curSel].Hide() self._pages.SetSelection(indx) # Fire a wxEVT_FLATNOTEBOOK_PAGE_CHANGED event event.SetEventType(wxEVT_FLATNOTEBOOK_PAGE_CHANGED) event.SetOldSelection(oldSelection) self.GetEventHandler().ProcessEvent(event) else: # Hide the page page.Hide() self.Thaw() self._mainSizer.Layout() self.Refresh() return True def SetSelection(self, page): """ Sets the selection for the given page. The call to this function generates the page changing events """ if page >= len(self._windows) or not self._windows: return # Support for disabed tabs if not self._pages.GetEnabled(page) and len(self._windows) > 1 and not self._bForceSelection: return curSel = self._pages.GetSelection() # program allows the page change self.Freeze() if curSel >= 0: # Remove the window from the main sizer self._mainSizer.Detach(self._windows[curSel]) self._windows[curSel].Hide() if self.GetWindowStyleFlag() & FNB_BOTTOM: self._mainSizer.Insert(0, self._windows[page], 1, wx.EXPAND) else: # We leave a space of 1 pixel around the window self._mainSizer.Add(self._windows[page], 1, wx.EXPAND) self._windows[page].Show() self.Thaw() self._mainSizer.Layout() if page != self._pages._iActivePage: # there is a real page changing self._pages._iPreviousActivePage = self._pages._iActivePage self._pages._iActivePage = page self._pages.DoSetSelection(page) def DeletePage(self, page): """ Deletes the specified page, and the associated window. The call to this function generates the page changing events. """ if page >= len(self._windows) or page < 0: return # Fire a closing event event = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CLOSING, self.GetId()) event.SetSelection(page) event.SetEventObject(self) self.GetEventHandler().ProcessEvent(event) # The event handler allows it? if not event.IsAllowed(): return self.Freeze() # Delete the requested page pageRemoved = self._windows[page] # If the page is the current window, remove it from the sizer # as well if page == self._pages.GetSelection(): self._mainSizer.Detach(pageRemoved) # Remove it from the array as well self._windows.pop(page) # Now we can destroy it in wxWidgets use Destroy instead of delete pageRemoved.Destroy() self.Thaw() self._pages.DoDeletePage(page) self.Refresh() # Fire a closed event closedEvent = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CLOSED, self.GetId()) closedEvent.SetSelection(page) closedEvent.SetEventObject(self) self.GetEventHandler().ProcessEvent(closedEvent) def DeleteAllPages(self): """ Deletes all the pages. """ if not self._windows: return False self.Freeze() for page in self._windows: page.Destroy() self._windows = [] self.Thaw() # Clear the container of the tabs as well self._pages.DeleteAllPages() return True def GetCurrentPage(self): """ Returns the currently selected notebook page or None. """ sel = self._pages.GetSelection() if sel < 0: return None return self._windows[sel] def GetPage(self, page): """ Returns the window at the given page position, or None. """ if page >= len(self._windows): return None return self._windows[page] def GetPageIndex(self, win): """ Returns the index at which the window is found. """ try: return self._windows.index(win) except: return -1 def GetSelection(self): """ Returns the currently selected page, or -1 if none was selected. """ return self._pages.GetSelection() def AdvanceSelection(self, bForward=True): """ Cycles through the tabs. The call to this function generates the page changing events. """ self._pages.AdvanceSelection(bForward) def GetPageCount(self): """ Returns the number of pages in the L{FlatNotebook} control. """ return self._pages.GetPageCount() def OnNavigationKey(self, event): """ Handles the wx.EVT_NAVIGATION_KEY event for L{FlatNotebook}. """ if event.IsWindowChange(): # change pages if self.HasFlag(FNB_SMART_TABS): if not self._popupWin: self._popupWin = TabNavigatorWindow(self) self._popupWin.SetReturnCode(wx.ID_OK) self._popupWin.ShowModal() self._popupWin.Destroy() self._popupWin = None else: # a dialog is already opened self._popupWin.OnNavigationKey(event) return else: # change pages self.AdvanceSelection(event.GetDirection()) else: # pass to the parent if self.GetParent(): event.SetCurrentFocus(self) self.GetParent().ProcessEvent(event) def GetPageShapeAngle(self, page_index): """ Returns the angle associated to a tab. """ if page_index < 0 or page_index >= len(self._pages._pagesInfoVec): return None, False result = self._pages._pagesInfoVec[page_index].GetTabAngle() return result, True def SetPageShapeAngle(self, page_index, angle): """ Sets the angle associated to a tab. """ if page_index < 0 or page_index >= len(self._pages._pagesInfoVec): return if angle > 15: return self._pages._pagesInfoVec[page_index].SetTabAngle(angle) def SetAllPagesShapeAngle(self, angle): """ Sets the angle associated to all the tab. """ if angle > 15: return for ii in xrange(len(self._pages._pagesInfoVec)): self._pages._pagesInfoVec[ii].SetTabAngle(angle) self.Refresh() def GetPageBestSize(self): """ Return the page best size. """ return self._pages.GetClientSize() def SetPageText(self, page, text): """ Sets the text for the given page. """ bVal = self._pages.SetPageText(page, text) self._pages.Refresh() return bVal def SetPadding(self, padding): """ Sets the amount of space around each page's icon and label, in pixels. NB: only the horizontal padding is considered. """ self._nPadding = padding.GetWidth() def GetTabArea(self): """ Returns the associated page. """ return self._pages def GetPadding(self): """ Returns the amount of space around each page's icon and label, in pixels. """ return self._nPadding def SetWindowStyleFlag(self, style): """ Sets the L{FlatNotebook} window style flags. """ wx.Panel.SetWindowStyleFlag(self, style) if self._pages: # For changing the tab position (i.e. placing them top/bottom) # refreshing the tab container is not enough self.SetSelection(self._pages._iActivePage) def RemovePage(self, page): """ Deletes the specified page, without deleting the associated window. """ if page >= len(self._windows): return False # Fire a closing event event = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CLOSING, self.GetId()) event.SetSelection(page) event.SetEventObject(self) self.GetEventHandler().ProcessEvent(event) # The event handler allows it? if not event.IsAllowed(): return False self.Freeze() # Remove the requested page pageRemoved = self._windows[page] # If the page is the current window, remove it from the sizer # as well if page == self._pages.GetSelection(): self._mainSizer.Detach(pageRemoved) # Remove it from the array as well self._windows.pop(page) self.Thaw() self._pages.DoDeletePage(page) return True def SetRightClickMenu(self, menu): """ Sets the popup menu associated to a right click on a tab. """ self._pages._pRightClickMenu = menu def GetPageText(self, page): """ Returns the tab caption. """ return self._pages.GetPageText(page) def SetGradientColours(self, fr, to, border): """ Sets the gradient colours for the tab. """ self._pages._colorFrom = fr self._pages._colorTo = to self._pages._colorBorder = border def SetGradientColourFrom(self, fr): """ Sets the starting colour for the gradient. """ self._pages._colorFrom = fr def SetGradientColourTo(self, to): """ Sets the ending colour for the gradient. """ self._pages._colorTo = to def SetGradientColourBorder(self, border): """ Sets the tab border colour. """ self._pages._colorBorder = border def GetGradientColourFrom(self): """ Gets first gradient colour. """ return self._pages._colorFrom def GetGradientColourTo(self): """ Gets second gradient colour. """ return self._pages._colorTo def GetGradientColourBorder(self): """ Gets the tab border colour. """ return self._pages._colorBorder def GetBorderColour(self): """ Returns the border colour. """ return self._pages._colorBorder def GetActiveTabTextColour(self): """ Get the active tab text colour. """ return self._pages._activeTextColor def SetPageImage(self, page, imgindex): """ Sets the image index for the given page. Image is an index into the image list which was set with SetImageList. """ self._pages.SetPageImage(page, imgindex) def GetPageImage(self, page): """ Returns the image index for the given page. Image is an index into the image list which was set with SetImageList. """ return self._pages.GetPageImage(page) def GetEnabled(self, page): """ Returns whether a tab is enabled or not. """ return self._pages.GetEnabled(page) def Enable(self, page, enabled=True): """ Enables or disables a tab. """ if page >= len(self._windows): return self._windows[page].Enable(enabled) self._pages.Enable(page, enabled) def GetNonActiveTabTextColour(self): """ Returns the non active tabs text colour. """ return self._pages._nonActiveTextColor def SetNonActiveTabTextColour(self, color): """ Sets the non active tabs text colour. """ self._pages._nonActiveTextColor = color def SetTabAreaColour(self, color): """ Sets the area behind the tabs colour. """ self._pages._tabAreaColor = color def GetTabAreaColour(self): """ Returns the area behind the tabs colour. """ return self._pages._tabAreaColor def SetActiveTabColour(self, color): """ Sets the active tab colour. """ self._pages._activeTabColor = color def GetActiveTabColour(self): """ Returns the active tab colour. """ return self._pages._activeTabColor # ---------------------------------------------------------------------------- # # Class PageContainer # Acts as a container for the pages you add to FlatNotebook # ---------------------------------------------------------------------------- # class PageContainer(wx.Panel): """ This class acts as a container for the pages you add to L{FlatNotebook}. """ def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0): """ Default class constructor. """ self._ImageList = None self._iActivePage = -1 self._pDropTarget = None self._nLeftClickZone = FNB_NOWHERE self._iPreviousActivePage = -1 self._pRightClickMenu = None self._nXButtonStatus = FNB_BTN_NONE self._nArrowDownButtonStatus = FNB_BTN_NONE self._pParent = parent self._nRightButtonStatus = FNB_BTN_NONE self._nLeftButtonStatus = FNB_BTN_NONE self._nTabXButtonStatus = FNB_BTN_NONE self._pagesInfoVec = [] self._colorTo = wx.SystemSettings_GetColour(wx.SYS_COLOUR_ACTIVECAPTION) self._colorFrom = wx.WHITE self._activeTabColor = wx.WHITE self._activeTextColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNTEXT) self._nonActiveTextColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW) self._tabAreaColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNFACE) self._nFrom = 0 self._isdragging = False # Set default page height, this is done according to the system font memDc = wx.MemoryDC() memDc.SelectObject(wx.EmptyBitmap(10,10)) normalFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) boldFont = normalFont boldFont.SetWeight(wx.BOLD) memDc.SetFont(boldFont) height = memDc.GetCharHeight() tabHeight = height + FNB_HEIGHT_SPACER # We use 10 pixels as padding wx.Panel.__init__(self, parent, id, pos, wx.Size(size.x, tabHeight), style|wx.NO_BORDER|wx.NO_FULL_REPAINT_ON_RESIZE) self._pDropTarget = FNBDropTarget(self) self.SetDropTarget(self._pDropTarget) self._mgr = FNBRendererMgr() self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) self.Bind(wx.EVT_MIDDLE_DOWN, self.OnMiddleDown) self.Bind(wx.EVT_MOTION, self.OnMouseMove) self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave) self.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnterWindow) self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick) def OnEraseBackground(self, event): """ Handles the wx.EVT_ERASE_BACKGROUND event for L{PageContainer} (does nothing).""" pass def OnPaint(self, event): """ Handles the wx.EVT_PAINT event for L{PageContainer}.""" # Currently having problems with buffered DCs because of # recent changes. Just do the buffering ourselves instead. #dc = wx.BufferedPaintDC(self) size = self.GetSize() bmp = wx.EmptyBitmap(*size) dc = wx.MemoryDC() dc.SelectObject(bmp) renderer = self._mgr.GetRenderer(self.GetParent().GetWindowStyleFlag()) renderer.DrawTabs(self, dc) pdc = wx.PaintDC(self) pdc.Blit(0,0, size.width, size.height, dc, 0,0) def AddPage(self, caption, selected=True, imgindex=-1): """ Add a page to the L{FlatNotebook}. @param window: Specifies the new page. @param caption: Specifies the text for the new page. @param selected: Specifies whether the page should be selected. @param imgindex: Specifies the optional image index for the new page. Return value: True if successful, False otherwise. """ if selected: self._iPreviousActivePage = self._iActivePage self._iActivePage = len(self._pagesInfoVec) # Create page info and add it to the vector pageInfo = PageInfo(caption, imgindex) self._pagesInfoVec.append(pageInfo) self.Refresh() def InsertPage(self, indx, text, selected=True, imgindex=-1): """ Inserts a new page at the specified position. @param indx: Specifies the position of the new page. @param page: Specifies the new page. @param text: Specifies the text for the new page. @param select: Specifies whether the page should be selected. @param imgindex: Specifies the optional image index for the new page. Return value: True if successful, False otherwise. """ if selected: self._iPreviousActivePage = self._iActivePage self._iActivePage = len(self._pagesInfoVec) self._pagesInfoVec.insert(indx, PageInfo(text, imgindex)) self.Refresh() return True def OnSize(self, event): """ Handles the wx.EVT_SIZE events for L{PageContainer}. """ self.Refresh() # Call on paint event.Skip() def OnMiddleDown(self, event): """ Handles the wx.EVT_MIDDLE_DOWN events for L{PageContainer}. """ # Test if this style is enabled style = self.GetParent().GetWindowStyleFlag() if not style & FNB_MOUSE_MIDDLE_CLOSES_TABS: return where, tabIdx = self.HitTest(event.GetPosition()) if where == FNB_TAB: self.DeletePage(tabIdx) event.Skip() def OnRightDown(self, event): """ Handles the wx.EVT_RIGHT_DOWN events for L{PageContainer}. """ if self._pRightClickMenu: where, tabIdx = self.HitTest(event.GetPosition()) if where in [FNB_TAB, FNB_TAB_X]: if self._pagesInfoVec[tabIdx].GetEnabled(): # Set the current tab to be active self.SetSelection(tabIdx) # If the owner has defined a context menu for the tabs, # popup the right click menu if self._pRightClickMenu: self.PopupMenu(self._pRightClickMenu) else: # send a message to popup a custom menu event = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CONTEXT_MENU, self.GetParent().GetId()) event.SetSelection(tabIdx) event.SetOldSelection(self._iActivePage) event.SetEventObject(self.GetParent()) self.GetParent().GetEventHandler().ProcessEvent(event) event.Skip() def OnLeftDown(self, event): """ Handles the wx.EVT_LEFT_DOWN events for L{PageContainer}. """ # Reset buttons status self._nXButtonStatus = FNB_BTN_NONE self._nLeftButtonStatus = FNB_BTN_NONE self._nRightButtonStatus = FNB_BTN_NONE self._nTabXButtonStatus = FNB_BTN_NONE self._nArrowDownButtonStatus = FNB_BTN_NONE self._nLeftClickZone, tabIdx = self.HitTest(event.GetPosition()) if self._nLeftClickZone == FNB_DROP_DOWN_ARROW: self._nArrowDownButtonStatus = FNB_BTN_PRESSED self.Refresh() elif self._nLeftClickZone == FNB_LEFT_ARROW: self._nLeftButtonStatus = FNB_BTN_PRESSED self.Refresh() elif self._nLeftClickZone == FNB_RIGHT_ARROW: self._nRightButtonStatus = FNB_BTN_PRESSED self.Refresh() elif self._nLeftClickZone == FNB_X: self._nXButtonStatus = FNB_BTN_PRESSED self.Refresh() elif self._nLeftClickZone == FNB_TAB_X: self._nTabXButtonStatus = FNB_BTN_PRESSED self.Refresh() elif self._nLeftClickZone == FNB_TAB: if self._iActivePage != tabIdx: # In case the tab is disabled, we dont allow to choose it if self._pagesInfoVec[tabIdx].GetEnabled(): oldSelection = self._iActivePage event = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CHANGING, self.GetParent().GetId()) event.SetSelection(tabIdx) event.SetOldSelection(oldSelection) event.SetEventObject(self.GetParent()) if not self.GetParent().GetEventHandler().ProcessEvent(event) or event.IsAllowed(): self.SetSelection(tabIdx) # Fire a wxEVT_FLATNOTEBOOK_PAGE_CHANGED event event.SetEventType(wxEVT_FLATNOTEBOOK_PAGE_CHANGED) event.SetOldSelection(oldSelection) self.GetParent().GetEventHandler().ProcessEvent(event) def OnLeftUp(self, event): """ Handles the wx.EVT_LEFT_UP events for L{PageContainer}. """ # forget the zone that was initially clicked self._nLeftClickZone = FNB_NOWHERE where, tabIdx = self.HitTest(event.GetPosition()) if where == FNB_LEFT_ARROW: if self._nFrom == 0: return # Make sure that the button was pressed before if self._nLeftButtonStatus != FNB_BTN_PRESSED: return self._nLeftButtonStatus = FNB_BTN_HOVER # We scroll left with bulks of 5 scrollLeft = self.GetNumTabsCanScrollLeft() self._nFrom -= scrollLeft if self._nFrom < 0: self._nFrom = 0 self.Refresh() elif where == FNB_RIGHT_ARROW: if self._nFrom >= len(self._pagesInfoVec) - 1: return # Make sure that the button was pressed before if self._nRightButtonStatus != FNB_BTN_PRESSED: return self._nRightButtonStatus = FNB_BTN_HOVER # Check if the right most tab is visible, if it is # don't rotate right anymore if self._pagesInfoVec[-1].GetPosition() != wx.Point(-1, -1): return lastVisibleTab = self.GetLastVisibleTab() if lastVisibleTab < 0: # Probably the screen is too small for displaying even a single # tab, in this case we do nothing return self._nFrom += self.GetNumOfVisibleTabs() self.Refresh() elif where == FNB_X: # Make sure that the button was pressed before if self._nXButtonStatus != FNB_BTN_PRESSED: return self._nXButtonStatus = FNB_BTN_HOVER self.DeletePage(self._iActivePage) elif where == FNB_TAB_X: # Make sure that the button was pressed before if self._nTabXButtonStatus != FNB_BTN_PRESSED: return self._nTabXButtonStatus = FNB_BTN_HOVER self.DeletePage(self._iActivePage) elif where == FNB_DROP_DOWN_ARROW: # Make sure that the button was pressed before if self._nArrowDownButtonStatus != FNB_BTN_PRESSED: return self._nArrowDownButtonStatus = FNB_BTN_NONE # Refresh the button status renderer = self._mgr.GetRenderer(self.GetParent().GetWindowStyleFlag()) dc = wx.ClientDC(self) renderer.DrawDropDownArrow(self, dc) self.PopupTabsMenu() def HitTest(self, pt): """ HitTest method for L{PageContainer}. Returns the flag (if any) and the hit page (if any). """ style = self.GetParent().GetWindowStyleFlag() render = self._mgr.GetRenderer(style) fullrect = self.GetClientRect() btnLeftPos = render.GetLeftButtonPos(self) btnRightPos = render.GetRightButtonPos(self) btnXPos = render.GetXPos(self) tabIdx = -1 if len(self._pagesInfoVec) == 0: return FNB_NOWHERE, tabIdx rect = wx.Rect(btnXPos, 8, 16, 16) if rect.Contains(pt): return (style & FNB_NO_X_BUTTON and [FNB_NOWHERE] or [FNB_X])[0], tabIdx rect = wx.Rect(btnRightPos, 8, 16, 16) if style & FNB_DROPDOWN_TABS_LIST: rect = wx.Rect(render.GetDropArrowButtonPos(self), 8, 16, 16) if rect.Contains(pt): return FNB_DROP_DOWN_ARROW, tabIdx if rect.Contains(pt): return (style & FNB_NO_NAV_BUTTONS and [FNB_NOWHERE] or [FNB_RIGHT_ARROW])[0], tabIdx rect = wx.Rect(btnLeftPos, 8, 16, 16) if rect.Contains(pt): return (style & FNB_NO_NAV_BUTTONS and [FNB_NOWHERE] or [FNB_LEFT_ARROW])[0], tabIdx # Test whether a left click was made on a tab bFoundMatch = False for cur in xrange(self._nFrom, len(self._pagesInfoVec)): pgInfo = self._pagesInfoVec[cur] if pgInfo.GetPosition() == wx.Point(-1, -1): continue if style & FNB_X_ON_TAB and cur == self.GetSelection(): # 'x' button exists on a tab if self._pagesInfoVec[cur].GetXRect().Contains(pt): return FNB_TAB_X, cur if style & FNB_VC8: if self._pagesInfoVec[cur].GetRegion().Contains(pt.x, pt.y): if bFoundMatch or cur == self.GetSelection(): return FNB_TAB, cur tabIdx = cur bFoundMatch = True else: tabRect = wx.Rect(pgInfo.GetPosition().x, pgInfo.GetPosition().y, pgInfo.GetSize().x, pgInfo.GetSize().y) if tabRect.Contains(pt): # We have a match return FNB_TAB, cur if bFoundMatch: return FNB_TAB, tabIdx if self._isdragging: # We are doing DND, so check also the region outside the tabs # try before the first tab pgInfo = self._pagesInfoVec[0] tabRect = wx.Rect(0, pgInfo.GetPosition().y, pgInfo.GetPosition().x, self.GetParent().GetSize().y) if tabRect.Contains(pt): return FNB_TAB, 0 # try after the last tab pgInfo = self._pagesInfoVec[-1] startpos = pgInfo.GetPosition().x+pgInfo.GetSize().x tabRect = wx.Rect(startpos, pgInfo.GetPosition().y, fullrect.width-startpos, self.GetParent().GetSize().y) if tabRect.Contains(pt): return FNB_TAB, len(self._pagesInfoVec) # Default return FNB_NOWHERE, -1 def SetSelection(self, page): """ Sets the selected page. """ book = self.GetParent() book.SetSelection(page) self.DoSetSelection(page) def DoSetSelection(self, page): """ Does the actual selection of a page. """ if page < len(self._pagesInfoVec): #! fix for tabfocus da_page = self._pParent.GetPage(page) if da_page != None: da_page.SetFocus() if not self.IsTabVisible(page): if page == len(self._pagesInfoVec) - 1: # Incase the added tab is last, # the function IsTabVisible() will always return False # and thus will cause an evil behaviour that the new # tab will hide all other tabs, we need to check if the # new selected tab can fit to the current screen if not self.CanFitToScreen(page): self._nFrom = page else: if not self.CanFitToScreen(page): # Redraw the tabs starting from page self._nFrom = page self.Refresh() def DeletePage(self, page): """ Delete the specified page from L{FlatNotebook}. """ book = self.GetParent() book.DeletePage(page) book.Refresh() def IsTabVisible(self, page): """ Returns whether a tab is visible or not. """ iLastVisiblePage = self.GetLastVisibleTab() return page <= iLastVisiblePage and page >= self._nFrom def DoDeletePage(self, page): """ Does the actual page deletion. """ # Remove the page from the vector book = self.GetParent() self._pagesInfoVec.pop(page) # Thanks to Yiaanis AKA Mandrav if self._iActivePage >= page: self._iActivePage = self._iActivePage - 1 self._iPreviousActivePage = -1 # The delete page was the last first on the array, # but the book still has more pages, so we set the # active page to be the first one (0) if self._iActivePage < 0 and len(self._pagesInfoVec) > 0: self._iActivePage = 0 self._iPreviousActivePage = -1 # Refresh the tabs if self._iActivePage >= 0: book._bForceSelection = True # Check for selection and send event event = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CHANGING, self.GetParent().GetId()) event.SetSelection(self._iActivePage) event.SetOldSelection(self._iPreviousActivePage) event.SetEventObject(self.GetParent()) book.SetSelection(self._iActivePage) book._bForceSelection = False # Fire a wxEVT_FLATNOTEBOOK_PAGE_CHANGED event event.SetEventType(wxEVT_FLATNOTEBOOK_PAGE_CHANGED) event.SetOldSelection(self._iPreviousActivePage) self.GetParent().GetEventHandler().ProcessEvent(event) if not self._pagesInfoVec: # Erase the page container drawings dc = wx.ClientDC(self) dc.Clear() def DeleteAllPages(self): """ Deletes all the pages. """ self._iActivePage = -1 self._iPreviousActivePage = -1 self._nFrom = 0 self._pagesInfoVec = [] # Erase the page container drawings dc = wx.ClientDC(self) dc.Clear() def OnMouseMove(self, event): """ Handles the wx.EVT_MOTION for L{PageContainer}. """ if self._pagesInfoVec and self.IsShown(): xButtonStatus = self._nXButtonStatus xTabButtonStatus = self._nTabXButtonStatus rightButtonStatus = self._nRightButtonStatus leftButtonStatus = self._nLeftButtonStatus dropDownButtonStatus = self._nArrowDownButtonStatus style = self.GetParent().GetWindowStyleFlag() self._nXButtonStatus = FNB_BTN_NONE self._nRightButtonStatus = FNB_BTN_NONE self._nLeftButtonStatus = FNB_BTN_NONE self._nTabXButtonStatus = FNB_BTN_NONE self._nArrowDownButtonStatus = FNB_BTN_NONE where, tabIdx = self.HitTest(event.GetPosition()) if where == FNB_X: if event.LeftIsDown(): self._nXButtonStatus = (self._nLeftClickZone==FNB_X and [FNB_BTN_PRESSED] or [FNB_BTN_NONE])[0] else: self._nXButtonStatus = FNB_BTN_HOVER elif where == FNB_DROP_DOWN_ARROW: if event.LeftIsDown(): self._nArrowDownButtonStatus = (self._nLeftClickZone==FNB_DROP_DOWN_ARROW and [FNB_BTN_PRESSED] or [FNB_BTN_NONE])[0] else: self._nArrowDownButtonStatus = FNB_BTN_HOVER elif where == FNB_TAB_X: if event.LeftIsDown(): self._nTabXButtonStatus = (self._nLeftClickZone==FNB_TAB_X and [FNB_BTN_PRESSED] or [FNB_BTN_NONE])[0] else: self._nTabXButtonStatus = FNB_BTN_HOVER elif where == FNB_RIGHT_ARROW: if event.LeftIsDown(): self._nRightButtonStatus = (self._nLeftClickZone==FNB_RIGHT_ARROW and [FNB_BTN_PRESSED] or [FNB_BTN_NONE])[0] else: self._nRightButtonStatus = FNB_BTN_HOVER elif where == FNB_LEFT_ARROW: if event.LeftIsDown(): self._nLeftButtonStatus = (self._nLeftClickZone==FNB_LEFT_ARROW and [FNB_BTN_PRESSED] or [FNB_BTN_NONE])[0] else: self._nLeftButtonStatus = FNB_BTN_HOVER elif where == FNB_TAB: # Call virtual method for showing tooltip self.ShowTabTooltip(tabIdx) if not self.GetEnabled(tabIdx): # Set the cursor to be 'No-entry' wx.SetCursor(wx.StockCursor(wx.CURSOR_NO_ENTRY)) # Support for drag and drop if event.LeftIsDown() and not (style & FNB_NODRAG): self._isdragging = True draginfo = FNBDragInfo(self, tabIdx) drginfo = cPickle.dumps(draginfo) dataobject = wx.CustomDataObject(wx.CustomDataFormat("FlatNotebook")) dataobject.SetData(drginfo) dragSource = wx.DropSource(self) dragSource.SetData(dataobject) dragSource.DoDragDrop(wx.Drag_DefaultMove) bRedrawX = self._nXButtonStatus != xButtonStatus bRedrawRight = self._nRightButtonStatus != rightButtonStatus bRedrawLeft = self._nLeftButtonStatus != leftButtonStatus bRedrawTabX = self._nTabXButtonStatus != xTabButtonStatus bRedrawDropArrow = self._nArrowDownButtonStatus != dropDownButtonStatus render = self._mgr.GetRenderer(style) if (bRedrawX or bRedrawRight or bRedrawLeft or bRedrawTabX or bRedrawDropArrow): dc = wx.ClientDC(self) if bRedrawX: render.DrawX(self, dc) if bRedrawLeft: render.DrawLeftArrow(self, dc) if bRedrawRight: render.DrawRightArrow(self, dc) if bRedrawTabX: render.DrawTabX(self, dc, self._pagesInfoVec[tabIdx].GetXRect(), tabIdx, self._nTabXButtonStatus) if bRedrawDropArrow: render.DrawDropDownArrow(self, dc) event.Skip() def GetLastVisibleTab(self): """ Returns the last visible tab. """ ii = 0 for ii in xrange(self._nFrom, len(self._pagesInfoVec)): if self._pagesInfoVec[ii].GetPosition() == wx.Point(-1, -1): break return ii-1 def GetNumTabsCanScrollLeft(self): """ Returns the number of tabs than can be scrolled left. """ # Reserved area for the buttons (<>x) rect = self.GetClientRect() clientWidth = rect.width posx = self._pParent._nPadding numTabs = 0 pom = 0 # In case we have error prevent crash if self._nFrom < 0: return 0 dc = wx.ClientDC(self) style = self.GetParent().GetWindowStyleFlag() render = self._mgr.GetRenderer(style) for ii in xrange(self._nFrom, -1, -1): boldFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) boldFont.SetWeight(wx.FONTWEIGHT_BOLD) dc.SetFont(boldFont) height = dc.GetCharHeight() tabHeight = height + FNB_HEIGHT_SPACER # We use 6 pixels as padding if style & FNB_VC71: tabHeight = (style & FNB_BOTTOM and [tabHeight - 4] or [tabHeight])[0] elif style & FNB_FANCY_TABS: tabHeight = (style & FNB_BOTTOM and [tabHeight - 3] or [tabHeight])[0] width, pom = dc.GetTextExtent(self.GetPageText(ii)) if style != FNB_VC71: shapePoints = int(tabHeight*math.tan(float(self._pagesInfoVec[ii].GetTabAngle())/180.0*math.pi)) else: shapePoints = 0 tabWidth = 2*self._pParent._nPadding + width if not (style & FNB_VC71): # Default style tabWidth += 2*shapePoints hasImage = self._ImageList != None and self._pagesInfoVec[ii].GetImageIndex() != -1 # For VC71 style, we only add the icon size (16 pixels) if hasImage: if not self.IsDefaultTabs(): tabWidth += 16 + self._pParent._nPadding else: # Default style tabWidth += 16 + self._pParent._nPadding + shapePoints/2 if posx + tabWidth + render.GetButtonsAreaLength(self) >= clientWidth: break numTabs = numTabs + 1 posx += tabWidth return numTabs def IsDefaultTabs(self): """ Returns whether a tab has a default style. """ style = self.GetParent().GetWindowStyleFlag() res = (style & FNB_VC71) or (style & FNB_FANCY_TABS) or (style & FNB_VC8) return not res def AdvanceSelection(self, bForward=True): """ Cycles through the tabs. The call to this function generates the page changing events. """ nSel = self.GetSelection() if nSel < 0: return nMax = self.GetPageCount() - 1 oldSelection = self._iActivePage if bForward: newSelection = (nSel == nMax and [0] or [nSel + 1])[0] else: newSelection = (nSel == 0 and [nMax] or [nSel - 1])[0] if not self._pagesInfoVec[newSelection].GetEnabled(): return event = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CHANGING, self.GetParent().GetId()) event.SetSelection(newSelection) event.SetOldSelection(oldSelection) event.SetEventObject(self.GetParent()) if not self.GetParent().GetEventHandler().ProcessEvent(event) or event.IsAllowed(): self.SetSelection(newSelection) # Fire a wxEVT_FLATNOTEBOOK_PAGE_CHANGED event event.SetEventType(wxEVT_FLATNOTEBOOK_PAGE_CHANGED) event.SetOldSelection(oldSelection) self.GetParent().GetEventHandler().ProcessEvent(event) def OnMouseLeave(self, event): """ Handles the wx.EVT_LEAVE_WINDOW event for L{PageContainer}. """ self._nLeftButtonStatus = FNB_BTN_NONE self._nXButtonStatus = FNB_BTN_NONE self._nRightButtonStatus = FNB_BTN_NONE self._nTabXButtonStatus = FNB_BTN_NONE self._nArrowDownButtonStatus = FNB_BTN_NONE style = self.GetParent().GetWindowStyleFlag() render = self._mgr.GetRenderer(style) dc = wx.ClientDC(self) render.DrawX(self, dc) render.DrawLeftArrow(self, dc) render.DrawRightArrow(self, dc) selection = self.GetSelection() if selection == -1: event.Skip() return if not self.IsTabVisible(selection): if selection == len(self._pagesInfoVec) - 1: if not self.CanFitToScreen(selection): event.Skip() return else: event.Skip() return render.DrawTabX(self, dc, self._pagesInfoVec[selection].GetXRect(), selection, self._nTabXButtonStatus) event.Skip() def OnMouseEnterWindow(self, event): """ Handles the wx.EVT_ENTER_WINDOW event for L{PageContainer}. """ self._nLeftButtonStatus = FNB_BTN_NONE self._nXButtonStatus = FNB_BTN_NONE self._nRightButtonStatus = FNB_BTN_NONE self._nLeftClickZone = FNB_BTN_NONE self._nArrowDownButtonStatus = FNB_BTN_NONE event.Skip() def ShowTabTooltip(self, tabIdx): """ Shows a tab tooltip. """ pWindow = self._pParent.GetPage(tabIdx) if pWindow: pToolTip = pWindow.GetToolTip() if pToolTip and pToolTip.GetWindow() == pWindow: self.SetToolTipString(pToolTip.GetTip()) def SetPageImage(self, page, imgindex): """ Sets the image index associated to a page. """ if page < len(self._pagesInfoVec): self._pagesInfoVec[page].SetImageIndex(imgindex) self.Refresh() def GetPageImage(self, page): """ Returns the image index associated to a page. """ if page < len(self._pagesInfoVec): return self._pagesInfoVec[page].GetImageIndex() return -1 def OnDropTarget(self, x, y, nTabPage, wnd_oldContainer): """ Handles the drop action from a DND operation. """ # Disable drag'n'drop for disabled tab if not wnd_oldContainer._pagesInfoVec[nTabPage].GetEnabled(): return wx.DragCancel self._isdragging = True oldContainer = wnd_oldContainer nIndex = -1 where, nIndex = self.HitTest(wx.Point(x, y)) oldNotebook = oldContainer.GetParent() newNotebook = self.GetParent() if oldNotebook == newNotebook: if nTabPage >= 0: if where == FNB_TAB: self.MoveTabPage(nTabPage, nIndex) elif self.GetParent().GetWindowStyleFlag() & FNB_ALLOW_FOREIGN_DND: if wx.Platform in ["__WXMSW__", "__WXGTK__"]: if nTabPage >= 0: window = oldNotebook.GetPage(nTabPage) if window: where, nIndex = newNotebook._pages.HitTest(wx.Point(x, y)) caption = oldContainer.GetPageText(nTabPage) imageindex = oldContainer.GetPageImage(nTabPage) oldNotebook.RemovePage(nTabPage) window.Reparent(newNotebook) if imageindex >= 0: bmp = oldNotebook.GetImageList().GetBitmap(imageindex) newImageList = newNotebook.GetImageList() if not newImageList: xbmp, ybmp = bmp.GetWidth(), bmp.GetHeight() newImageList = wx.ImageList(xbmp, ybmp) imageindex = 0 else: imageindex = newImageList.GetImageCount() newImageList.Add(bmp) newNotebook.SetImageList(newImageList) newNotebook.InsertPage(nIndex, window, caption, True, imageindex) self._isdragging = False return wx.DragMove def MoveTabPage(self, nMove, nMoveTo): """ Moves a tab inside the same L{FlatNotebook}. """ if nMove == nMoveTo: return elif nMoveTo < len(self._pParent._windows): nMoveTo = nMoveTo + 1 self._pParent.Freeze() # Remove the window from the main sizer nCurSel = self._pParent._pages.GetSelection() self._pParent._mainSizer.Detach(self._pParent._windows[nCurSel]) self._pParent._windows[nCurSel].Hide() pWindow = self._pParent._windows[nMove] self._pParent._windows.pop(nMove) self._pParent._windows.insert(nMoveTo-1, pWindow) pgInfo = self._pagesInfoVec[nMove] self._pagesInfoVec.pop(nMove) self._pagesInfoVec.insert(nMoveTo - 1, pgInfo) # Add the page according to the style pSizer = self._pParent._mainSizer style = self.GetParent().GetWindowStyleFlag() if style & FNB_BOTTOM: pSizer.Insert(0, pWindow, 1, wx.EXPAND) else: # We leave a space of 1 pixel around the window pSizer.Add(pWindow, 1, wx.EXPAND) pWindow.Show() pSizer.Layout() self._iActivePage = nMoveTo - 1 self._iPreviousActivePage = -1 self.DoSetSelection(self._iActivePage) self.Refresh() self._pParent.Thaw() def CanFitToScreen(self, page): """ Returns wheter a tab can fit in the left space in the screen or not. """ # Incase the from is greater than page, # we need to reset the self._nFrom, so in order # to force the caller to do so, we return false if self._nFrom > page: return False style = self.GetParent().GetWindowStyleFlag() render = self._mgr.GetRenderer(style) if not self.HasFlag(FNB_VC8): rect = self.GetClientRect(); clientWidth = rect.width; tabHeight = render.CalcTabHeight(self) tabWidth = render.CalcTabWidth(self, page, tabHeight) posx = self._pParent._nPadding if self._nFrom >= 0: for i in xrange(self._nFrom, len(self._pagesInfoVec)): if self._pagesInfoVec[i].GetPosition() == wx.Point(-1, -1): break posx += self._pagesInfoVec[i].GetSize().x if posx + tabWidth + render.GetButtonsAreaLength(self) >= clientWidth: return False return True else: # TODO:: this is ugly and should be improved, we should *never* access the # raw pointer directly like we do here (render.Get()) vc8_render = render vTabInfo = vc8_render.NumberTabsCanFit(self) if page - self._nFrom >= len(vTabInfo): return False return True def GetNumOfVisibleTabs(self): """ Returns the number of visible tabs. """ count = 0 for ii in xrange(self._nFrom, len(self._pagesInfoVec)): if self._pagesInfoVec[ii].GetPosition() == wx.Point(-1, -1): break count = count + 1 return count def GetEnabled(self, page): """ Returns whether a tab is enabled or not. """ if page >= len(self._pagesInfoVec): return True # Seems strange, but this is the default return self._pagesInfoVec[page].GetEnabled() def Enable(self, page, enabled=True): """ Enables or disables a tab. """ if page >= len(self._pagesInfoVec): return self._pagesInfoVec[page].Enable(enabled) def GetSingleLineBorderColour(self): """ Returns the colour for the single line border. """ if self.HasFlag(FNB_FANCY_TABS): return self._colorFrom return wx.WHITE def HasFlag(self, flag): """ Returns whether a flag is present in the L{FlatNotebook} style. """ style = self.GetParent().GetWindowStyleFlag() res = (style & flag and [True] or [False])[0] return res def ClearFlag(self, flag): """ Deletes a flag from the L{FlatNotebook} style. """ style = self.GetParent().GetWindowStyleFlag() style &= ~flag self.SetWindowStyleFlag(style) def TabHasImage(self, tabIdx): """ Returns whether a tab has an associated image index or not. """ if self._ImageList: return self._pagesInfoVec[tabIdx].GetImageIndex() != -1 return False def OnLeftDClick(self, event): """ Handles the wx.EVT_LEFT_DCLICK event for L{PageContainer}. """ if self.HasFlag(FNB_DCLICK_CLOSES_TABS): where, tabIdx = self.HitTest(event.GetPosition()) if where == FNB_TAB: self.DeletePage(tabIdx) else: event.Skip() def PopupTabsMenu(self): """ Pops up the menu activated with the drop down arrow in the navigation area. """ popupMenu = wx.Menu() for i in xrange(len(self._pagesInfoVec)): pi = self._pagesInfoVec[i] item = wx.MenuItem(popupMenu, i, pi.GetCaption(), pi.GetCaption(), wx.ITEM_NORMAL) self.Bind(wx.EVT_MENU, self.OnTabMenuSelection, item) # This code is commented, since there is an alignment problem with wx2.6.3 & Menus # if self.TabHasImage(ii): # item.SetBitmaps( (*m_ImageList)[pi.GetImageIndex()] ); popupMenu.AppendItem(item) self.PopupMenu(popupMenu) def OnTabMenuSelection(self, event): """ Handles the wx.EVT_MENU event for L{PageContainer}. """ selection = event.GetId() self._pParent.SetSelection(selection) def SetImageList(self, imglist): """ Sets the image list for the page control. """ self._ImageList = imglist def GetImageList(self): """ Returns the image list for the page control. """ return self._ImageList def GetSelection(self): """ Returns the current selected page. """ return self._iActivePage def GetPageCount(self): """ Returns the number of tabs in the L{FlatNotebook} control. """ return len(self._pagesInfoVec) def GetPageText(self, page): """ Returns the tab caption of the page. """ return self._pagesInfoVec[page].GetCaption() def SetPageText(self, page, text): """ Sets the tab caption of the page. """ self._pagesInfoVec[page].SetCaption(text) return True