X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/6cb4f153c300067e21eac198e4b57b47db9b9767..ad55c49e82848208ca5af4f24c5a5e9a9de1d7b7:/wxPython/wx/lib/flatnotebook.py?ds=sidebyside diff --git a/wxPython/wx/lib/flatnotebook.py b/wxPython/wx/lib/flatnotebook.py index b43def683a..a177d5e265 100644 --- a/wxPython/wx/lib/flatnotebook.py +++ b/wxPython/wx/lib/flatnotebook.py @@ -11,7 +11,7 @@ # Python Code By: # # Andrea Gavana, @ 02 Oct 2006 -# Latest Revision: 04 Oct 2006, 20.00 GMT +# Latest Revision: 12 Oct 2006, 20.00 GMT # # # For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please @@ -31,160 +31,158 @@ 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) +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. -Usage: - -The following example shows a simple implementation that uses FlatNotebook inside -a very simple frame:: - - import wx - import wx.lib.flatnotebook as FNB - - class MyFrame(wx.Frame): - - def __init__(self, parent, id=wx.ID_ANY, title="", pos=wx.DefaultPosition, size=(800, 600), - style=wx.DEFAULT_FRAME_STYLE | wx.MAXIMIZE |wx.NO_FULL_REPAINT_ON_RESIZE): - - wx.Frame.__init__(self, parent, id, title, pos, size, style) - - mainSizer = wx.BoxSizer(wx.VERTICAL) - self.SetSizer(mainSizer) - - bookStyle = FNB.FNB_TABS_BORDER_SIMPLE - - self.book = FNB.StyledNotebook(self, wx.ID_ANY, style=bookStyle) - mainSizer.Add(self.book, 1, wx.EXPAND) - - # Add some pages to the notebook - self.Freeze() - - text = wx.TextCtrl(self.book, -1, "Book Page 1", style=wx.TE_MULTILINE) - self.book.AddPage(text, "Book Page 1") - - text = wx.TextCtrl(self.book, -1, "Book Page 2", style=wx.TE_MULTILINE) - self.book.AddPage(text, "Book Page 2") - - self.Thaw() - - mainSizer.Layout() - self.SendSizeEvent() - - # our normal wxApp-derived class, as usual - - app = wx.PySimpleApp() - - frame = MyFrame(None) - app.SetTopWindow(frame) - frame.Show() - - app.MainLoop() - - License And Version: FlatNotebook Is Freeware And Distributed Under The wxPython License. -Latest Revision: Andrea Gavana @ 04 Oct 2006, 20.00 GMT -Version 0.3. +Latest Revision: Andrea Gavana @ 12 Oct 2006, 20.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 +# 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 +# Use Visual Studio 2005 (VC8) style for tabs FNB_VC8 = 256 +"""Use Visual Studio 2005 (VC8) style for tabs""" # Place 'X' on a tab -# Note: This style is not supported on VC8 style 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.Color(0, 128, 128) +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; @@ -208,10 +206,18 @@ wxEVT_FLATNOTEBOOK_PAGE_CONTEXT_MENU = wx.NewEventType() #-----------------------------------# 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 @@ -525,6 +531,119 @@ right_arrow_xpm = [ "````````````````" ] +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): @@ -543,7 +662,17 @@ def LightColour(color, 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.Color(r, g, b) + 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): @@ -571,7 +700,7 @@ def PaintStraightGradientBox(dc, rect, startColor, endColor, vertical=True): g = startColor.Green() + ((i*gd*100)/high)/100 b = startColor.Blue() + ((i*bd*100)/high)/100 - p = wx.Pen(wx.Color(r, g, b)) + p = wx.Pen(wx.Colour(r, g, b)) dc.SetPen(p) if vertical: @@ -584,14 +713,30 @@ def PaintStraightGradientBox(dc, rect, startColor, endColor, vertical=True): dc.SetBrush(savedBrush) -def RandomColor(): - """ Creates a random colour. """ +# ---------------------------------------------------------------------------- # +# Class FNBDropSource +# Gives Some Custom UI Feedback during the DnD Operations +# ---------------------------------------------------------------------------- # + +class FNBDropSource(wx.DropSource): + """ + Give some custom UI feedback during the drag and drop operation in this + function. It is called on each mouse move, so your implementation must + not be too slow. + """ - 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 + def __init__(self, win): + """ Default class constructor. Used internally. """ + + wx.DropSource.__init__(self, win) + self._win = win + - return wx.Color(r, g, b) + def GiveFeedback(self, effect): + """ Provides user with a nice feedback when tab is being dragged. """ + + self._win.DrawDragHint() + return False # ---------------------------------------------------------------------------- # @@ -613,7 +758,7 @@ class FNBDragInfo: def GetContainer(self): - """ Returns the FlatNotebook page (usually a panel). """ + """ Returns the L{FlatNotebook} page (usually a panel). """ return FNBDragInfo._map.get(self._id, None) @@ -660,16 +805,20 @@ class FNBDropTarget(wx.DropTarget): # ---------------------------------------------------------------------------- # 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: - - caption: the tab caption; - - imageindex: the tab image index based on the assigned (set) wx.ImageList (if any); - - tabangle: the tab angle (only on standard tabs, from 0 to 15 degrees); - - enabled: sets enabled or disabled the tab. + @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 @@ -779,13 +928,13 @@ class PageInfo: return self._xRect - def GetColor(self): + def GetColour(self): """ Returns the tab colour. """ return self._color - def SetColor(self, color): + def SetColour(self, color): """ Sets the tab colour. """ self._color = color @@ -798,8 +947,9 @@ class PageInfo: class FlatNotebookEvent(wx.PyCommandEvent): """ This events will be sent when a EVT_FLATNOTEBOOK_PAGE_CHANGED, - EVT_FLATNOTEBOOK_PAGE_CHANGING And EVT_FLATNOTEBOOK_PAGE_CLOSING is mapped in - the parent. + 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): @@ -860,2483 +1010,3155 @@ class FlatNotebookEvent(wx.PyCommandEvent): # ---------------------------------------------------------------------------- # -# Class FlatNotebookBase +# Class TabNavigatorWindow # ---------------------------------------------------------------------------- # -class FlatNotebookBase(wx.Panel): - - def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, - style=0, name="FlatNotebook"): - """ - Default class constructor. +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. + """ - All the parameters are as in wxPython class construction, except the - 'style': this can be assigned to whatever combination of FNB_* styles. - """ + def __init__(self, parent=None): + """ Default class constructor. Used internally.""" - self._bForceSelection = False - self._nPadding = 6 - self._nFrom = 0 - style |= wx.TAB_TRAVERSAL - self._pages = None - self._windows = [] + wx.Dialog.__init__(self, parent, wx.ID_ANY, "", style=0) - wx.Panel.__init__(self, parent, id, pos, size, style) + self._selectedItem = -1 + self._indexMap = [] - self._pages = StyledTabsContainer(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, style) + self._bmp = GetMondrianBitmap() - self.Bind(wx.EVT_NAVIGATION_KEY, self.OnNavigationKey) + sz = wx.BoxSizer(wx.VERTICAL) - self._pages._colorBorder = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW) + 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(1,1)) + font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) + font.SetWeight(wx.BOLD) + mem_dc.SetFont(font) - self._mainSizer = wx.BoxSizer(wx.VERTICAL) - self.SetSizer(self._mainSizer) + panelHeight = mem_dc.GetCharHeight() + panelHeight += 4 # Place a spacer of 2 pixels - self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_APPWORKSPACE)) + # 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)) - # Add the tab container to the sizer - self._mainSizer.Insert(0, self._pages, 0, wx.EXPAND) + sz.Add(self._panel) + sz.Add(self._listBox, 1, wx.EXPAND) + + self.SetSizer(sz) - # Set default page height - dc = wx.ClientDC(self) + # 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) - if "__WXGTK__" in wx.PlatformInfo: - # 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 = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) - boldFont.SetWeight(wx.FONTWEIGHT_BOLD) - dc.SetFont(boldFont) + 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() - width, height = dc.GetTextExtent("Tp") - tabHeight = height + FNB_HEIGHT_SPACER # We use 8 pixels as padding - self._pages.SetSizeHints(-1, tabHeight) + def OnKeyUp(self, event): + """Handles the wx.EVT_KEY_UP for the L{TabNavigatorWindow}.""" + + if event.GetKeyCode() == wx.WXK_CONTROL: + self.CloseDialog() - self._mainSizer.Layout() - self._pages._nFrom = self._nFrom - self._pDropTarget = FNBDropTarget(self) - self.SetDropTarget(self._pDropTarget) + 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 CreatePageContainer(self): - """ Creates the page container for the tabs. """ - return PageContainerBase(self, wx.ID_ANY) + 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 - def SetActiveTabTextColour(self, textColour): - """ Sets the text colour for the active tab. """ + # Skip previous selected page as well + if c == prevSel: + continue - self._pages._activeTextColor = textColour + 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 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 OnItemSelected(self, event): + """Handles the wx.EVT_LISTBOX_DCLICK event for the wx.ListBox inside L{TabNavigatorWindow}. """ + self.CloseDialog() - def AddPage(self, window, caption, selected=True, imgindex=-1): - """ - Add a page to the FlatNotebook. - Parameters: - - window: Specifies the new page. - - caption: Specifies the text for the new page. - - selected: Specifies whether the page should be selected. - - imgindex: Specifies the optional image index for the new page. + 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._pages.FireEvent(iter) + self.EndModal(wx.ID_OK) - Return value: - True if successful, False otherwise. - """ - # sanity check - if not window: - return False + def OnPanelPaint(self, event): + """Handles the wx.EVT_PAINT event for L{TabNavigatorWindow} top panel. """ - # reparent the window to us - window.Reparent(self) + dc = wx.PaintDC(self._panel) + rect = self._panel.GetClientRect() - # Add tab - bSelected = selected or not self._windows - curSel = self._pages.GetSelection() + bmp = wx.EmptyBitmap(rect.width, rect.height) - if not self._pages.IsShown(): - self._pages.Show() + mem_dc = wx.MemoryDC() + mem_dc.SelectObject(bmp) - self._pages.AddPage(caption, bSelected, imgindex) - self._windows.append(window) + endColour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW) + startColour = LightColour(endColour, 50) + PaintStraightGradientBox(mem_dc, rect, startColour, endColour) - self.Freeze() + # 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) - # 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, window, 1, wx.EXPAND) - - else: - - # We leave a space of 1 pixel around the window - self._mainSizer.Add(window, 1, wx.EXPAND) - - else: - - # Hide the page - window.Hide() + # 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) - self._mainSizer.Layout() - self.Thaw() - self.Refresh() + dc.DrawBitmap(bmp, 0, 0) - return True + def OnPanelEraseBg(self, event): + """Handles the wx.EVT_ERASE_BACKGROUND event for L{TabNavigatorWindow} top panel. """ - 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. - """ + pass - self._pages.SetImageList(imglist) +# ---------------------------------------------------------------------------- # +# Class FNBRenderer +# ---------------------------------------------------------------------------- # - def GetImageList(self): - """ Returns the associated image list. """ +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. """ - return self._pages.GetImageList() + self._tabXBgBmp = wx.EmptyBitmap(16, 16) + self._xBgBmp = wx.EmptyBitmap(16, 14) + self._leftBgBmp = wx.EmptyBitmap(16, 14) + self._rightBgBmp = wx.EmptyBitmap(16, 14) + self._tabHeight = None - def InsertPage(self, indx, page, text, select=True, imgindex=-1): - """ - Inserts a new page at the specified position. + def GetLeftButtonPos(self, pageContainer): + """ Returns the left button position in the navigation area. """ - Parameters: - - indx: Specifies the position of the new page. - - page: Specifies the new page. - - text: Specifies the text for the new page. - - select: Specifies whether the page should be selected. - - imgindex: Specifies the optional image index for the new page. + pc = pageContainer + style = pc.GetParent().GetWindowStyleFlag() + rect = pc.GetClientRect() + clientWidth = rect.width - Return value: - True if successful, False otherwise. - """ - - # sanity check - if not page: - return False + if style & FNB_NO_X_BUTTON: + return clientWidth - 38 + else: + return clientWidth - 54 - # reparent the window to us - page.Reparent(self) - if not self._windows: - - self.AddPage(page, text, select, imgindex) - return True - # Insert tab - bSelected = select or not self._windows - curSel = self._pages.GetSelection() - - indx = max(0, min(indx, len(self._windows))) + def GetRightButtonPos(self, pageContainer): + """ Returns the right button position in the navigation area. """ - if indx <= len(self._windows): - - self._windows.insert(indx, page) + pc = pageContainer + style = pc.GetParent().GetWindowStyleFlag() + rect = pc.GetClientRect() + clientWidth = rect.width + if style & FNB_NO_X_BUTTON: + return clientWidth - 22 else: - - self._windows.append(page) - - self._pages.InsertPage(indx, text, bSelected, imgindex) - - if indx <= curSel: - curSel = curSel + 1 + return clientWidth - 38 - 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) - - else: - - # Hide the page - page.Hide() - - self.Thaw() - self._mainSizer.Layout() - self.Refresh() + def GetDropArrowButtonPos(self, pageContainer): + """ Returns the drop down button position in the navigation area. """ - return True + return self.GetRightButtonPos(pageContainer) - def SetSelection(self, page): - """ - Sets the selection for the given page. - The call to this function generates the page changing events - """ + def GetXPos(self, pageContainer): + """ Returns the 'X' button position in the navigation area. """ - if page >= len(self._windows) or not self._windows: - return + pc = pageContainer + style = pc.GetParent().GetWindowStyleFlag() + rect = pc.GetClientRect() + clientWidth = rect.width + + if style & FNB_NO_X_BUTTON: + return clientWidth + else: + return clientWidth - 22 - # Support for disabed tabs - if not self._pages.GetEnabled(page) and len(self._windows) > 1 and not self._bForceSelection: - return - curSel = self._pages.GetSelection() + def GetButtonsAreaLength(self, pageContainer): + """ Returns the navigation area width. """ - # 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._mainSizer.Layout() - self._pages._iActivePage = page + pc = pageContainer + style = pc.GetParent().GetWindowStyleFlag() - self.Thaw() + # '' + 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 - self._pages.DoSetSelection(page) + # '<>' + 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 - def DeletePage(self, page): - """ - Deletes the specified page, and the associated window. - The call to this function generates the page changing events. - """ + # '<>x' + return 53 - if page >= len(self._windows): - return - # Fire a closing event - event = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CLOSING, self.GetId()) - event.SetSelection(page) - event.SetEventObject(self) - self.GetEventHandler().ProcessEvent(event) + def DrawLeftArrow(self, pageContainer, dc): + """ Draw the left navigation arrow. """ - # The event handler allows it? - if not event.IsAllowed(): + pc = pageContainer + + style = pc.GetParent().GetWindowStyleFlag() + if style & FNB_NO_NAV_BUTTONS: return - self.Freeze() + # Make sure that there are pages in the container + if not pc._pagesInfoVec: + return - # Delete the requested page - pageRemoved = self._windows[page] + # 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 the page is the current window, remove it from the sizer - # as well - if page == self._pages.GetSelection(): - self._mainSizer.Detach(pageRemoved) + if pc._nFrom == 0: + # Handle disabled arrow + arrowBmp = wx.BitmapFromXPMData(left_arrow_disabled_xpm) - # Remove it from the array as well - self._windows.pop(page) + arrowBmp.SetMask(wx.Mask(arrowBmp, MASK_COLOR)) - # Now we can destroy it in wxWidgets use Destroy instead of delete - pageRemoved.Destroy() + # Erase old bitmap + posx = self.GetLeftButtonPos(pc) + dc.DrawBitmap(self._leftBgBmp, posx, 6) - self.Thaw() + # Draw the new bitmap + dc.DrawBitmap(arrowBmp, posx, 6, True) - 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 DrawRightArrow(self, pageContainer, dc): + """ Draw the right navigation arrow. """ + pc = pageContainer + + style = pc.GetParent().GetWindowStyleFlag() + if style & FNB_NO_NAV_BUTTONS: + return - def DeleteAllPages(self): - """ Deletes all the pages. """ + # Make sure that there are pages in the container + if not pc._pagesInfoVec: + return - if not self._windows: - return False + # 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) - self.Freeze() - - for page in self._windows: - page.Destroy() + # 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) - self._windows = [] - self.Thaw() + arrowBmp.SetMask(wx.Mask(arrowBmp, MASK_COLOR)) - # Clear the container of the tabs as well - self._pages.DeleteAllPages() - return True + # erase old bitmap + posx = self.GetRightButtonPos(pc) + dc.DrawBitmap(self._rightBgBmp, posx, 6) + # Draw the new bitmap + dc.DrawBitmap(arrowBmp, posx, 6, 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 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 - def GetPage(self, page): - """ Returns the window at the given page position, or None. """ + # Make sure that there are pages in the container + if not pc._pagesInfoVec: + return - if page >= len(self._windows): - return None + 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) - return self._windows[page] + downBmp.SetMask(wx.Mask(downBmp, MASK_COLOR)) + # erase old bitmap + posx = self.GetDropArrowButtonPos(pc) + dc.DrawBitmap(self._xBgBmp, posx, 6) - def GetPageIndex(self, win): - """ Returns the index at which the window is found. """ + # Draw the new bitmap + dc.DrawBitmap(downBmp, posx, 6, True) - try: - return self._windows.index(win) - except: - return -1 + def DrawX(self, pageContainer, dc): + """ Draw the 'X' navigation button in the navigation area. """ - def GetSelection(self): - """ Returns the currently selected page, or -1 if none was selected. """ + pc = pageContainer - return self._pages.GetSelection() - + # Check if this style is enabled + style = pc.GetParent().GetWindowStyleFlag() + if style & FNB_NO_X_BUTTON: + return - def AdvanceSelection(self, bForward=True): - """ - Cycles through the tabs. - The call to this function generates the page changing events. - """ + # Make sure that there are pages in the container + if not pc._pagesInfoVec: + return - self._pages.AdvanceSelection(bForward) + # 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) - def GetPageCount(self): - """ Returns the number of pages in the FlatNotebook control. """ - return self._pages.GetPageCount() + # Draw the new bitmap + dc.DrawBitmap(xbmp, posx, 6, True) - def OnNavigationKey(self, event): - """ Handles the wx.EVT_NAVIGATION_KEY event for FlatNotebook. """ + def DrawTabX(self, pageContainer, dc, rect, tabIdx, btnStatus): + """ Draws the 'X' in the selected tab. """ - if event.IsWindowChange(): - # change pages - self.AdvanceSelection(event.GetDirection()) - else: - # pass to the parent - if self.GetParent(): - event.SetCurrentFocus(self) - self.GetParent().ProcessEvent(event) - + pc = pageContainer + if not pc.HasFlag(FNB_X_ON_TAB): + return - def GetPageShapeAngle(self, page_index): - """ Returns the angle associated to a tab. """ + # We draw the 'x' on the active tab only + if tabIdx != pc.GetSelection() or tabIdx < 0: + return - if page_index < 0 or page_index >= len(self._pages._pagesInfoVec): - return None, False + # Set the bitmap according to the button status - result = self._pages._pagesInfoVec[page_index].GetTabAngle() - return result, True + 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)) - def SetPageShapeAngle(self, page_index, angle): - """ Sets the angle associated to a tab. """ + # erase old button + dc.DrawBitmap(self._tabXBgBmp, rect.x, rect.y) - if page_index < 0 or page_index >= len(self._pages._pagesInfoVec): - return + # Draw the new bitmap + dc.DrawBitmap(xBmp, rect.x, rect.y, True) - if angle > 15: - return + # Update the vector + rr = wx.Rect(rect.x, rect.y, 14, 13) + pc._pagesInfoVec[tabIdx].SetXRect(rr) - self._pages._pagesInfoVec[page_index].SetTabAngle(angle) + def _GetBitmap(self, dc, rect, bmp): - def SetAllPagesShapeAngle(self, angle): - """ Sets the angle associated to all the tab. """ + 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 - if angle > 15: - return - - for ii in xrange(len(self._pages._pagesInfoVec)): - self._pages._pagesInfoVec[ii].SetTabAngle(angle) - - self.Refresh() + def DrawTabsLine(self, pageContainer, dc): + """ Draws a line over the tabs. """ - def GetPageBestSize(self): - """ Return the page best size. """ + pc = pageContainer + + clntRect = pc.GetClientRect() + clientRect3 = wx.Rect(0, 0, clntRect.width, clntRect.height) - return self._pages.GetClientSize() + 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) - def SetPageText(self, page, text): - """ Sets the text for the given page. """ + 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) - bVal = self._pages.SetPageText(page, text) - self._pages.Refresh() - return bVal + def CalcTabWidth(self, pageContainer, tabIdx, tabHeight): + """ Calculates the width of the input tab. """ + pc = pageContainer + dc = wx.MemoryDC() + dc.SelectObject(wx.EmptyBitmap(1,1)) - 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. - """ + boldFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) + boldFont.SetWeight(wx.FONTWEIGHT_BOLD) - self._nPadding = padding.GetWidth() + 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)) - def GetTabArea(self): - """ Returns the associated page. """ + # Set a minimum size to a tab + if width < 20: + width = 20 - return self._pages + 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 - def GetPadding(self): - """ Returns the amount of space around each page's icon and label, in pixels. """ + tabWidth += pc._pParent.GetPadding() + spacer - return self._nPadding - - - def SetWindowStyleFlag(self, style): - """ Sets the FlatNotebook window style flags. """ + if pc.IsDefaultTabs(): + # Default style + tabWidth += 2*shapePoints - wx.Panel.SetWindowStyleFlag(self, style) + hasImage = pc._ImageList != None and pc._pagesInfoVec[tabIdx].GetImageIndex() != -1 - if self._pages: + # For VC71 style, we only add the icon size (16 pixels) + if hasImage: - # For changing the tab position (i.e. placing them top/bottom) - # refreshing the tab container is not enough - self.SetSelection(self._pages._iActivePage) + if not pc.IsDefaultTabs(): + tabWidth += 16 + pc._pParent.GetPadding() + else: + # Default style + tabWidth += 16 + pc._pParent.GetPadding() + shapePoints/2 + + return tabWidth - def RemovePage(self, page): - """ Deletes the specified page, without deleting the associated window. """ + def CalcTabHeight(self, pageContainer): + """ Calculates the height of the input tab. """ - if page >= len(self._windows): - return False + if self._tabHeight: + return self._tabHeight - # Fire a closing event - event = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CLOSING, self.GetId()) - event.SetSelection(page) - event.SetEventObject(self) - self.GetEventHandler().ProcessEvent(event) + pc = pageContainer + dc = wx.MemoryDC() + dc.SelectObject(wx.EmptyBitmap(1,1)) - # The event handler allows it? - if not event.IsAllowed(): - return False + # 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 - self.Freeze() + if "__WXGTK__" in wx.PlatformInfo: + boldFont.SetWeight(wx.FONTWEIGHT_BOLD) + dc.SetFont(boldFont) - # Remove the requested page - pageRemoved = self._windows[page] + 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 the page is the current window, remove it from the sizer - # as well - if page == self._pages.GetSelection(): - self._mainSizer.Detach(pageRemoved) + self._tabHeight = tabHeight - # Remove it from the array as well - self._windows.pop(page) - self.Thaw() + return tabHeight - self._pages.DoDeletePage(page) - return True + 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() - def SetRightClickMenu(self, menu): - """ Sets the popup menu associated to a right click on a tab. """ + # Calculate the number of rows required for drawing the tabs + rect = pc.GetClientRect() + clientWidth = rect.width - self._pages._pRightClickMenu = menu + # 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) - def GetPageText(self, page): - """ Returns the tab caption. """ + noselBrush = wx.Brush(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNFACE)) + selBrush = wx.Brush(pc._activeTabColor) - return self._pages.GetPageText(page) + 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) - def SetGradientColors(self, fr, to, border): - """ Sets the gradient colours for the tab. """ + # 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) - self._pages._colorFrom = fr - self._pages._colorTo = to - self._pages._colorBorder = border + # 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) - def SetGradientColorFrom(self, fr): - """ Sets the starting colour for the gradient. """ + #--------------------------------------- + # 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) - self._pages._colorFrom = fr + # 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) - def SetGradientColorTo(self, to): - """ Sets the ending colour for the gradient. """ + 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] - self._pages._colorTo = to + 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) - def SetGradientColorBorder(self, border): - """ Sets the tab border colour. """ + 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) - self._pages._colorBorder = border + # Restore the pen + dc.SetPen(borderPen) + + # Draw labels + normalFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) + boldFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) + boldFont.SetWeight(wx.FONTWEIGHT_BOLD) + dc.SetFont(boldFont) + posx = pc._pParent.GetPadding() - def GetGradientColorFrom(self): - """ Gets first gradient colour. """ + # Update all the tabs from 0 to 'pc._nFrom' to be non visible + for i in xrange(pc._nFrom): + + pc._pagesInfoVec[i].SetPosition(wx.Point(-1, -1)) + pc._pagesInfoVec[i].GetRegion().Clear() - return self._pages._colorFrom + 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]) - def GetGradientColorTo(self): - """ Gets second gradient colour. """ + # Add the padding to the tab width + # Tab width: + # +-----------------------------------------------------------+ + # | PADDING | IMG | IMG_PADDING | TEXT | PADDING | x |PADDING | + # +-----------------------------------------------------------+ + tabWidth = self.CalcTabWidth(pageContainer, i, tabHeight) - return self._pages._colorTo + # 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() - def GetGradientColorBorder(self): - """ Gets the tab border colour. """ + # 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)) - return self._pages._colorBorder + # 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) - def GetActiveTabTextColour(self): - """ Get the active tab text colour. """ + # Update the tab position & size + posy = (pc.HasFlag(FNB_BOTTOM) and [0] or [VERTICAL_BORDER_PADDING])[0] - return self._pages._activeTextColor + 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) - def SetPageImageIndex(self, page, imgindex): + def DrawDragHint(self, pc, tabIdx): """ - Sets the image index for the given page. Image is an index into the - image list which was set with SetImageList. + Draws tab drag hint, the default implementation is to do nothing. + You can override this function to provide a nice feedback to user. """ + + pass - self._pages.SetPageImageIndex(page, imgindex) + def NumberTabsCanFit(self, pageContainer, fr=-1): - def GetPageImageIndex(self, page): - """ - Returns the image index for the given page. Image is an index into the - image list which was set with SetImageList. - """ + pc = pageContainer + + rect = pc.GetClientRect() + clientWidth = rect.width - return self._pages.GetPageImageIndex(page) + vTabInfo = [] + tabHeight = self.CalcTabHeight(pageContainer) - def GetEnabled(self, page): - """ Returns whether a tab is enabled or not. """ + # The drawing starts from posx + posx = pc._pParent.GetPadding() - return self._pages.GetEnabled(page) + if fr < 0: + fr = pc._nFrom + for i in xrange(fr, len(pc._pagesInfoVec)): - def Enable(self, page, enabled=True): - """ Enables or disables a tab. """ + tabWidth = self.CalcTabWidth(pageContainer, i, tabHeight) + if posx + tabWidth + self.GetButtonsAreaLength(pc) >= clientWidth: + break; - if page >= len(self._windows): - return + # Add a result to the returned vector + tabRect = wx.Rect(posx, VERTICAL_BORDER_PADDING, tabWidth , tabHeight) + vTabInfo.append(tabRect) - self._windows[page].Enable(enabled) - self._pages.Enable(page, enabled) + # Advance posx + posx += tabWidth + FNB_HEIGHT_SPACER + return vTabInfo - def GetNonActiveTabTextColour(self): - """ Returns the non active tabs text colour. """ + +# ---------------------------------------------------------------------------- # +# Class FNBRendererMgr +# A manager that handles all the renderers defined below and calls the +# appropriate one when drawing is needed +# ---------------------------------------------------------------------------- # - return self._pages._nonActiveTextColor +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 - def SetNonActiveTabTextColour(self, color): - """ Sets the non active tabs text colour. """ + 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()}) - self._pages._nonActiveTextColor = color + def GetRenderer(self, style): + """ Returns the current renderer based on the style selected. """ - def SetTabAreaColour(self, color): - """ Sets the area behind the tabs colour. """ - - self._pages._tabAreaColor = color + # 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] - def GetTabAreaColour(self): - """ Returns the area behind the tabs colour. """ + if style & FNB_FANCY_TABS: + return self._renderers[FNB_FANCY_TABS] - return self._pages._tabAreaColor + if style & FNB_VC8: + return self._renderers[FNB_VC8] + # the default is to return the default renderer + return self._renderers[-1] - def SetActiveTabColour(self, color): - """ Sets the active tab colour. """ - self._pages._activeTabColor = color +#------------------------------------------ +# Default renderer +#------------------------------------------ +class FNBRendererDefault(FNBRenderer): + """ + This class handles the drawing of tabs using the I{Standard} renderer. + """ + + def __init__(self): + """ Default class constructor. """ - def GetActiveTabColour(self): - """ Returns the active tab colour. """ + FNBRenderer.__init__(self) + - return self._pages._activeTabColor + 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 -# ---------------------------------------------------------------------------- # -# Class PageContainerBase -# Acts as a container for the pages you add to FlatNotebook -# ---------------------------------------------------------------------------- # + 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] -class PageContainerBase(wx.Panel): + 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] - 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._tabXBgBmp = wx.EmptyBitmap(16, 16) - self._xBgBmp = wx.EmptyBitmap(16, 14) - self._leftBgBmp = wx.EmptyBitmap(16, 14) - self._rightBgBmp = wx.EmptyBitmap(16, 14) + tabPoints[2].x = tabPoints[1].x+2 + tabPoints[2].y = (pc.HasFlag(FNB_BOTTOM) and [tabHeight - VERTICAL_BORDER_PADDING] or [VERTICAL_BORDER_PADDING])[0] - self._pRightClickMenu = None - self._nXButtonStatus = FNB_BTN_NONE - self._pParent = parent - self._nRightButtonStatus = FNB_BTN_NONE - self._nLeftButtonStatus = FNB_BTN_NONE - self._nTabXButtonStatus = FNB_BTN_NONE + 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] - self._pagesInfoVec = [] + 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] - 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) + 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] - self._nFrom = 0 - self._isdragging = False + tabPoints[6].x = tabPoints[0].x + tabPoints[6].y = tabPoints[0].y - wx.Panel.__init__(self, parent, id, pos, size, style) + 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) - self._pDropTarget = FNBDropTarget(self) - self.SetDropTarget(self._pDropTarget) + if tabIdx == pc.GetSelection(): + + savePen = dc.GetPen() + whitePen = wx.Pen(wx.WHITE) + whitePen.SetWidth(1) + dc.SetPen(whitePen) - 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) + 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 + # ----------------------------------- - def GetButtonAreaWidth(self): - """ Returns the width of the navigation button area. """ + # 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] - style = self.GetParent().GetWindowStyleFlag() - btnareawidth = 2*self._pParent._nPadding + if hasImage: + textOffset = 2*pc._pParent._nPadding + 16 + shapePoints/2 + else: + textOffset = pc._pParent._nPadding + shapePoints/2 - if style & FNB_NO_X_BUTTON == 0: - btnareawidth += BUTTON_SPACE + textOffset += 2 - if style & FNB_NO_NAV_BUTTONS == 0: - btnareawidth += 2*BUTTON_SPACE + 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) - return btnareawidth + # 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) - def OnEraseBackground(self, event): - """ Handles the wx.EVT_ERASE_BACKGROUND event for PageContainerBase (does nothing).""" + # Draw the tab + self.DrawTabX(pc, dc, x_rect, tabIdx, btnStatus) + - pass +#------------------------------------------------------------------ +# Visual studio 7.1 +#------------------------------------------------------------------ - - def OnPaint(self, event): - """ Handles the wx.EVT_PAINT event for PageContainerBase.""" +class FNBRendererVC71(FNBRenderer): + """ + This class handles the drawing of tabs using the I{VC71} renderer. + """ - dc = wx.BufferedPaintDC(self) + def __init__(self): + """ Default class constructor. """ - if "__WXMAC__" in wx.PlatformInfo: - # Works well on MSW & GTK, however this lines should be skipped on MAC - if len(self._pagesInfoVec) == 0 or self._nFrom >= len(self._pagesInfoVec): - self.Hide() - event.Skip() - return - - # Get the text hight - style = self.GetParent().GetWindowStyleFlag() + FNBRenderer.__init__(self) - # 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 = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) - boldFont.SetWeight(wx.FONTWEIGHT_BOLD) - if "__WXGTK__" in wx.PlatformInfo: - dc.SetFont(boldFont) - width, height = dc.GetTextExtent("Tp") + def DrawTab(self, pageContainer, dc, posx, tabIdx, tabWidth, tabHeight, btnStatus): + """ Draws a tab using the I{VC71} style. """ - tabHeight = height + FNB_HEIGHT_SPACER # We use 8 pixels as padding + # Visual studio 7.1 style + borderPen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)) + pc = pageContainer - # Calculate the number of rows required for drawing the tabs - rect = self.GetClientRect() - clientWidth = rect.width + 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]) - # Set the maximum client size - self.SetSizeHints(self.GetButtonsAreaLength(), tabHeight) - borderPen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)) + if tabIdx == pc.GetSelection(): + + posy = (pc.HasFlag(FNB_BOTTOM) and [0] or [VERTICAL_BORDER_PADDING])[0] + tabH = (pc.HasFlag(FNB_BOTTOM) and [tabHeight - 5] or [tabHeight - 3])[0] + dc.DrawRectangle(posx, posy, tabWidth, tabH) - if style & FNB_VC71: - backBrush = wx.Brush(wx.Colour(247, 243, 233)) - else: - backBrush = wx.Brush(self._tabAreaColor) + # Draw a black line on the left side of the + # rectangle + dc.SetPen(wx.BLACK_PEN) - noselBrush = wx.Brush(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNFACE)) - selBrush = wx.Brush(self._activeTabColor) + blackLineY1 = VERTICAL_BORDER_PADDING + blackLineY2 = tabH + dc.DrawLine(posx + tabWidth, blackLineY1, posx + tabWidth, blackLineY2) - size = self.GetSize() + # 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 - # Background - dc.SetTextBackground((style & FNB_VC71 and [wx.Colour(247, 243, 233)] or [self.GetBackgroundColour()])[0]) - dc.SetTextForeground(self._activeTextColor) - dc.SetBrush(backBrush) + 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) - # If border style is set, set the pen to be border pen - if style & FNB_TABS_BORDER_SIMPLE: - dc.SetPen(borderPen) + # 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: - pc = (self.HasFlag(FNB_VC71) and [wx.Colour(247, 243, 233)] or [self.GetBackgroundColour()])[0] - dc.SetPen(wx.Pen(pc)) - dc.DrawRectangle(0, 0, size.x, size.y) + # We dont draw a rectangle for non selected tabs, but only + # vertical line on the left - # Take 3 bitmaps for the background for the buttons + 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) - mem_dc = wx.MemoryDC() + # ----------------------------------- + # Text and image drawing + # ----------------------------------- - #--------------------------------------- - # X button - #--------------------------------------- - rect = wx.Rect(self.GetXPos(), 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) + # 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 [5] or [8])[0] - #--------------------------------------- - # Right button - #--------------------------------------- - rect = wx.Rect(self.GetRightButtonPos(), 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) + if hasImage: + textOffset = 2*pc._pParent._nPadding + 16 + else: + textOffset = pc._pParent._nPadding - #--------------------------------------- - # Left button - #--------------------------------------- - rect = wx.Rect(self.GetLeftButtonPos(), 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) + if tabIdx != pc.GetSelection(): - # We always draw the bottom/upper line of the tabs - # regradless the style - dc.SetPen(borderPen) - self.DrawTabsLine(dc) + # 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 - # Restore the pen - dc.SetPen(borderPen) + # 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) - if self.HasFlag(FNB_VC71): - - greyLineYVal = self.HasFlag((FNB_BOTTOM and [0] or [size.y - 2])[0]) - whiteLineYVal = self.HasFlag((FNB_BOTTOM and [3] or [size.y - 3])[0]) + # Draw the tab + self.DrawTabX(pc, dc, x_rect, tabIdx, btnStatus) - 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 +#------------------------------------------------------------------ +# Fancy style +#------------------------------------------------------------------ - for num in xrange(3): - dc.DrawLine(0, greyLineYVal + num, size.x, greyLineYVal + num) +class FNBRendererFancy(FNBRenderer): + """ + This class handles the drawing of tabs using the I{Fancy} renderer. + """ - wbPen = wx.Pen((self.HasFlag(FNB_BOTTOM) and [wx.BLACK] or [wx.WHITE])[0]) - dc.SetPen(wbPen) - dc.DrawLine(1, whiteLineYVal, size.x - 1, whiteLineYVal) + def __init__(self): + """ Default class constructor. """ - # Restore the pen - dc.SetPen(borderPen) + 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(): - if "__WXMAC__" in wx.PlatformInfo: - # On MAC, Add these lines so the tab background gets painted - if len(self._pagesInfoVec) == 0 or self._nFrom >= len(self._pagesInfoVec): - self.Hide() - return - - # Draw labels - dc.SetFont(boldFont) - posx = self._pParent.GetPadding() + posy = (pc.HasFlag(FNB_BOTTOM) and [2] or [VERTICAL_BORDER_PADDING])[0] + th = tabHeight - 5 - # Update all the tabs from 0 to '_nFrom' to be non visible - for ii in xrange(self._nFrom): - self._pagesInfoVec[ii].SetPosition(wx.Point(-1, -1)) - self._pagesInfoVec[ii].GetRegion().Clear() + rect = wx.Rect(posx, posy, tabWidth, th) - 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 - 2] or [tabHeight])[0] + 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] - #---------------------------------------------------------- - # Go over and draw the visible tabs - #---------------------------------------------------------- + PaintStraightGradientBox(dc, rect, col1, col2) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + dc.SetPen(pen) + dc.DrawRectangleRect(rect) - count = self._nFrom + # 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) - for ii in xrange(self._nFrom, len(self._pagesInfoVec)): + else: - if style != FNB_VC71: - shapePoints = int(tabHeight*math.tan(float(self._pagesInfoVec[ii].GetTabAngle())/180.0*math.pi)) - else: - shapePoints = 0 - + # We dont draw a rectangle for non selected tabs, but only + # vertical line on the left dc.SetPen(borderPen) - dc.SetBrush((ii==self.GetSelection() and [selBrush] or [noselBrush])[0]) + dc.DrawLine(posx + tabWidth, VERTICAL_BORDER_PADDING + 3, posx + tabWidth, tabHeight - 4) + - # 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(self.GetPageText(ii)) + # ----------------------------------- + # Text and image drawing + # ----------------------------------- - # Now set the font to the correct font - dc.SetFont((ii==self.GetSelection() and [boldFont] or [normalFont])[0]) + # 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] - # Set a minimum size to a tab - if width < 20: - width = 20 + if hasImage: + textOffset = 2*pc._pParent._nPadding + 16 + else: + textOffset = pc._pParent._nPadding - # Add the padding to the tab width + 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 = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) + 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 = 2*self._pParent._nPadding + width - imageYCoord = (self.HasFlag(FNB_BOTTOM) and [6] or [8])[0] + 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._pParent.GetGradientColourTo() + col1 = pc._pParent.GetGradientColourFrom() + + # 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 - # Style to add a small 'x' button on the top right - # of the tab - if style & FNB_X_ON_TAB and ii == self.GetSelection(): - - # The xpm image that contains the 'x' button is 9 pixles - tabWidth += self._pParent._nPadding + 9 - - if not (style & FNB_VC71) and not (style & FNB_FANCY_TABS): - # Default style - tabWidth += 2*shapePoints + # According to the equation y = ax + b => x = (y-b)/a + # We know the first 2 points - hasImage = self._ImageList != None and self._pagesInfoVec[ii].GetImageIndex() != -1 + # Vertical line + if x1 == x2: + return int(x1) + + a = (y2 - y1)/(x2 - x1) + b = y1 - ((y2 - y1)/(x2 - x1))*x1 - # For VC71 style, we only add the icon size (16 pixels) - if hasImage: - - if style & FNB_VC71 or style & FNB_FANCY_TABS: - tabWidth += 16 + self._pParent._nPadding - else: - # Default style - tabWidth += 16 + self._pParent._nPadding + shapePoints/2 - - # Check if we can draw more - if posx + tabWidth + self.GetButtonsAreaLength() >= clientWidth: - break - - count = count + 1 + if a == 0: + return int(x1) - # By default we clean the tab region - self._pagesInfoVec[ii].GetRegion().Clear() + x = (y - b)/a - # Clean the 'x' buttn on the tab - # 'Clean' rectanlge is a rectangle with width or height - # with values lower than or equal to 0 - self._pagesInfoVec[ii].GetXRect().SetSize(wx.Size(-1, -1)) + return int(x) - # Draw the tab - if style & FNB_FANCY_TABS: - self.DrawFancyTab(dc, posx, ii, tabWidth, tabHeight) - elif style & FNB_VC71: - self.DrawVC71Tab(dc, posx, ii, tabWidth, tabHeight) - else: - self.DrawStandardTab(dc, posx, ii, tabWidth, tabHeight) - # The width of the images are 16 pixels - if hasImage: - textOffset = 2*self._pParent._nPadding + 16 + shapePoints/2 - else: - textOffset = self._pParent._nPadding + shapePoints/2 + def NumberTabsCanFit(self, pageContainer, fr=-1): + """ Returns the number of tabs that can fit in the visible area. """ - # After several testing, it seems that we can draw - # the text 2 pixles to the right - this is done only - # for the standard tabs - - if not self.HasFlag(FNB_FANCY_TABS): - textOffset += 2 + pc = pageContainer + + rect = pc.GetClientRect() + clientWidth = rect.width - if ii != self.GetSelection(): - # Set the text background to be like the vertical lines - dc.SetTextForeground(self._nonActiveTextColor) - - if hasImage: - - imageXOffset = textOffset - 16 - self._pParent._nPadding - self._ImageList.Draw(self._pagesInfoVec[ii].GetImageIndex(), dc, - posx + imageXOffset, imageYCoord, - wx.IMAGELIST_DRAW_TRANSPARENT, True) + # Empty results + vTabInfo = [] + tabHeight = self.CalcTabHeight(pageContainer) - dc.DrawText(self.GetPageText(ii), posx + textOffset, imageYCoord) + # The drawing starts from posx + posx = pc._pParent.GetPadding() + + if fr < 0: + fr = pc._nFrom - # From version 1.2 - a style to add 'x' button - # on a tab - - if self.HasFlag(FNB_X_ON_TAB) and ii == self.GetSelection(): - - textWidth, textHeight = dc.GetTextExtent(self.GetPageText(ii)) - tabCloseButtonXCoord = posx + textOffset + textWidth + 1 + for i in xrange(fr, len(pc._pagesInfoVec)): + + vc8glitch = tabHeight + FNB_HEIGHT_SPACER + tabWidth = self.CalcTabWidth(pageContainer, i, tabHeight) - # 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) - mem_dc = wx.MemoryDC() - mem_dc.SelectObject(self._tabXBgBmp) - mem_dc.Blit(0, 0, x_rect.width, x_rect.height, dc, x_rect.x, x_rect.y) - mem_dc.SelectObject(wx.NullBitmap) + if posx + tabWidth + vc8glitch + self.GetButtonsAreaLength(pc) >= clientWidth: + break - # Draw the tab - self.DrawTabX(dc, x_rect, ii) - - # Restore the text forground - dc.SetTextForeground(self._activeTextColor) + # Add a result to the returned vector + tabRect = wx.Rect(posx, VERTICAL_BORDER_PADDING, tabWidth, tabHeight) + vTabInfo.append(tabRect) - # Update the tab position & size - posy = (style & FNB_BOTTOM and [0] or [VERTICAL_BORDER_PADDING])[0] - - self._pagesInfoVec[ii].SetPosition(wx.Point(posx, posy)) - self._pagesInfoVec[ii].SetSize(wx.Size(tabWidth, tabHeight)) - posx += tabWidth - - # Update all tabs that can not fit into the screen as non-visible - for ii in xrange(count, len(self._pagesInfoVec)): - - self._pagesInfoVec[ii].SetPosition(wx.Point(-1, -1)) - self._pagesInfoVec[ii].GetRegion().Clear() + # Advance posx + posx += tabWidth + FNB_HEIGHT_SPACER - # Draw the left/right/close buttons - # Left arrow - self.DrawLeftArrow(dc) - self.DrawRightArrow(dc) - self.DrawX(dc) + return vTabInfo + +# ---------------------------------------------------------------------------- # +# Class FlatNotebook +# ---------------------------------------------------------------------------- # - def DrawFancyTab(self, dc, posx, tabIdx, tabWidth, tabHeight): +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"): """ - Fancy tabs - like with VC71 but with the following differences: - - The Selected tab is colored with gradient color + 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) - borderPen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)) - pen = (tabIdx==self.GetSelection() and [wx.Pen(self._colorBorder)] \ - or [wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE))])[0] + self._pages = PageContainer(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, style) + + self.Bind(wx.EVT_NAVIGATION_KEY, self.OnNavigationKey) + + self.Init() - fnb_bottom = self.HasFlag(FNB_BOTTOM) - if tabIdx == self.GetSelection(): + def Init(self): + """ Initializes all the class attributes. """ - posy = (fnb_bottom and [2] or [VERTICAL_BORDER_PADDING])[0] - th = (fnb_bottom and [tabHeight - 2] or [tabHeight - 5])[0] + self._pages._colorBorder = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW) - rect = wx.Rect(posx, posy, tabWidth, th) - self.FillGradientColor(dc, rect) - dc.SetBrush(wx.TRANSPARENT_BRUSH) - dc.SetPen(pen) - dc.DrawRectangleRect(rect) + self._mainSizer = wx.BoxSizer(wx.VERTICAL) + self.SetSizer(self._mainSizer) - # erase the bottom/top line of the rectangle - dc.SetPen(wx.Pen(self._colorFrom)) - if 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) + # 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) - else: + if "__WXGTK__" in wx.PlatformInfo: + # 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 + boldFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) + boldFont.SetWeight(wx.FONTWEIGHT_BOLD) + dc.SetFont(boldFont) - # 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) + 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() - def DrawVC71Tab(self, dc, posx, tabIdx, tabWidth, tabHeight): - """ Draws tabs with VC71 style. """ + self._pages._nFrom = self._nFrom + self._pDropTarget = FNBDropTarget(self) + self.SetDropTarget(self._pDropTarget) - fnb_bottom = self.HasFlag(FNB_BOTTOM) - - # Visual studio 7.1 style - borderPen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)) - dc.SetPen((tabIdx==self.GetSelection() and [wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE))] or [borderPen])[0]) - dc.SetBrush((tabIdx==self.GetSelection() and [wx.Brush(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE))] or \ - [wx.Brush(wx.Colour(247, 243, 233))])[0]) - if tabIdx == self.GetSelection(): - - posy = (fnb_bottom and [0] or [VERTICAL_BORDER_PADDING])[0] - dc.DrawRectangle(posx, posy, tabWidth, tabHeight - 1) + def SetActiveTabTextColour(self, textColour): + """ Sets the text colour for the active tab. """ - # Draw a black line on the left side of the - # rectangle - dc.SetPen(wx.BLACK_PEN) + self._pages._activeTextColor = textColour - blackLineY1 = VERTICAL_BORDER_PADDING - blackLineY2 = (fnb_bottom and [self.GetSize().y - 5] or [self.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 thick white line on top of the rectangle - # Otherwise, draw a thin (1 pixel) black line at the bottom + def OnDropTarget(self, x, y, nTabPage, wnd_oldContainer): + """ Handles the drop action from a DND operation. """ - pen = wx.Pen((fnb_bottom and [wx.BLACK] or [wx.WHITE])[0]) - dc.SetPen(pen) - whiteLinePosY = (fnb_bottom and [blackLineY2] or [VERTICAL_BORDER_PADDING])[0] - dc.DrawLine(posx , whiteLinePosY, posx + tabWidth + 1, whiteLinePosY) + return self._pages.OnDropTarget(x, y, nTabPage, wnd_oldContainer) - # Draw a white vertical line to the left of the tab - dc.SetPen(wx.WHITE_PEN) - if not 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 = (fnb_bottom and [VERTICAL_BORDER_PADDING + 2] or [VERTICAL_BORDER_PADDING + 1])[0] - blackLineY2 = self.GetSize().y - 5 - dc.DrawLine(posx + tabWidth, blackLineY1, posx + tabWidth, blackLineY2) - + def GetPreviousSelection(self): + """ Returns the previous selection. """ + + return self._pages._iPreviousActivePage - def DrawStandardTab(self, dc, posx, tabIdx, tabWidth, tabHeight): - """ Draws tabs with standard style. """ - fnb_bottom = self.HasFlag(FNB_BOTTOM) + 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. - # Default style - borderPen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)) + Return value: + True if successful, False otherwise. + """ - tabPoints = [wx.Point() for ii in xrange(7)] - tabPoints[0].x = posx - tabPoints[0].y = (fnb_bottom and [2] or [tabHeight - 2])[0] + # sanity check + if not page: + return False - tabPoints[1].x = int(posx+(tabHeight-2)*math.tan(float(self._pagesInfoVec[tabIdx].GetTabAngle())/180.0*math.pi)) - tabPoints[1].y = (fnb_bottom and [tabHeight - (VERTICAL_BORDER_PADDING+2)] or [(VERTICAL_BORDER_PADDING+2)])[0] + # reparent the window to us + page.Reparent(self) - tabPoints[2].x = tabPoints[1].x+2 - tabPoints[2].y = (fnb_bottom and [tabHeight - VERTICAL_BORDER_PADDING] or [VERTICAL_BORDER_PADDING])[0] + # Add tab + bSelected = select or len(self._windows) == 0 - tabPoints[3].x = int(posx+tabWidth-(tabHeight-2)*math.tan(float(self._pagesInfoVec[tabIdx].GetTabAngle())/180.0*math.pi))-2 - tabPoints[3].y = (fnb_bottom and [tabHeight - VERTICAL_BORDER_PADDING] or [VERTICAL_BORDER_PADDING])[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() or len(self._windows) == 0: + bSelected = True + + curSel = self._pages.GetSelection() - tabPoints[4].x = tabPoints[3].x+2 - tabPoints[4].y = (fnb_bottom and [tabHeight - (VERTICAL_BORDER_PADDING+2)] or [(VERTICAL_BORDER_PADDING+2)])[0] + if not self._pages.IsShown(): + self._pages.Show() - tabPoints[5].x = int(tabPoints[4].x+(tabHeight-2)*math.tan(float(self._pagesInfoVec[tabIdx].GetTabAngle())/180.0*math.pi)) - tabPoints[5].y = (fnb_bottom and [2] or [tabHeight - 2])[0] + self._pages.AddPage(text, bSelected, imageId) + self._windows.append(page) - tabPoints[6].x = tabPoints[0].x - tabPoints[6].y = tabPoints[0].y + self.Freeze() - if tabIdx == self.GetSelection(): - - # Draw the tab as rounded rectangle - dc.DrawPolygon(tabPoints) - - else: + # Check if a new selection was made + if bSelected: - if tabIdx != self.GetSelection() - 1: + if curSel >= 0: - # Draw a vertical line to the right of the text - pt1x = tabPoints[5].x - pt1y = (fnb_bottom and [4] or [tabHeight - 6])[0] - pt2x = tabPoints[5].x - pt2y = (fnb_bottom and [tabHeight - 4] or [4])[0] - dc.DrawLine(pt1x, pt1y, pt2x, pt2y) + # Remove the window from the main sizer + self._mainSizer.Detach(self._windows[curSel]) + self._windows[curSel].Hide() - if tabIdx == self.GetSelection(): - - savePen = dc.GetPen() - whitePen = wx.Pen(wx.WHITE) - whitePen.SetWidth(1) - dc.SetPen(whitePen) + 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) - secPt = wx.Point(tabPoints[5].x + 1, tabPoints[5].y) - dc.DrawLine(tabPoints[0].x, tabPoints[0].y, secPt.x, secPt.y) + # Fire a wxEVT_FLATNOTEBOOK_PAGE_CHANGED event + event.SetEventType(wxEVT_FLATNOTEBOOK_PAGE_CHANGED) + event.SetOldSelection(oldSelection) + self.GetEventHandler().ProcessEvent(event) + + else: - # Restore the pen - dc.SetPen(savePen) - + # Hide the page + page.Hide() - def AddPage(self, caption, selected=True, imgindex=-1): + self.Thaw() + self._mainSizer.Layout() + self.Refresh() + + return True + + + def SetImageList(self, imageList): + """ + Sets the image list for the page control. It does not take ownership + of the image list, you must delete it yourself. """ - Add a page to the FlatNotebook. - Parameters: - - window: Specifies the new page. - - caption: Specifies the text for the new page. - - selected: Specifies whether the page should be selected. - - imgindex: Specifies the optional image index for the new page. - - Return value: - True if successful, False otherwise. - """ + self._pages.SetImageList(imageList) - if selected: - - self._iActivePage = len(self._pagesInfoVec) + + def GetImageList(self): + """ Returns the associated image list. """ - # Create page info and add it to the vector - pageInfo = PageInfo(caption, imgindex) - self._pagesInfoVec.append(pageInfo) - self.Refresh() + return self._pages.GetImageList() - def InsertPage(self, indx, text, selected=True, imgindex=-1): + def InsertPage(self, indx, page, text, select=True, imageId=-1): """ Inserts a new page at the specified position. - Parameters: - - indx: Specifies the position of the new page. - - page: Specifies the new page. - - text: Specifies the text for the new page. - - select: Specifies whether the page should be selected. - - imgindex: Specifies the optional image index for the new page. + @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. """ - if selected: - - 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 PageContainerBase. """ + # sanity check + if not page: + return False - self.Refresh() # Call on paint - event.Skip() + # reparent the window to us + page.Reparent(self) + if not self._windows: + + self.AddPage(page, text, select, imageId) + return True - def OnMiddleDown(self, event): - """ Handles the wx.EVT_MIDDLE_DOWN events for PageContainerBase. """ + # Insert tab + bSelected = select or not self._windows + curSel = self._pages.GetSelection() + + indx = max(0, min(indx, len(self._windows))) - # Test if this style is enabled - style = self.GetParent().GetWindowStyleFlag() + if indx <= len(self._windows): - if not style & FNB_MOUSE_MIDDLE_CLOSES_TABS: - return + self._windows.insert(indx, page) + + else: + + self._windows.append(page) - where, tabIdx = self.HitTest(event.GetPosition()) + if bSelected: - if where == FNB_TAB: - self.DeletePage(tabIdx) + 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() or len(self._windows) == 0: + bSelected = True - event.Skip() + self._pages.InsertPage(indx, text, bSelected, imageId) + + if indx <= curSel: + curSel = curSel + 1 + self.Freeze() - def OnRightDown(self, event): - """ Handles the wx.EVT_RIGHT_DOWN events for PageContainerBase. """ + # 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) - if self._pRightClickMenu: + # Fire a wxEVT_FLATNOTEBOOK_PAGE_CHANGED event + event.SetEventType(wxEVT_FLATNOTEBOOK_PAGE_CHANGED) + event.SetOldSelection(oldSelection) + self.GetEventHandler().ProcessEvent(event) - where, tabIdx = self.HitTest(event.GetPosition()) + else: + + # Hide the page + page.Hide() - if where in [FNB_TAB, FNB_TAB_X]: + self.Thaw() + self._mainSizer.Layout() + self.Refresh() - 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() + return True - def OnLeftDown(self, event): - """ Handles the wx.EVT_LEFT_DOWN events for PageContainerBase. """ + def SetSelection(self, page): + """ + Sets the selection for the given page. + The call to this function generates the page changing events + """ - # Reset buttons status - self._nXButtonStatus = FNB_BTN_NONE - self._nLeftButtonStatus = FNB_BTN_NONE - self._nRightButtonStatus = FNB_BTN_NONE - self._nTabXButtonStatus = FNB_BTN_NONE + if page >= len(self._windows) or not self._windows: + return - self._nLeftClickZone, tabIdx = self.HitTest(event.GetPosition()) + # Support for disabed tabs + if not self._pages.GetEnabled(page) and len(self._windows) > 1 and not self._bForceSelection: + return - if 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() + curSel = self._pages.GetSelection() - 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(): + # 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 - oldSelection = self._iActivePage + self._pages._iActivePage = page + self._pages.DoSetSelection(page) - 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_TABBEDCTRL_PAGE_CHANGED event - event.SetEventType(wxEVT_FLATNOTEBOOK_PAGE_CHANGED) - event.SetOldSelection(oldSelection) - self.GetParent().GetEventHandler().ProcessEvent(event) + 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 - def OnLeftUp(self, event): - """ Handles the wx.EVT_LEFT_UP events for PageContainerBase. """ + # Fire a closing event + event = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CLOSING, self.GetId()) + event.SetSelection(page) + event.SetEventObject(self) + self.GetEventHandler().ProcessEvent(event) - # forget the zone that was initially clicked - self._nLeftClickZone = FNB_NOWHERE + # The event handler allows it? + if not event.IsAllowed(): + return - where, tabIdx = self.HitTest(event.GetPosition()) + 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) - if where == FNB_LEFT_ARROW: - - if self._nFrom == 0: - return + # Remove it from the array as well + self._windows.pop(page) - # Make sure that the button was pressed before - if self._nLeftButtonStatus != FNB_BTN_PRESSED: - return + # Now we can destroy it in wxWidgets use Destroy instead of delete + pageRemoved.Destroy() - self._nLeftButtonStatus = FNB_BTN_HOVER + self.Thaw() - # We scroll left with bulks of 5 - scrollLeft = self.GetNumTabsCanScrollLeft() + self._pages.DoDeletePage(page) + self.Refresh() - self._nFrom -= scrollLeft - if self._nFrom < 0: - self._nFrom = 0 + # Fire a closed event + closedEvent = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CLOSED, self.GetId()) + closedEvent.SetSelection(page) + closedEvent.SetEventObject(self) + self.GetEventHandler().ProcessEvent(closedEvent) - 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 + def DeleteAllPages(self): + """ Deletes all the pages. """ - self._nRightButtonStatus = FNB_BTN_HOVER + if not self._windows: + return False - # 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 + self.Freeze() + + for page in self._windows: + page.Destroy() + + self._windows = [] + self.Thaw() - 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 + # Clear the container of the tabs as well + self._pages.DeleteAllPages() + return True - 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 + def GetCurrentPage(self): + """ Returns the currently selected notebook page or None. """ + + sel = self._pages.GetSelection() + if sel < 0: + return None - self.DeletePage(self._iActivePage) - - elif where == FNB_TAB_X: - - # Make sure that the button was pressed before - if self._nTabXButtonStatus != FNB_BTN_PRESSED: - return + return self._windows[sel] - self._nTabXButtonStatus = FNB_BTN_HOVER - self.DeletePage(self._iActivePage) - + def GetPage(self, page): + """ Returns the window at the given page position, or None. """ - def HitTest(self, pt): - """ - HitTest method for PageContainerBase. - Returns the flag (if any) and the hit page (if any). - """ + if page >= len(self._windows): + return None - fullrect = self.GetClientRect() - btnLeftPos = self.GetLeftButtonPos() - btnRightPos = self.GetRightButtonPos() - btnXPos = self.GetXPos() - style = self.GetParent().GetWindowStyleFlag() - - tabIdx = -1 + return self._windows[page] - if not self._pagesInfoVec: - return FNB_NOWHERE, -1 - - rect = wx.Rect(btnXPos, 6, 16, 16) - if rect.Contains(pt): - return (style & FNB_NO_X_BUTTON and [FNB_NOWHERE] or [FNB_X])[0], -1 - - rect = wx.Rect(btnRightPos, 6, 16, 16) - if rect.Contains(pt): - return (style & FNB_NO_NAV_BUTTONS and [FNB_NOWHERE] or [FNB_RIGHT_ARROW])[0], -1 - - rect = wx.Rect(btnLeftPos, 6, 16, 16) - if rect.Contains(pt): - return (style & FNB_NO_NAV_BUTTONS and [FNB_NOWHERE] or [FNB_LEFT_ARROW])[0], -1 - # Test whether a left click was made on a tab - 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 - - 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 + def GetPageIndex(self, win): + """ Returns the index at which the window is found. """ - 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: + return self._windows.index(win) + except: + return -1 - # 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)-1 - # Default - return FNB_NOWHERE, -1 + def GetSelection(self): + """ Returns the currently selected page, or -1 if none was selected. """ + + return self._pages.GetSelection() - def SetSelection(self, page): - """ Sets the selected page. """ + def AdvanceSelection(self, forward=True): + """ + Cycles through the tabs. + The call to this function generates the page changing events. + """ - book = self.GetParent() - book.SetSelection(page) - self.DoSetSelection(page) + self._pages.AdvanceSelection(forward) - def DoSetSelection(self, page): - """ Does the actual selection of a page. """ + def GetPageCount(self): + """ Returns the number of pages in the L{FlatNotebook} control. """ - # Make sure that the selection is visible - style = self.GetParent().GetWindowStyleFlag() - if style & FNB_NO_NAV_BUTTONS: - # Incase that we dont have navigation buttons, - # there is no point of checking if the tab is visible - # Just do the refresh - self.Refresh() - return - - if page < len(self._pagesInfoVec): - #! fix for tabfocus - da_page = self._pParent.GetPage(page) + return self._pages.GetPageCount() - # THIS IS GIVING TROUBLES!! - 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 OnNavigationKey(self, event): + """ Handles the wx.EVT_NAVIGATION_KEY event for L{FlatNotebook}. """ + if event.IsWindowChange(): + if len(self._windows) == 0: + return + # 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 DeletePage(self, page): - """ Delete the specified page from FlatNotebook. """ + def GetPageShapeAngle(self, page_index): + """ Returns the angle associated to a tab. """ - book = self.GetParent() - book.DeletePage(page) - book.Refresh() + 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 IsTabVisible(self, page): - """ Returns whether a tab is visible or not. """ + def SetPageShapeAngle(self, page_index, angle): + """ Sets the angle associated to a tab. """ - iLastVisiblePage = self.GetLastVisibleTab() - return page <= iLastVisiblePage and page >= self._nFrom + if page_index < 0 or page_index >= len(self._pages._pagesInfoVec): + return + if angle > 15: + return - def DoDeletePage(self, page): - """ Does the actual page deletion. """ + self._pages._pagesInfoVec[page_index].SetTabAngle(angle) - # 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 + def SetAllPagesShapeAngle(self, angle): + """ Sets the angle associated to all the tab. """ - # 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 self._pagesInfoVec: - self._iActivePage = 0 + if angle > 15: + return - # Refresh the tabs - if self._iActivePage >= 0: - - book._bForceSelection = True - book.SetSelection(self._iActivePage) - book._bForceSelection = False - - if not self._pagesInfoVec: - # Erase the page container drawings - dc = wx.ClientDC(self) - dc.Clear() + for ii in xrange(len(self._pages._pagesInfoVec)): + self._pages._pagesInfoVec[ii].SetTabAngle(angle) + self.Refresh() - def DeleteAllPages(self): - """ Deletes all the pages. """ - self._iActivePage = -1 - self._nFrom = 0 - self._pagesInfoVec = [] + def GetPageBestSize(self): + """ Return the page best size. """ - # Erase the page container drawings - dc = wx.ClientDC(self) - dc.Clear() + return self._pages.GetClientSize() - def DrawTabX(self, dc, rect, tabIdx): - """ Draws the 'X' in the selected tab (VC8 style excluded). """ + def SetPageText(self, page, text): + """ Sets the text for the given page. """ - if not self.HasFlag(FNB_X_ON_TAB) or not self.CanDrawXOnTab(): - return + bVal = self._pages.SetPageText(page, text) + self._pages.Refresh() - # We draw the 'x' on the active tab only - if tabIdx != self.GetSelection() or tabIdx < 0 or not self.CanFitToScreen(tabIdx): - return + return bVal - # Set the bitmap according to the button status - if self._nTabXButtonStatus == FNB_BTN_HOVER: - xBmp = wx.BitmapFromXPMData(x_button_hilite_xpm) - elif self._nTabXButtonStatus == 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)) + 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. + """ - # erase old button - dc.DrawBitmap(self._tabXBgBmp, rect.x, rect.y) + self._nPadding = padding.GetWidth() - # Draw the new bitmap - dc.DrawBitmap(xBmp, rect.x, rect.y, True) - # Update the vectpr - self._pagesInfoVec[tabIdx].SetXRect(rect) + def GetTabArea(self): + """ Returns the associated page. """ + return self._pages - def DrawLeftArrow(self, dc): - """ Draw the left navigation arrow. """ - style = self.GetParent().GetWindowStyleFlag() - if style & FNB_NO_NAV_BUTTONS: - return + def GetPadding(self): + """ Returns the amount of space around each page's icon and label, in pixels. """ + + return self._nPadding - # Make sure that there are pages in the container - if not self._pagesInfoVec: - return - # Set the bitmap according to the button status - if self._nLeftButtonStatus == FNB_BTN_HOVER: - arrowBmp = wx.BitmapFromXPMData(left_arrow_hilite_xpm) - elif self._nLeftButtonStatus == FNB_BTN_PRESSED: - arrowBmp = wx.BitmapFromXPMData(left_arrow_pressed_xpm) - else: - arrowBmp = wx.BitmapFromXPMData(left_arrow_xpm) + def SetWindowStyleFlag(self, style): + """ Sets the L{FlatNotebook} window style flags. """ + + wx.Panel.SetWindowStyleFlag(self, style) + renderer = self._pages._mgr.GetRenderer(self.GetWindowStyleFlag()) + renderer._tabHeight = None - if self._nFrom == 0: - # Handle disabled arrow - arrowBmp = wx.BitmapFromXPMData(left_arrow_disabled_xpm) + if self._pages: - arrowBmp.SetMask(wx.Mask(arrowBmp, MASK_COLOR)) + # For changing the tab position (i.e. placing them top/bottom) + # refreshing the tab container is not enough + self.SetSelection(self._pages._iActivePage) - # Erase old bitmap - posx = self.GetLeftButtonPos() - dc.DrawBitmap(self._leftBgBmp, posx, 6) - # Draw the new bitmap - dc.DrawBitmap(arrowBmp, posx, 6, True) + def RemovePage(self, page): + """ Deletes the specified page, without deleting the associated window. """ + if page >= len(self._windows): + return False - def DrawRightArrow(self, dc): - """ Draw the right navigation arrow. """ + # Fire a closing event + event = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CLOSING, self.GetId()) + event.SetSelection(page) + event.SetEventObject(self) + self.GetEventHandler().ProcessEvent(event) - style = self.GetParent().GetWindowStyleFlag() - if style & FNB_NO_NAV_BUTTONS: - return + # The event handler allows it? + if not event.IsAllowed(): + return False - # Make sure that there are pages in the container - if not self._pagesInfoVec: - return + self.Freeze() - # Set the bitmap according to the button status - if self._nRightButtonStatus == FNB_BTN_HOVER: - arrowBmp = wx.BitmapFromXPMData(right_arrow_hilite_xpm) - elif self._nRightButtonStatus == FNB_BTN_PRESSED: - arrowBmp = wx.BitmapFromXPMData(right_arrow_pressed_xpm) - else: - arrowBmp = wx.BitmapFromXPMData(right_arrow_xpm) + # Remove the requested page + pageRemoved = self._windows[page] - # 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): - arrowBmp = wx.BitmapFromXPMData(right_arrow_disabled_xpm) + # If the page is the current window, remove it from the sizer + # as well + if page == self._pages.GetSelection(): + self._mainSizer.Detach(pageRemoved) - arrowBmp.SetMask(wx.Mask(arrowBmp, MASK_COLOR)) + # Remove it from the array as well + self._windows.pop(page) + self.Thaw() - # erase old bitmap - posx = self.GetRightButtonPos() - dc.DrawBitmap(self._rightBgBmp, posx, 6) + self._pages.DoDeletePage(page) - # Draw the new bitmap - dc.DrawBitmap(arrowBmp, posx, 6, True) + return True - def DrawX(self, dc): - """ Draw the 'X' navigation button in the navigation area. """ + def SetRightClickMenu(self, menu): + """ Sets the popup menu associated to a right click on a tab. """ - # Check if this style is enabled - style = self.GetParent().GetWindowStyleFlag() - if style & FNB_NO_X_BUTTON: - return + self._pages._pRightClickMenu = menu - # Make sure that there are pages in the container - if not self._pagesInfoVec: - return - # Set the bitmap according to the button status - if self._nXButtonStatus == FNB_BTN_HOVER: - xbmp = wx.BitmapFromXPMData(x_button_hilite_xpm) - elif self._nXButtonStatus == FNB_BTN_PRESSED: - xbmp = wx.BitmapFromXPMData(x_button_pressed_xpm) - else: - xbmp = wx.BitmapFromXPMData(x_button_xpm) + def GetPageText(self, nPage): + """ Returns the tab caption. """ - xbmp.SetMask(wx.Mask(xbmp, MASK_COLOR)) - # erase old bitmap + return self._pages.GetPageText(nPage) - posx = self.GetXPos() - dc.DrawBitmap(self._xBgBmp, posx, 6) - # Draw the new bitmap - dc.DrawBitmap(xbmp, posx, 6, True) + 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 OnMouseMove(self, event): - """ Handles the wx.EVT_MOTION for PageContainerBase. """ - if self._pagesInfoVec and self.IsShown(): - - xButtonStatus = self._nXButtonStatus - xTabButtonStatus = self._nTabXButtonStatus - rightButtonStatus = self._nRightButtonStatus - leftButtonStatus = self._nLeftButtonStatus - style = self.GetParent().GetWindowStyleFlag() + def SetGradientColourFrom(self, fr): + """ Sets the starting colour for the gradient. """ - self._nXButtonStatus = FNB_BTN_NONE - self._nRightButtonStatus = FNB_BTN_NONE - self._nLeftButtonStatus = FNB_BTN_NONE - self._nTabXButtonStatus = FNB_BTN_NONE + self._pages._colorFrom = fr - 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_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): + def SetGradientColourTo(self, to): + """ Sets the ending colour for the gradient. """ - 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 + self._pages._colorTo = to - if (bRedrawX or bRedrawRight or bRedrawLeft or bRedrawTabX): - - dc = wx.ClientDC(self) - if bRedrawX: - - self.DrawX(dc) - - if bRedrawLeft: - - self.DrawLeftArrow(dc) - - if bRedrawRight: - - self.DrawRightArrow(dc) - - if bRedrawTabX: - - self.DrawTabX(dc, self._pagesInfoVec[tabIdx].GetXRect(), tabIdx) - - event.Skip() + def SetGradientColourBorder(self, border): + """ Sets the tab border colour. """ - def GetLastVisibleTab(self): - """ Returns the last visible tab. """ + self._pages._colorBorder = border - 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 GetGradientColourFrom(self): + """ Gets first gradient colour. """ - def GetNumTabsCanScrollLeft(self): - """ Returns the number of tabs than can be scrolled left. """ + return self._pages._colorFrom - # Reserved area for the buttons (<>x) - rect = self.GetClientRect() - clientWidth = rect.width - posx = self._pParent._nPadding - numTabs = 0 - pom = 0 - - dc = wx.ClientDC(self) - # In case we have error prevent crash - if self._nFrom < 0: - return 0 + def GetGradientColourTo(self): + """ Gets second gradient colour. """ - style = self.GetParent().GetWindowStyleFlag() - - for ii in xrange(self._nFrom, -1, -1): + return self._pages._colorTo - boldFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) - boldFont.SetWeight(wx.FONTWEIGHT_BOLD) - dc.SetFont(boldFont) - width, height = dc.GetTextExtent("Tp") + def GetGradientColourBorder(self): + """ Gets the tab border colour. """ - 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] + return self._pages._colorBorder - 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 = self._pParent._nPadding*2 + width - - if not (style & FNB_VC71): - # Default style - tabWidth += 2*shapePoints + def GetBorderColour(self): + """ Returns the border colour. """ - hasImage = self._ImageList != None and self._pagesInfoVec[ii].GetImageIndex() != -1 + return self._pages._colorBorder + - # 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 + self.GetButtonsAreaLength() >= clientWidth: - break + def GetActiveTabTextColour(self): + """ Get the active tab text colour. """ - numTabs = numTabs + 1 - posx += tabWidth - - return numTabs + return self._pages._activeTextColor - def IsDefaultTabs(self): - """ Returns whether a tab has a default style. """ + def SetPageImage(self, page, image): + """ + Sets the image index for the given page. Image is an index into the + image list which was set with SetImageList. + """ - style = self.GetParent().GetWindowStyleFlag() - res = (style & FNB_VC71) or (style & FNB_FANCY_TABS) - return not res + self._pages.SetPageImage(page, image) - def AdvanceSelection(self, bForward=True): + def GetPageImage(self, nPage): """ - Cycles through the tabs. - The call to this function generates the page changing events. + Returns the image index for the given page. Image is an index into the + image list which was set with SetImageList. """ - nSel = self.GetSelection() + return self._pages.GetPageImage(nPage) - if nSel < 0: + + 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 - nMax = self.GetPageCount() - 1 - if bForward: - self.SetSelection((nSel == nMax and [0] or [nSel + 1])[0]) - else: - self.SetSelection((nSel == 0 and [nMax] or [nSel - 1])[0]) + self._windows[page].Enable(enabled) + self._pages.Enable(page, enabled) - def OnMouseLeave(self, event): - """ Handles the wx.EVT_LEAVE_WINDOW event for PageContainerBase. """ + def GetNonActiveTabTextColour(self): + """ Returns the non active tabs text colour. """ - self._nLeftButtonStatus = FNB_BTN_NONE - self._nXButtonStatus = FNB_BTN_NONE - self._nRightButtonStatus = FNB_BTN_NONE - self._nTabXButtonStatus = FNB_BTN_NONE + return self._pages._nonActiveTextColor - dc = wx.ClientDC(self) - self.DrawX(dc) - self.DrawLeftArrow(dc) - self.DrawRightArrow(dc) - selection = self.GetSelection() - - if selection != -1: - self.DrawTabX(dc, self._pagesInfoVec[selection].GetXRect(), selection) - - event.Skip() + def SetNonActiveTabTextColour(self, color): + """ Sets the non active tabs text colour. """ + self._pages._nonActiveTextColor = color - def OnMouseEnterWindow(self, event): - """ Handles the wx.EVT_ENTER_WINDOW event for PageContainerBase. """ - self._nLeftButtonStatus = FNB_BTN_NONE - self._nXButtonStatus = FNB_BTN_NONE - self._nRightButtonStatus = FNB_BTN_NONE - self._nLeftClickZone = FNB_BTN_NONE + def SetTabAreaColour(self, color): + """ Sets the area behind the tabs colour. """ - event.Skip() + self._pages._tabAreaColor = color + + + def GetTabAreaColour(self): + """ Returns the area behind the tabs colour. """ + return self._pages._tabAreaColor - 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 SetActiveTabColour(self, color): + """ Sets the active tab colour. """ - def FillGradientColor(self, dc, rect): - """ Gradient fill from colour 1 to colour 2 with top to bottom. """ + self._pages._activeTabColor = color - if rect.height < 1 or rect.width < 1: - return - size = rect.height + def GetActiveTabColour(self): + """ Returns the active tab colour. """ - # calculate gradient coefficients - style = self.GetParent().GetWindowStyleFlag() - col2 = ((style & FNB_BOTTOM) and [self._colorTo] or [self._colorFrom])[0] - col1 = ((style & FNB_BOTTOM) and [self._colorFrom] or [self._colorTo])[0] + return self._pages._activeTabColor - 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) - for y in xrange(rect.y, rect.y + size): - currCol = wx.Colour(col1.Red() + rf, col1.Green() + gf, col1.Blue() + bf) - dc.SetBrush(wx.Brush(currCol)) - dc.SetPen(wx.Pen(currCol)) - dc.DrawLine(rect.x, y, rect.x + rect.width, y) - rf += rstep - gf += gstep - bf += bstep - +# ---------------------------------------------------------------------------- # +# Class PageContainer +# Acts as a container for the pages you add to FlatNotebook +# ---------------------------------------------------------------------------- # - def SetPageImageIndex(self, page, imgindex): - """ Sets the image index associated to a page. """ +class PageContainer(wx.Panel): + """ + This class acts as a container for the pages you add to L{FlatNotebook}. + """ - if page < len(self._pagesInfoVec): + def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, + size=wx.DefaultSize, style=0): + """ Default class constructor. """ - self._pagesInfoVec[page].SetImageIndex(imgindex) - self.Refresh() - + self._ImageList = None + self._iActivePage = -1 + self._pDropTarget = None + self._nLeftClickZone = FNB_NOWHERE + self._iPreviousActivePage = -1 - def GetPageImageIndex(self, page): - """ Returns the image index associated to a page. """ + 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 - if page < len(self._pagesInfoVec): - - return self._pagesInfoVec[page].GetImageIndex() - - return -1 + 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) - def OnDropTarget(self, x, y, nTabPage, wnd_oldContainer): - """ Handles the drop action from a DND operation. """ + self._nFrom = 0 + self._isdragging = False - # Disable drag'n'drop for disabled tab - if not wnd_oldContainer._pagesInfoVec[nTabPage].GetEnabled(): - return wx.DragCancel + # Set default page height, this is done according to the system font + memDc = wx.MemoryDC() + memDc.SelectObject(wx.EmptyBitmap(1,1)) + + if "__WXGTK__" in wx.PlatformInfo: + boldFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) + boldFont.SetWeight(wx.BOLD) + memDc.SetFont(boldFont) - self._isdragging = True - oldContainer = wnd_oldContainer - nIndex = -1 + height = memDc.GetCharHeight() + tabHeight = height + FNB_HEIGHT_SPACER # We use 10 pixels as padding - where, nIndex = self.HitTest(wx.Point(x, y)) + wx.Panel.__init__(self, parent, id, pos, wx.Size(size.x, tabHeight), + style|wx.NO_BORDER|wx.NO_FULL_REPAINT_ON_RESIZE) - oldNotebook = oldContainer.GetParent() - newNotebook = self.GetParent() + self._pDropTarget = FNBDropTarget(self) + self.SetDropTarget(self._pDropTarget) + self._mgr = FNBRendererMgr() - if oldNotebook == newNotebook: - - if nTabPage >= 0: - - if where == FNB_TAB: - self.MoveTabPage(nTabPage, nIndex) - - else: - - if wx.Platform in ["__WXMSW__", "__WXGTK__"]: - if nTabPage >= 0: - - window = oldNotebook.GetPage(nTabPage) + 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) - if window: - where, nIndex = newNotebook._pages.HitTest(wx.Point(x, y)) - caption = oldContainer.GetPageText(nTabPage) - imageindex = oldContainer.GetPageImageIndex(nTabPage) - oldNotebook.RemovePage(nTabPage) - window.Reparent(newNotebook) - newNotebook.InsertPage(nIndex, window, caption, True, imageindex) + def OnEraseBackground(self, event): + """ Handles the wx.EVT_ERASE_BACKGROUND event for L{PageContainer} (does nothing).""" - self._isdragging = False - - return wx.DragMove + pass + + def OnPaint(self, event): + """ Handles the wx.EVT_PAINT event for L{PageContainer}.""" - def MoveTabPage(self, nMove, nMoveTo): - """ Moves a tab inside the same FlatNotebook. """ + dc = wx.BufferedPaintDC(self) + renderer = self._mgr.GetRenderer(self.GetParent().GetWindowStyleFlag()) + renderer.DrawTabs(self, dc) - if nMove == nMoveTo: - return - elif nMoveTo < len(self._pParent._windows): - nMoveTo = nMoveTo + 1 + def AddPage(self, caption, selected=True, imgindex=-1): + """ + Add a page to the L{FlatNotebook}. - self._pParent.Freeze() + @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. - # 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() + Return value: + True if successful, False otherwise. + """ - pWindow = self._pParent._windows[nMove] - self._pParent._windows.pop(nMove) - self._pParent._windows.insert(nMoveTo-1, pWindow) + if selected: - pgInfo = self._pagesInfoVec[nMove] + 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() - 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() + def InsertPage(self, indx, text, selected=True, imgindex=-1): + """ + Inserts a new page at the specified position. - if style & FNB_BOTTOM: - - pSizer.Insert(0, pWindow, 1, wx.EXPAND) + @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. - else: + Return value: + True if successful, False otherwise. + """ + + if selected: + + self._iPreviousActivePage = self._iActivePage + self._iActivePage = len(self._pagesInfoVec) - # We leave a space of 1 pixel around the window - pSizer.Add(pWindow, 1, wx.EXPAND) + self._pagesInfoVec.insert(indx, PageInfo(text, imgindex)) - pWindow.Show() - - pSizer.Layout() - self._iActivePage = nMoveTo - 1 - self.DoSetSelection(self._iActivePage) self.Refresh() - self._pParent.Thaw() + return True - def CanFitToScreen(self, page): - """ Returns whether all the tabs can fit in the available space. """ + def OnSize(self, event): + """ Handles the wx.EVT_SIZE events for L{PageContainer}. """ - # 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 + # When resizing the control, try to fit to screen as many tabs as we can + style = self.GetParent().GetWindowStyleFlag() + renderer = self._mgr.GetRenderer(style) + + fr = 0 + page = self.GetSelection() + + for fr in xrange(self._nFrom): + vTabInfo = renderer.NumberTabsCanFit(self, fr) + if page - fr >= len(vTabInfo): + continue + break - # Calculate the tab width including borders and image if any - dc = wx.ClientDC(self) + self._nFrom = fr - style = self.GetParent().GetWindowStyleFlag() + self.Refresh() # Call on paint + event.Skip() - width, height = dc.GetTextExtent("Tp") - width, pom = dc.GetTextExtent(self.GetPageText(page)) - tabHeight = height + FNB_HEIGHT_SPACER # We use 6 pixels as padding + def OnMiddleDown(self, event): + """ Handles the wx.EVT_MIDDLE_DOWN events for L{PageContainer}. """ - 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 - 2] or [tabHeight])[0] + # Test if this style is enabled + style = self.GetParent().GetWindowStyleFlag() + + if not style & FNB_MOUSE_MIDDLE_CLOSES_TABS: + return - tabWidth = self._pParent._nPadding * 2 + width + where, tabIdx = self.HitTest(event.GetPosition()) - if not style & FNB_VC71: - shapePoints = int(tabHeight*math.tan(float(self._pagesInfoVec[page].GetTabAngle())/180.0*math.pi)) - else: - shapePoints = 0 + if where == FNB_TAB: + self.DeletePage(tabIdx) + + event.Skip() - if not style & FNB_VC71: - # Default style - tabWidth += 2*shapePoints - hasImage = self._ImageList != None + def OnRightDown(self, event): + """ Handles the wx.EVT_RIGHT_DOWN events for L{PageContainer}. """ - if hasImage: - hasImage &= self._pagesInfoVec[page].GetImageIndex() != -1 + where, tabIdx = self.HitTest(event.GetPosition()) - # For VC71 style, we only add the icon size (16 pixels) - if hasImage and (style & FNB_VC71 or style & FNB_FANCY_TABS): - tabWidth += 16 - else: - # Default style - tabWidth += 16 + shapePoints/2 + if where in [FNB_TAB, FNB_TAB_X]: - # Check if we can draw more - posx = self._pParent._nPadding + if self._pagesInfoVec[tabIdx].GetEnabled(): + # Fire events and eventually (if allowed) change selection + self.FireEvent(tabIdx) - if self._nFrom >= 0: - for ii in xrange(self._nFrom, len(self._pagesInfoVec)): - if self._pagesInfoVec[ii].GetPosition() == wx.Point(-1, -1): - break - posx += self._pagesInfoVec[ii].GetSize().x + # 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) + + if self._pRightClickMenu: + self.PopupMenu(self._pRightClickMenu) - rect = self.GetClientRect() - clientWidth = rect.width + event.Skip() - if posx + tabWidth + self.GetButtonsAreaLength() >= clientWidth: - return False - return True + 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 - def GetNumOfVisibleTabs(self): - """ Returns the number of visible tabs. """ + self._nLeftClickZone, tabIdx = self.HitTest(event.GetPosition()) - count = 0 - for ii in xrange(self._nFrom, len(self._pagesInfoVec)): - if self._pagesInfoVec[ii].GetPosition() == wx.Point(-1, -1): - break - count = count + 1 + 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() - return count + 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(): + self.FireEvent(tabIdx) - def GetEnabled(self, page): - """ Returns whether a tab is enabled or not. """ + 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 - if page >= len(self._pagesInfoVec): - return True # Seems strange, but this is the default + where, tabIdx = self.HitTest(event.GetPosition()) - return self._pagesInfoVec[page].GetEnabled() + 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 - def Enable(self, page, enabled=True): - """ Enables or disables a tab. """ + self._nLeftButtonStatus = FNB_BTN_HOVER - if page >= len(self._pagesInfoVec): - return - - self._pagesInfoVec[page].Enable(enabled) - + # We scroll left with bulks of 5 + scrollLeft = self.GetNumTabsCanScrollLeft() - def GetLeftButtonPos(self): - """ Returns the left button position in the navigation area. """ + self._nFrom -= scrollLeft + if self._nFrom < 0: + self._nFrom = 0 - style = self.GetParent().GetWindowStyleFlag() - rect = self.GetClientRect() - clientWidth = rect.width - - if style & FNB_NO_X_BUTTON: - return clientWidth - 38 - else: - return clientWidth - 54 + 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 - def GetRightButtonPos(self): - """ Returns the right button position in the navigation area. """ + self._nRightButtonStatus = FNB_BTN_HOVER - style = self.GetParent().GetWindowStyleFlag() - rect = self.GetClientRect() - clientWidth = rect.width - - if style & FNB_NO_X_BUTTON: - return clientWidth - 22 - else: - return clientWidth - 38 + # 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 - def GetXPos(self): - """ Returns the 'X' button position in the navigation area. """ + 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 - style = self.GetParent().GetWindowStyleFlag() - rect = self.GetClientRect() - clientWidth = rect.width - - if style & FNB_NO_X_BUTTON: - return clientWidth - else: - return clientWidth - 22 + 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 - def GetButtonsAreaLength(self): - """ Returns the navigation area width. """ + self._nTabXButtonStatus = FNB_BTN_HOVER - style = self.GetParent().GetWindowStyleFlag() - - if style & FNB_NO_NAV_BUTTONS and style & FNB_NO_X_BUTTON: - return 0 - elif style & FNB_NO_NAV_BUTTONS and not style & FNB_NO_X_BUTTON: - return 53 - 16 - elif not style & FNB_NO_NAV_BUTTONS and style & FNB_NO_X_BUTTON: - return 53 - 16 - else: - # All buttons - return 53 + self.DeletePage(self._iActivePage) + elif where == FNB_DROP_DOWN_ARROW: - def GetSingleLineBorderColor(self): + # Make sure that the button was pressed before + if self._nArrowDownButtonStatus != FNB_BTN_PRESSED: + return - if self.HasFlag(FNB_FANCY_TABS): - return self._colorFrom - - return wx.WHITE + self._nArrowDownButtonStatus = FNB_BTN_NONE + # Refresh the button status + renderer = self._mgr.GetRenderer(self.GetParent().GetWindowStyleFlag()) + dc = wx.ClientDC(self) + renderer.DrawDropDownArrow(self, dc) - def DrawTabsLine(self, dc): - """ Draws a line over the tabs. """ + self.PopupTabsMenu() - clntRect = self.GetClientRect() - clientRect3 = wx.Rect(0, 0, clntRect.width, clntRect.height) - if self.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(self.GetSingleLineBorderColor())) - dc.DrawRectangleRect(clientRect2) - dc.DrawRectangleRect(clientRect3) + def HitTest(self, pt): + """ + HitTest method for L{PageContainer}. + Returns the flag (if any) and the hit page (if any). + """ - dc.SetPen(wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW))) - dc.DrawRectangleRect(clientRect) + style = self.GetParent().GetWindowStyleFlag() + render = self._mgr.GetRenderer(style) - if not self.HasFlag(FNB_TABS_BORDER_SIMPLE): + fullrect = self.GetClientRect() + btnLeftPos = render.GetLeftButtonPos(self) + btnRightPos = render.GetRightButtonPos(self) + btnXPos = render.GetXPos(self) - dc.SetPen(wx.Pen((self.HasFlag(FNB_VC71) and [wx.Colour(247, 243, 233)] or [self._tabAreaColor])[0])) - dc.DrawLine(0, 0, 0, clientRect.height+1) - - if self.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) + tabIdx = -1 + if len(self._pagesInfoVec) == 0: + return FNB_NOWHERE, tabIdx - def HasFlag(self, flag): - """ Returns whether a flag is present in the FlatNotebook style. """ + 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 - style = self.GetParent().GetWindowStyleFlag() - res = (style & flag and [True] or [False])[0] - return res + 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 - def ClearFlag(self, flag): - """ Deletes a flag from the FlatNotebook style. """ + 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 - style = self.GetParent().GetWindowStyleFlag() - style &= ~flag - self.SetWindowStyleFlag(style) + # 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] - def TabHasImage(self, tabIdx): - """ Returns whether a tab has an associated image index or not. """ + if pgInfo.GetPosition() == wx.Point(-1, -1): + continue - if self._ImageList: - return self._pagesInfoVec[tabIdx].GetImageIndex() != -1 - - return False + 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 - def OnLeftDClick(self, event): - """ Handles the wx.EVT_LEFT_DCLICK event for PageContainerBase. """ + tabIdx = cur + bFoundMatch = True + + else: - if self.HasFlag(FNB_DCLICK_CLOSES_TABS): - - where, tabIdx = self.HitTest(event.GetPosition()) - - if where == FNB_TAB: - self.DeletePage(tabIdx) - - else: - - event.Skip() - + 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 - def SetImageList(self, imglist): - """ Sets the image list for the page control. """ + if bFoundMatch: + return FNB_TAB, tabIdx - self._ImageList = imglist + 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) - def GetImageList(self): - """ Returns the image list for the page control. """ + if tabRect.Contains(pt): + return FNB_TAB, len(self._pagesInfoVec) - return self._ImageList + # Default + return FNB_NOWHERE, -1 - def GetSelection(self): - """ Returns the current selected page. """ + def SetSelection(self, page): + """ Sets the selected page. """ - return self._iActivePage + book = self.GetParent() + book.SetSelection(page) + self.DoSetSelection(page) - def GetPageCount(self): - """ Returns the number of tabs in the FlatNotebook control. """ + def DoSetSelection(self, page): + """ Does the actual selection of a page. """ - return len(self._pagesInfoVec) + 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): + # Try to remove one tab from start and try again + + if not self.CanFitToScreen(page): + if self._nFrom > page: + self._nFrom = page + else: + while self._nFrom < page: + self._nFrom += 1 + if self.CanFitToScreen(page): + break + + self.Refresh() - def GetPageText(self, page): - """ Returns the tab caption of the page. """ - return self._pagesInfoVec[page].GetCaption() + def DeletePage(self, page): + """ Delete the specified page from L{FlatNotebook}. """ + book = self.GetParent() + book.DeletePage(page) + book.Refresh() - def SetPageText(self, page, text): - """ Sets the tab caption of the page. """ - self._pagesInfoVec[page].SetCaption(text) - return True + def IsTabVisible(self, page): + """ Returns whether a tab is visible or not. """ + iLastVisiblePage = self.GetLastVisibleTab() + return page <= iLastVisiblePage and page >= self._nFrom - def CanDrawXOnTab(self): - """ Returns whether an 'X' can be drawn on a tab (all styles except VC8. """ - - return True + 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 -# ---------------------------------------------------------------------------- # -# Class FlatNotebook -# Simple super class based on PageContainerBase -# ---------------------------------------------------------------------------- # + # 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 -class FlatNotebook(FlatNotebookBase): + # Refresh the tabs + if self._iActivePage >= 0: + + book._bForceSelection = True - def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, - style=0, name="FlatNotebook"): - """ - Default class constructor. + # 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()) - It is better to use directly the StyledNotebook class (see below) and then - assigning the style you wish instead of calling FlatNotebook. - """ + book.SetSelection(self._iActivePage) + book._bForceSelection = False - style |= wx.TAB_TRAVERSAL + # 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() - FlatNotebookBase.__init__(self, parent, id, pos, size, style, name) - self._pages = self.CreatePageContainer() - - def CreatePageContainer(self): - """ Creates the page container. """ + def DeleteAllPages(self): + """ Deletes all the pages. """ - return FlatNotebookBase.CreatePageContainer(self) + self._iActivePage = -1 + self._iPreviousActivePage = -1 + self._nFrom = 0 + self._pagesInfoVec = [] + # Erase the page container drawings + dc = wx.ClientDC(self) + dc.Clear() -#-------------------------------------------------------------------- -# StyledNotebook - a notebook with look n feel of Visual Studio 2005 -#-------------------------------------------------------------------- -class StyledNotebook(FlatNotebookBase): + def OnMouseMove(self, event): + """ Handles the wx.EVT_MOTION for L{PageContainer}. """ - def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, - style=0, name="StyledNotebook"): - """ Default class constructor. + if self._pagesInfoVec and self.IsShown(): - It is better to use directly the StyledNotebook class and then - assigning the style you wish instead of calling FlatNotebook. - """ + xButtonStatus = self._nXButtonStatus + xTabButtonStatus = self._nTabXButtonStatus + rightButtonStatus = self._nRightButtonStatus + leftButtonStatus = self._nLeftButtonStatus + dropDownButtonStatus = self._nArrowDownButtonStatus + + style = self.GetParent().GetWindowStyleFlag() - style |= wx.TAB_TRAVERSAL - - FlatNotebookBase.__init__(self, parent, id, pos, size, style, name) - - # Custom initialization of the tab area - if style & FNB_VC8: - # Initialise the default style colors - self.SetNonActiveTabTextColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNTEXT)) + 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 - def CreatePageContainer(self): - """ Creates the page container. """ + elif where == FNB_DROP_DOWN_ARROW: + if event.LeftIsDown(): - return StyledTabsContainer(self, wx.ID_ANY) + self._nArrowDownButtonStatus = (self._nLeftClickZone==FNB_DROP_DOWN_ARROW and [FNB_BTN_PRESSED] or [FNB_BTN_NONE])[0] + else: -# ---------------------------------------------------------------------------- # -# Class StyledTabsContainer -# Acts as a container for the pages you add to FlatNotebook -# A more generic and more powerful implementation of PageContainerBase, can -# handle also VC8 tabs style -# ---------------------------------------------------------------------------- # + self._nArrowDownButtonStatus = FNB_BTN_HOVER -class StyledTabsContainer(PageContainerBase): + 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: - def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, - style=0): - """ Default class constructor. """ + 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.Dragging() and not (style & FNB_NODRAG): - self._factor = 1 + self._isdragging = True + draginfo = FNBDragInfo(self, tabIdx) + drginfo = cPickle.dumps(draginfo) + dataobject = wx.CustomDataObject(wx.CustomDataFormat("FlatNotebook")) + dataobject.SetData(drginfo) + dragSource = FNBDropSource(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 - self._colorTo = LightColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE), 0) - self._colorFrom = LightColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE), 60) + render = self._mgr.GetRenderer(style) - PageContainerBase.__init__(self, parent, id, pos, size, style) + if (bRedrawX or bRedrawRight or bRedrawLeft or bRedrawTabX or bRedrawDropArrow): - self.Bind(wx.EVT_PAINT, self.OnPaint) + 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) - - def NumberTabsCanFit(self, dc): - """ Returns the number of tabs that can fit inside the available space. """ + if bRedrawDropArrow: - rect = self.GetClientRect() - clientWidth = rect.width + render.DrawDropDownArrow(self, dc) - # Empty results - vTabInfo = [] + event.Skip() - # We take the maxmimum font size, this is - # achieved by setting the font to be bold - font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) - font.SetWeight(wx.FONTWEIGHT_BOLD) - dc.SetFont(font) - width, height = dc.GetTextExtent("Tp") + def GetLastVisibleTab(self): + """ Returns the last visible tab. """ - tabHeight = height + FNB_HEIGHT_SPACER # We use 8 pixels - # The drawing starts from posx - posx = self._pParent.GetPadding() + if self._nFrom < 0: + return -1 + + ii = 0 - for i in xrange(self._nFrom, len(self._pagesInfoVec)): + for ii in xrange(self._nFrom, len(self._pagesInfoVec)): - width, pom = dc.GetTextExtent(self.GetPageText(i)) - - # Set a minimum size to a tab - if width < 20: - width = 20 - - tabWidth = self._pParent.GetPadding() * 2 + width - - # Add the image width if it exist - if self.TabHasImage(i): - tabWidth += 16 + self._pParent.GetPadding() - - vc8glitch = tabHeight + FNB_HEIGHT_SPACER - if posx + tabWidth + vc8glitch + self.GetButtonsAreaLength() >= clientWidth: + if self._pagesInfoVec[ii].GetPosition() == wx.Point(-1, -1): 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 - + return ii-1 + def GetNumTabsCanScrollLeft(self): """ Returns the number of tabs than can be scrolled left. """ @@ -3344,725 +4166,481 @@ class StyledTabsContainer(PageContainerBase): # Reserved area for the buttons (<>x) rect = self.GetClientRect() clientWidth = rect.width - posx = self._pParent.GetPadding() + posx = self._pParent._nPadding numTabs = 0 pom = 0 - - dc = wx.ClientDC(self) - - # Incase we have error prevent crash + + # 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 i in xrange(self._nFrom, -1, -1): - - boldFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) + 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) - width, height = dc.GetTextExtent("Tp") + height = dc.GetCharHeight() tabHeight = height + FNB_HEIGHT_SPACER # We use 6 pixels as padding - if style & FNB_VC71: - tabHeight = (self.HasFlag(FNB_BOTTOM) and [tabHeight - 4] or [tabHeight])[0] + tabHeight = (style & FNB_BOTTOM and [tabHeight - 4] or [tabHeight])[0] elif style & FNB_FANCY_TABS: - tabHeight = (self.HasFlag(FNB_BOTTOM) and [tabHeight - 3] or [tabHeight])[0] + tabHeight = (style & FNB_BOTTOM and [tabHeight - 3] or [tabHeight])[0] - width, pom = dc.GetTextExtent(self.GetPageText(i)) - + width, pom = dc.GetTextExtent(self.GetPageText(ii)) if style != FNB_VC71: - shapePoints = int(tabHeight*math.tan(float(self._pagesInfoVec[i].GetTabAngle())/180.0*math.pi)) + shapePoints = int(tabHeight*math.tan(float(self._pagesInfoVec[ii].GetTabAngle())/180.0*math.pi)) else: shapePoints = 0 - tabWidth = self._pParent.GetPadding() * 2 + width - if not style & FNB_VC71: + 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 self.TabHasImage(i): + if hasImage: if not self.IsDefaultTabs(): - tabWidth += 16 + self._pParent.GetPadding() + tabWidth += 16 + self._pParent._nPadding else: # Default style - tabWidth += 16 + self._pParent.GetPadding() + shapePoints/2 - - vc8glitch = (style & FNB_VC8 and [tabHeight + FNB_HEIGHT_SPACER] or [0])[0] + tabWidth += 16 + self._pParent._nPadding + shapePoints/2 - if posx + tabWidth + vc8glitch + self.GetButtonsAreaLength() >= clientWidth: + if posx + tabWidth + render.GetButtonsAreaLength(self) >= clientWidth: break numTabs = numTabs + 1 - posx += tabWidth - - return numTabs - - - def CanDrawXOnTab(self): - """ Returns whether an 'X' button can be drawn on a tab (not VC8 style). """ - - style = self.GetParent().GetWindowStyleFlag() - isVC8 = (style & FNB_VC8 and [True] or [False])[0] - return not isVC8 - - - def IsDefaultTabs(self): - """ Returns whether a tab has a default style or not. """ - - style = self.GetParent().GetWindowStyleFlag() - res = (style & FNB_VC71) or (style & FNB_FANCY_TABS) or (style & FNB_VC8) - return not res - - - def HitTest(self, pt): - """ HitTest specific method for VC8 style. """ - - fullrect = self.GetClientRect() - btnLeftPos = self.GetLeftButtonPos() - btnRightPos = self.GetRightButtonPos() - btnXPos = self.GetXPos() - style = self.GetParent().GetWindowStyleFlag() - tabIdx = -1 - - if not self._pagesInfoVec: - return FNB_NOWHERE, -1 - - rect = wx.Rect(btnXPos, 8, 16, 16) - - if rect.Contains(pt): - return (style & FNB_NO_X_BUTTON and [FNB_NOWHERE] or [FNB_X])[0], -1 - - rect = wx.Rect(btnRightPos, 8, 16, 16) - - if rect.Contains(pt): - return (style & FNB_NO_NAV_BUTTONS and [FNB_NOWHERE] or [FNB_RIGHT_ARROW])[0], -1 - - 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], -1 - - # 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_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: - - 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 - - tabRect = wx.Rect(pgInfo.GetPosition().x, pgInfo.GetPosition().y, pgInfo.GetSize().x, pgInfo.GetSize().y) - - if tabRect.Contains(pt): - return FNB_TAB, cur - - if bFoundMatch: - return FNB_TAB, tabIdx + posx += tabWidth + + return numTabs - 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) + def IsDefaultTabs(self): + """ Returns whether a tab has a default style. """ - if tabRect.Contains(pt): - return FNB_TAB, len(self._pagesInfoVec) - - # Default - return FNB_NOWHERE, -1 + style = self.GetParent().GetWindowStyleFlag() + res = (style & FNB_VC71) or (style & FNB_FANCY_TABS) or (style & FNB_VC8) + return not res - def OnPaint(self, event): + def AdvanceSelection(self, bForward=True): """ - Handles the wx.EVT_PAINT event for StyledTabsContainer. - Switches to PageContainerBase.OnPaint() method if the style is not VC8. + Cycles through the tabs. + The call to this function generates the page changing events. """ - if not self.HasFlag(FNB_VC8): - - PageContainerBase.OnPaint(self, event) - return + nSel = self.GetSelection() - # Visual studio 8 style - dc = wx.BufferedPaintDC(self) + if nSel < 0: + return - if "__WXMAC__" in wx.PlatformInfo: - # Works well on MSW & GTK, however this lines should be skipped on MAC - if not self._pagesInfoVec or self._nFrom >= len(self._pagesInfoVec): - self.Hide() - event.Skip() - return + nMax = self.GetPageCount() - 1 - # Set the font for measuring the tab height - normalFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) - boldFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) - boldFont.SetWeight(wx.FONTWEIGHT_BOLD) - - if "__WXGTK__" in wx.PlatformInfo: - dc.SetFont(boldFont) + if bForward: + newSelection = (nSel == nMax and [0] or [nSel + 1])[0] + else: + newSelection = (nSel == 0 and [nMax] or [nSel - 1])[0] - width, height = dc.GetTextExtent("Tp") + if not self._pagesInfoVec[newSelection].GetEnabled(): + return - tabHeight = height + FNB_HEIGHT_SPACER # We use 8 pixels as padding + self.FireEvent(newSelection) - # Calculate the number of rows required for drawing the tabs - rect = self.GetClientRect() - # Set the maximum client size - self.SetSizeHints(self.GetButtonsAreaLength(), tabHeight) - borderPen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)) + def OnMouseLeave(self, event): + """ Handles the wx.EVT_LEAVE_WINDOW event for L{PageContainer}. """ - # Create brushes - backBrush = wx.Brush(self._tabAreaColor) - noselBrush = wx.Brush(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNFACE)) - selBrush = wx.Brush(self._activeTabColor) - size = self.GetSize() + 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 - # Background - dc.SetTextBackground(self.GetBackgroundColour()) - dc.SetTextForeground(self._activeTextColor) + style = self.GetParent().GetWindowStyleFlag() + render = self._mgr.GetRenderer(style) - # If border style is set, set the pen to be border pen - if self.HasFlag(FNB_TABS_BORDER_SIMPLE): - dc.SetPen(borderPen) - else: - dc.SetPen(wx.TRANSPARENT_PEN) + dc = wx.ClientDC(self) - lightFactor = (self.HasFlag(FNB_BACKGROUND_GRADIENT) and [70] or [0])[0] - # For VC8 style, we color the tab area in gradient coloring - PaintStraightGradientBox(dc, self.GetClientRect(), self._tabAreaColor, LightColour(self._tabAreaColor, lightFactor)) + render.DrawX(self, dc) + render.DrawLeftArrow(self, dc) + render.DrawRightArrow(self, dc) - dc.SetBrush(wx.TRANSPARENT_BRUSH) - dc.DrawRectangle(0, 0, size.x, size.y) + selection = self.GetSelection() - # Take 3 bitmaps for the background for the buttons + if selection == -1: + event.Skip() + return - mem_dc = wx.MemoryDC() + 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() - #--------------------------------------- - # X button - #--------------------------------------- - rect = wx.Rect(self.GetXPos(), 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(), 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) + def OnMouseEnterWindow(self, event): + """ Handles the wx.EVT_ENTER_WINDOW event for L{PageContainer}. """ - #--------------------------------------- - # Left button - #--------------------------------------- - rect = wx.Rect(self.GetLeftButtonPos(), 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(dc) + 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 - # Restore the pen - dc.SetPen(borderPen) + event.Skip() - # Draw labels - dc.SetFont(boldFont) - activeTabPosx = 0 - # Update all the tabs from 0 to 'self._nFrom' to be non visible - for i in xrange(self._nFrom): - - self._pagesInfoVec[i].SetPosition(wx.Point(-1, -1)) - self._pagesInfoVec[i].GetRegion().Clear() + def ShowTabTooltip(self, tabIdx): + """ Shows a tab tooltip. """ - # Draw the visible tabs, in VC8 style, we draw them from right to left - vTabsInfo = self.NumberTabsCanFit(dc) + pWindow = self._pParent.GetPage(tabIdx) - for cur in xrange(len(vTabsInfo) - 1, -1, -1): + if pWindow: + pToolTip = pWindow.GetToolTip() + if pToolTip and pToolTip.GetWindow() == pWindow: + self.SetToolTipString(pToolTip.GetTip()) - # 'i' points to the index of the currently drawn tab - # in self._pagesInfoVec vector - i = self._nFrom + cur - dc.SetPen(borderPen) - dc.SetBrush((i==self.GetSelection() and [selBrush] or [noselBrush])[0]) - - # 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(self.GetPageText(i)) - - # Now set the font to the correct font - dc.SetFont((i==self.GetSelection() and [boldFont] or [normalFont])[0]) - # Set a minimum size to a tab - if width < 20: - width = 20 - - # Add the padding to the tab width - # Tab width: - # +-----------------------------------------------------------+ - # | PADDING | IMG | IMG_PADDING | TEXT | PADDING | x |PADDING | - # +-----------------------------------------------------------+ + def SetPageImage(self, page, imgindex): + """ Sets the image index associated to a page. """ - tabWidth = self._pParent.GetPadding() * 2 + width - imageYCoord = (self.HasFlag(FNB_BOTTOM) and [6] or [8])[0] + if page < len(self._pagesInfoVec): + + self._pagesInfoVec[page].SetImageIndex(imgindex) + self.Refresh() - if self.TabHasImage(i): - tabWidth += 16 + self._pParent.GetPadding() - posx = vTabsInfo[cur].x + def GetPageImage(self, page): + """ Returns the image index associated to a page. """ - # 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 - self._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 - self._pagesInfoVec[i].GetXRect().SetSize(wx.Size(-1, -1)) + if page < len(self._pagesInfoVec): + + return self._pagesInfoVec[page].GetImageIndex() + + return -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 - if i == self.GetSelection(): - - activeTabPosx = posx - - else: - - self.DrawVC8Tab(dc, posx, i, tabWidth, tabHeight) - # Text drawing offset from the left border of the - # rectangle - # The width of the images are 16 pixels - vc8ShapeLen = tabHeight - - if self.TabHasImage(i): - textOffset = self._pParent.GetPadding() * 2 + 16 + vc8ShapeLen - else: - textOffset = self._pParent.GetPadding() + vc8ShapeLen + def OnDropTarget(self, x, y, nTabPage, wnd_oldContainer): + """ Handles the drop action from a DND operation. """ - # Set the non-active text color - if i != self.GetSelection(): - dc.SetTextForeground(self._nonActiveTextColor) + # Disable drag'n'drop for disabled tab + if not wnd_oldContainer._pagesInfoVec[nTabPage].GetEnabled(): + return wx.DragCancel - if self.TabHasImage(i): - imageXOffset = textOffset - 16 - self._pParent.GetPadding() - self._ImageList.Draw(self._pagesInfoVec[i].GetImageIndex(), dc, - posx + imageXOffset, imageYCoord, - wx.IMAGELIST_DRAW_TRANSPARENT, True) + self._isdragging = True + oldContainer = wnd_oldContainer + nIndex = -1 - dc.DrawText(self.GetPageText(i), posx + textOffset, imageYCoord) - - textWidth, textHeight = dc.GetTextExtent(self.GetPageText(i)) + where, nIndex = self.HitTest(wx.Point(x, y)) - # Restore the text forground - dc.SetTextForeground(self._activeTextColor) + oldNotebook = oldContainer.GetParent() + newNotebook = self.GetParent() - # Update the tab position & size - self._pagesInfoVec[i].SetPosition(wx.Point(posx, VERTICAL_BORDER_PADDING)) - self._pagesInfoVec[i].SetSize(wx.Size(tabWidth, tabHeight)) + if oldNotebook == newNotebook: - # Incase we are in VC8 style, redraw the active tab (incase it is visible) - if self.GetSelection() >= self._nFrom and self.GetSelection() < self._nFrom + len(vTabsInfo): + if nTabPage >= 0: + + if where == FNB_TAB: + self.MoveTabPage(nTabPage, nIndex) + + elif self.GetParent().GetWindowStyleFlag() & FNB_ALLOW_FOREIGN_DND: - hasImage = self.TabHasImage(self.GetSelection()) + if wx.Platform in ["__WXMSW__", "__WXGTK__"]: + if nTabPage >= 0: + + window = oldNotebook.GetPage(nTabPage) - dc.SetFont(boldFont) - width, pom = dc.GetTextExtent(self.GetPageText(self.GetSelection())) + 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) - tabWidth = self._pParent.GetPadding() * 2 + width + if imageindex >= 0: - if hasImage: - tabWidth += 16 + self._pParent.GetPadding() + bmp = oldNotebook.GetImageList().GetIcon(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.AddIcon(bmp) + newNotebook.SetImageList(newImageList) + + newNotebook.InsertPage(nIndex, window, caption, True, imageindex) - # Set the active tab font, pen brush and font-color - dc.SetPen(borderPen) - dc.SetBrush(selBrush) - dc.SetFont(boldFont) - dc.SetTextForeground(self._activeTextColor) - self.DrawVC8Tab(dc, activeTabPosx, self.GetSelection(), tabWidth, tabHeight) + self._isdragging = False + + return wx.DragMove - # 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 hasImage: - textOffset = self._pParent.GetPadding() * 2 + 16 + vc8ShapeLen - else: - textOffset = self._pParent.GetPadding() + vc8ShapeLen - # Draw the image for the tab if any - imageYCoord = (self.HasFlag(FNB_BOTTOM) and [6] or [8])[0] + def MoveTabPage(self, nMove, nMoveTo): + """ Moves a tab inside the same L{FlatNotebook}. """ - if hasImage: - imageXOffset = textOffset - 16 - self._pParent.GetPadding() - self._ImageList.Draw(self._pagesInfoVec[self.GetSelection()].GetImageIndex(), dc, - activeTabPosx + imageXOffset, imageYCoord, - wx.IMAGELIST_DRAW_TRANSPARENT, True) + if nMove == nMoveTo: + return - dc.DrawText(self.GetPageText(self.GetSelection()), activeTabPosx + textOffset, imageYCoord) + elif nMoveTo < len(self._pParent._windows): + nMoveTo = nMoveTo + 1 - # Update all tabs that can not fit into the screen as non-visible - for xx in xrange(self._nFrom + len(vTabsInfo), len(self._pagesInfoVec)): - - self._pagesInfoVec[xx].SetPosition(wx.Point(-1, -1)) - self._pagesInfoVec[xx].GetRegion().Clear() + self._pParent.Freeze() - # Draw the left/right/close buttons - # Left arrow - self.DrawLeftArrow(dc) - self.DrawRightArrow(dc) - self.DrawX(dc) + # 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) - def DrawVC8Tab(self, dc, posx, tabIdx, tabWidth, tabHeight): - """ Draws the VC8 style tabs. """ + pgInfo = self._pagesInfoVec[nMove] - borderPen = wx.Pen(self._colorBorder) - tabPoints = [wx.Point() for ii in xrange(8)] + self._pagesInfoVec.pop(nMove) + self._pagesInfoVec.insert(nMoveTo - 1, pgInfo) - # 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) - # - # + # 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() - tabPoints[0].x = (self.HasFlag(FNB_BOTTOM) and [posx] or [posx+self._factor])[0] - tabPoints[0].y = (self.HasFlag(FNB_BOTTOM) and [2] or [tabHeight - 3])[0] + pSizer.Layout() + self._iActivePage = nMoveTo - 1 + self._iPreviousActivePage = -1 + self.DoSetSelection(self._iActivePage) + self.Refresh() + self._pParent.Thaw() - tabPoints[1].x = tabPoints[0].x + tabHeight - VERTICAL_BORDER_PADDING - 3 - self._factor - tabPoints[1].y = (self.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 = (self.HasFlag(FNB_BOTTOM) and [tabHeight - VERTICAL_BORDER_PADDING] or [VERTICAL_BORDER_PADDING])[0] + def CanFitToScreen(self, page): + """ Returns wheter a tab can fit in the left space in the screen or not. """ - tabPoints[3].x = tabPoints[2].x + tabWidth - 2 - tabPoints[3].y = (self.HasFlag(FNB_BOTTOM) and [tabHeight - VERTICAL_BORDER_PADDING] or [VERTICAL_BORDER_PADDING])[0] + # 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 - tabPoints[4].x = tabPoints[3].x + 1 - tabPoints[4].y = (self.HasFlag(FNB_BOTTOM) and [tabPoints[3].y - 1] or [tabPoints[3].y + 1])[0] + style = self.GetParent().GetWindowStyleFlag() + render = self._mgr.GetRenderer(style) - tabPoints[5].x = tabPoints[4].x + 1 - tabPoints[5].y = (self.HasFlag(FNB_BOTTOM) and [(tabPoints[4].y - 1)] or [tabPoints[4].y + 1])[0] + vTabInfo = render.NumberTabsCanFit(self) - tabPoints[6].x = tabPoints[2].x + tabWidth - tabPoints[6].y = tabPoints[0].y + if page - self._nFrom >= len(vTabInfo): + return False + + return True - tabPoints[7].x = tabPoints[0].x - tabPoints[7].y = tabPoints[0].y - self._pagesInfoVec[tabIdx].SetRegion(tabPoints) + def GetNumOfVisibleTabs(self): + """ Returns the number of visible tabs. """ - # Draw the polygon - br = dc.GetBrush() - dc.SetBrush(wx.Brush((tabIdx == self.GetSelection() and [self._activeTabColor] or [self._colorTo])[0])) - dc.SetPen(wx.Pen((tabIdx == self.GetSelection() and [wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)] or [self._colorBorder])[0])) - dc.DrawPolygon(tabPoints) + count = 0 + for ii in xrange(self._nFrom, len(self._pagesInfoVec)): + if self._pagesInfoVec[ii].GetPosition() == wx.Point(-1, -1): + break + count = count + 1 - # Restore the brush - dc.SetBrush(br) + return count - rect = self.GetClientRect() - if tabIdx != self.GetSelection() and not self.HasFlag(FNB_BOTTOM): - - # Top default tabs - dc.SetPen(wx.Pen(self._colorBorder)) - lineY = rect.height - curPen = dc.GetPen() - curPen.SetWidth(1) - dc.SetPen(curPen) - dc.DrawLine(posx, lineY, posx+rect.width, lineY) + def GetEnabled(self, page): + """ Returns whether a tab is enabled or not. """ - # In case 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 == self.GetSelection(): + if page >= len(self._pagesInfoVec): + return True # Seems strange, but this is the default - borderPen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)) - brush = wx.TRANSPARENT_BRUSH - dc.SetPen(borderPen) - dc.SetBrush(brush) - dc.DrawPolygon(tabPoints) + return self._pagesInfoVec[page].GetEnabled() - # 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.FillVC8GradientColor(dc, tabPoints, tabIdx == self.GetSelection(), tabIdx) - # Draw a thin line to the right of the non-selected tab - if tabIdx != self.GetSelection(): + def Enable(self, page, enabled=True): + """ Enables or disables a tab. """ + + if page >= len(self._pagesInfoVec): + return - 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) + self._pagesInfoVec[page].Enable(enabled) - def FillVC8GradientColor(self, dc, tabPoints, bSelectedTab, tabIdx): - """ Fills a tab with a gradient colour. """ - - # calculate gradient coefficients - col2 = (self.HasFlag(FNB_BOTTOM) and [self._colorTo] or [self._colorFrom])[0] - col1 = (self.HasFlag(FNB_BOTTOM) and [self._colorFrom] or [self._colorTo])[0] + def GetSingleLineBorderColour(self): + """ Returns the colour for the single line border. """ - # If colorful tabs style is set, override the tab color - if self.HasFlag(FNB_COLORFUL_TABS): + if self.HasFlag(FNB_FANCY_TABS): + return self._colorFrom - if not self._pagesInfoVec[tabIdx].GetColor(): - - # First time, generate color, and keep it in the vector - tabColor = self.GenTabColour() - self._pagesInfoVec[tabIdx].SetColor(tabColor) - - if self.HasFlag(FNB_BOTTOM): - - col2 = LightColour(self._pagesInfoVec[tabIdx].GetColor(), 50 ) - col1 = LightColour(self._pagesInfoVec[tabIdx].GetColor(), 80 ) - - else: - - col1 = LightColour(self._pagesInfoVec[tabIdx].GetColor(), 50 ) - col2 = LightColour(self._pagesInfoVec[tabIdx].GetColor(), 80 ) - - size = abs(tabPoints[2].y - tabPoints[0].y) - 1 + return wx.WHITE - 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(dc) + def HasFlag(self, flag): + """ Returns whether a flag is present in the L{FlatNotebook} style. """ - while 1: - - if self.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) + style = self.GetParent().GetWindowStyleFlag() + res = (style & flag and [True] or [False])[0] + return res - dc.SetPen((bSelectedTab and [wx.Pen(self._activeTabColor)] or [wx.Pen(currCol)])[0]) - startX = self.GetStartX(tabPoints, y) - endX = self.GetEndX(tabPoints, y) - 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 [self._colorBorder])[0])) - - dc.DrawPoint(startX, y) - dc.DrawPoint(endX, y) - - # Progress the color - rf += rstep - gf += gstep - bf += bstep + def ClearFlag(self, flag): + """ Deletes a flag from the L{FlatNotebook} style. """ - if self.HasFlag(FNB_BOTTOM): - y = y + 1 - else: - y = y - 1 + style = self.GetParent().GetWindowStyleFlag() + style &= ~flag + self.SetWindowStyleFlag(style) - def GetStartX(self, tabPoints, y): - """ Returns the x start position of a tab. """ + def TabHasImage(self, tabIdx): + """ Returns whether a tab has an associated image index or not. """ - x1, x2, y1, y2 = 0.0, 0.0, 0.0, 0.0 + if self._ImageList: + return self._pagesInfoVec[tabIdx].GetImageIndex() != -1 + + return False - # We check the 3 points to the left - style = self.GetParent().GetWindowStyleFlag() - bBottomStyle = (style & FNB_BOTTOM and [True] or [False])[0] - match = False - if bBottomStyle: + 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) - 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 + event.Skip() + - # According to the equation y = ax + b => x = (y-b)/a - # We know the first 2 points + def PopupTabsMenu(self): + """ Pops up the menu activated with the drop down arrow in the navigation area. """ - if x2 == x1: - return x2 - else: - a = (y2 - y1)/(x2 - x1) + popupMenu = wx.Menu() - b = y1 - ((y2 - y1)/(x2 - x1))*x1 + 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) - if a == 0: - return int(x1) + # 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()] ); - x = (y - b)/a - - return int(x) + popupMenu.AppendItem(item) + item.Enable(pi.GetEnabled()) + + self.PopupMenu(popupMenu) - def GetEndX(self, tabPoints, y): - """ Returns the x end position of a tab. """ + def OnTabMenuSelection(self, event): + """ Handles the wx.EVT_MENU event for L{PageContainer}. """ - x1, x2, y1, y2 = 0.0, 0.0, 0.0, 0.0 + selection = event.GetId() + self.FireEvent(selection) - # We check the 3 points to the left - style = self.GetParent().GetWindowStyleFlag() - bBottomStyle = (style & FNB_BOTTOM and [True] or [False])[0] - match = False - if bBottomStyle: + def FireEvent(self, selection): + """ + Fires the wxEVT_FLATNOTEBOOK_PAGE_CHANGING and wxEVT_FLATNOTEBOOK_PAGE_CHANGED events + called from other methods (from menu selection or Smart Tabbing). + Utility function. + """ - 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 selection == self._iActivePage: + # No events for the same selection + return - else: + oldSelection = self._iActivePage + + event = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CHANGING, self.GetParent().GetId()) + event.SetSelection(selection) + event.SetOldSelection(oldSelection) + event.SetEventObject(self.GetParent()) - 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 self.GetParent().GetEventHandler().ProcessEvent(event) or event.IsAllowed(): + + self.SetSelection(selection) - if not match: - return tabPoints[3].x + # Fire a wxEVT_FLATNOTEBOOK_PAGE_CHANGED event + event.SetEventType(wxEVT_FLATNOTEBOOK_PAGE_CHANGED) + event.SetOldSelection(oldSelection) + self.GetParent().GetEventHandler().ProcessEvent(event) + - # According to the equation y = ax + b => x = (y-b)/a - # We know the first 2 points + def SetImageList(self, imglist): + """ Sets the image list for the page control. """ - # Vertical line - if x1 == x2: - return int(x1) - - a = (y2 - y1)/(x2 - x1) - b = y1 - ((y2 - y1)/(x2 - x1)) * x1 + self._ImageList = imglist - if a == 0: - return int(x1) - x = (y - b)/a + def GetImageList(self): + """ Returns the image list for the page control. """ + + return self._ImageList - return int(x) + def GetSelection(self): + """ Returns the current selected page. """ + + return self._iActivePage - def GenTabColour(self): - """ Generates a random soft pleasant colour for a tab. """ - return RandomColor() + def GetPageCount(self): + """ Returns the number of tabs in the L{FlatNotebook} control. """ + return len(self._pagesInfoVec) - def GetSingleLineBorderColor(self): - if self.HasFlag(FNB_VC8): - return self._activeTabColor - else: - return PageContainerBase.GetSingleLineBorderColor(self) + def GetPageText(self, page): + """ Returns the tab caption of the page. """ + return self._pagesInfoVec[page].GetCaption() - def SetFactor(self, factor): - """ Sets the brighten colour factor. """ - - self._factor = factor - self.Refresh() + def SetPageText(self, page, text): + """ Sets the tab caption of the page. """ - def GetFactor(self): - """ Returns the brighten colour factor. """ + self._pagesInfoVec[page].SetCaption(text) + return True - return self._factor + def DrawDragHint(self): + """ Draws small arrow at the place that the tab will be placed. """ + # get the index of tab that will be replaced with the dragged tab + pt = wx.GetMousePosition() + client_pt = self.ScreenToClient(pt) + where, tabIdx = self.HitTest(client_pt) + self._mgr.GetRenderer(self.GetParent().GetWindowStyleFlag()).DrawDragHint(self, tabIdx)