# Python Code By:
#
# Andrea Gavana, @ 02 Oct 2006
-# Latest Revision: 05 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
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 @ 05 Oct 2006, 20.00 GMT
-Version 0.4.
+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;
#-----------------------------------#
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
"````````````````"
]
+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):
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):
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:
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
# ---------------------------------------------------------------------------- #
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)
# ---------------------------------------------------------------------------- #
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
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
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):
# ---------------------------------------------------------------------------- #
-# 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
- # The child panels will inherit this bg color, so leave it at the default value
- #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)
- font = self.GetFont()
- font.SetWeight(wx.FONTWEIGHT_BOLD)
- dc.SetFont(font)
- height = dc.GetCharHeight()
- ##print height, font.Ok()
+ # 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)
- tabHeight = height + FNB_HEIGHT_SPACER # We use 8 pixels as padding
- self._pages.SetSizeHints(-1, tabHeight)
+ 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()
- self._mainSizer.Layout()
- self._pages._nFrom = self._nFrom
- self._pDropTarget = FNBDropTarget(self)
- self.SetDropTarget(self._pDropTarget)
+ def OnKeyUp(self, event):
+ """Handles the wx.EVT_KEY_UP for the L{TabNavigatorWindow}."""
+
+ if event.GetKeyCode() == wx.WXK_CONTROL:
+ self.CloseDialog()
- def CreatePageContainer(self):
- """ Creates the page container for the tabs. """
+ def OnNavigationKey(self, event):
+ """Handles the wx.EVT_NAVIGATION_KEY for the L{TabNavigatorWindow}. """
- return PageContainerBase(self, wx.ID_ANY)
+ 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 SetActiveTabTextColour(self, textColour):
- """ Sets the text colour for the active tab. """
+ def PopulateListControl(self, book):
+ """Populates the L{TabNavigatorWindow} listbox with a list of tabs."""
- self._pages._activeTextColor = textColour
+ selection = book.GetSelection()
+ count = book.GetPageCount()
+
+ self._listBox.Append(book.GetPageText(selection))
+ self._indexMap.append(selection)
+
+ prevSel = book.GetPreviousSelection()
+
+ if prevSel != wx.NOT_FOUND:
+
+ # Insert the previous selection as second entry
+ self._listBox.Append(book.GetPageText(prevSel))
+ self._indexMap.append(prevSel)
+
+ for c in xrange(count):
+
+ # Skip selected page
+ if c == selection:
+ continue
+ # Skip previous selected page as well
+ if c == prevSel:
+ continue
- def OnDropTarget(self, x, y, nTabPage, wnd_oldContainer):
- """ Handles the drop action from a DND operation. """
+ self._listBox.Append(book.GetPageText(c))
+ self._indexMap.append(c)
- return self._pages.OnDropTarget(x, y, nTabPage, wnd_oldContainer)
+ # Select the next entry after the current selection
+ self._listBox.SetSelection(0)
+ dummy = wx.NavigationKeyEvent()
+ dummy.SetDirection(True)
+ self.OnNavigationKey(dummy)
- def AddPage(self, window, caption, selected=True, imgindex=-1):
- """
- Add a page to the FlatNotebook.
+ def OnItemSelected(self, event):
+ """Handles the wx.EVT_LISTBOX_DCLICK event for the wx.ListBox inside L{TabNavigatorWindow}. """
- 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.CloseDialog()
- # sanity check
- if not window:
- return False
- # reparent the window to us
- window.Reparent(self)
+ def CloseDialog(self):
+ """Closes the L{TabNavigatorWindow} dialog, setting selection in L{FlatNotebook}."""
- # Add tab
- bSelected = selected or not self._windows
- curSel = self._pages.GetSelection()
+ bk = self.GetParent()
+ self._selectedItem = self._listBox.GetSelection()
+ iter = self._indexMap[self._selectedItem]
+ bk._pages.FireEvent(iter)
+ self.EndModal(wx.ID_OK)
+
- if not self._pages.IsShown():
- self._pages.Show()
+ def OnPanelPaint(self, event):
+ """Handles the wx.EVT_PAINT event for L{TabNavigatorWindow} top panel. """
- self._pages.AddPage(caption, bSelected, imgindex)
- self._windows.append(window)
+ dc = wx.PaintDC(self._panel)
+ rect = self._panel.GetClientRect()
- self.Freeze()
+ bmp = wx.EmptyBitmap(rect.width, rect.height)
- # 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()
+ mem_dc = wx.MemoryDC()
+ mem_dc.SelectObject(bmp)
+
+ endColour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)
+ startColour = LightColour(endColour, 50)
+ PaintStraightGradientBox(mem_dc, rect, startColour, endColour)
+
+ # Draw the caption title and place the bitmap
+ # get the bitmap optimal position, and draw it
+ bmpPt, txtPt = wx.Point(), wx.Point()
+ bmpPt.y = (rect.height - self._bmp.GetHeight())/2
+ bmpPt.x = 3
+ mem_dc.DrawBitmap(self._bmp, bmpPt.x, bmpPt.y, True)
+
+ # get the text position, and draw it
+ font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
+ font.SetWeight(wx.BOLD)
+ mem_dc.SetFont(font)
+ fontHeight = mem_dc.GetCharHeight()
+
+ txtPt.x = bmpPt.x + self._bmp.GetWidth() + 4
+ txtPt.y = (rect.height - fontHeight)/2
+ mem_dc.SetTextForeground(wx.WHITE)
+ mem_dc.DrawText("Opened tabs:", txtPt.x, txtPt.y)
+ mem_dc.SelectObject(wx.NullBitmap)
- 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
-
- 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()
-
- return True
+ return clientWidth - 38
- def SetSelection(self, page):
- """
- Sets the selection for the given page.
- The call to this function generates the page changing events
- """
+ def GetDropArrowButtonPos(self, pageContainer):
+ """ Returns the drop down button position in the navigation area. """
- if page >= len(self._windows) or not self._windows:
- return
+ return self.GetRightButtonPos(pageContainer)
- # 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 GetXPos(self, pageContainer):
+ """ Returns the 'X' button position in the navigation area. """
- # 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)
+ pc = pageContainer
+ style = pc.GetParent().GetWindowStyleFlag()
+ rect = pc.GetClientRect()
+ clientWidth = rect.width
+ if style & FNB_NO_X_BUTTON:
+ return clientWidth
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
+ return clientWidth - 22
- self.Thaw()
-
- self._pages.DoSetSelection(page)
+ def GetButtonsAreaLength(self, pageContainer):
+ """ Returns the navigation area width. """
- def DeletePage(self, page):
- """
- Deletes the specified page, and the associated window.
- The call to this function generates the page changing events.
- """
+ pc = pageContainer
+ style = pc.GetParent().GetWindowStyleFlag()
- if page >= len(self._windows):
- return
+ # ''
+ if style & FNB_NO_NAV_BUTTONS and style & FNB_NO_X_BUTTON and not style & FNB_DROPDOWN_TABS_LIST:
+ return 0
- # Fire a closing event
- event = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CLOSING, self.GetId())
- event.SetSelection(page)
- event.SetEventObject(self)
- self.GetEventHandler().ProcessEvent(event)
+ # 'x'
+ elif style & FNB_NO_NAV_BUTTONS and not style & FNB_NO_X_BUTTON and not style & FNB_DROPDOWN_TABS_LIST:
+ return 22
+
+ # '<>'
+ if not style & FNB_NO_NAV_BUTTONS and style & FNB_NO_X_BUTTON and not style & FNB_DROPDOWN_TABS_LIST:
+ return 53 - 16
+
+ # 'vx'
+ if style & FNB_DROPDOWN_TABS_LIST and not style & FNB_NO_X_BUTTON:
+ return 22 + 16
- # The event handler allows it?
- if not event.IsAllowed():
- return
+ # 'v'
+ if style & FNB_DROPDOWN_TABS_LIST and style & FNB_NO_X_BUTTON:
+ return 22
- self.Freeze()
+ # '<>x'
+ return 53
- # 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)
+ def DrawLeftArrow(self, pageContainer, dc):
+ """ Draw the left navigation arrow. """
+
+ pc = pageContainer
- # Remove it from the array as well
- self._windows.pop(page)
+ style = pc.GetParent().GetWindowStyleFlag()
+ if style & FNB_NO_NAV_BUTTONS:
+ return
- # Now we can destroy it in wxWidgets use Destroy instead of delete
- pageRemoved.Destroy()
+ # Make sure that there are pages in the container
+ if not pc._pagesInfoVec:
+ return
- self.Thaw()
+ # 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)
- self._pages.DoDeletePage(page)
- self.Refresh()
+ if pc._nFrom == 0:
+ # Handle disabled arrow
+ arrowBmp = wx.BitmapFromXPMData(left_arrow_disabled_xpm)
+
+ arrowBmp.SetMask(wx.Mask(arrowBmp, MASK_COLOR))
- # Fire a closed event
- closedEvent = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CLOSED, self.GetId())
- closedEvent.SetSelection(page)
- closedEvent.SetEventObject(self)
- self.GetEventHandler().ProcessEvent(closedEvent)
+ # Erase old bitmap
+ posx = self.GetLeftButtonPos(pc)
+ dc.DrawBitmap(self._leftBgBmp, posx, 6)
+ # Draw the new bitmap
+ dc.DrawBitmap(arrowBmp, posx, 6, True)
- def DeleteAllPages(self):
- """ Deletes all the pages. """
- if not self._windows:
- return False
+ def DrawRightArrow(self, pageContainer, dc):
+ """ Draw the right navigation arrow. """
- self.Freeze()
-
- for page in self._windows:
- page.Destroy()
+ pc = pageContainer
- self._windows = []
- self.Thaw()
+ style = pc.GetParent().GetWindowStyleFlag()
+ if style & FNB_NO_NAV_BUTTONS:
+ return
- # Clear the container of the tabs as well
- self._pages.DeleteAllPages()
- return True
+ # Make sure that there are pages in the container
+ if not pc._pagesInfoVec:
+ return
+ # Set the bitmap according to the button status
+ if pc._nRightButtonStatus == FNB_BTN_HOVER:
+ arrowBmp = wx.BitmapFromXPMData(right_arrow_hilite_xpm)
+ elif pc._nRightButtonStatus == FNB_BTN_PRESSED:
+ arrowBmp = wx.BitmapFromXPMData(right_arrow_pressed_xpm)
+ else:
+ arrowBmp = wx.BitmapFromXPMData(right_arrow_xpm)
- def GetCurrentPage(self):
- """ Returns the currently selected notebook page or None. """
+ # 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)
- sel = self._pages.GetSelection()
- if sel < 0:
- return None
-
- return self._windows[sel]
-
-
- def GetPage(self, page):
- """ Returns the window at the given page position, or None. """
+ arrowBmp.SetMask(wx.Mask(arrowBmp, MASK_COLOR))
- if page >= len(self._windows):
- return None
+ # erase old bitmap
+ posx = self.GetRightButtonPos(pc)
+ dc.DrawBitmap(self._rightBgBmp, posx, 6)
- return self._windows[page]
+ # Draw the new bitmap
+ dc.DrawBitmap(arrowBmp, posx, 6, True)
- def GetPageIndex(self, win):
- """ Returns the index at which the window is found. """
+ def DrawDropDownArrow(self, pageContainer, dc):
+ """ Draws the drop-down arrow in the navigation area. """
- try:
- return self._windows.index(win)
- except:
- return -1
+ pc = pageContainer
+
+ # Check if this style is enabled
+ style = pc.GetParent().GetWindowStyleFlag()
+ if not style & FNB_DROPDOWN_TABS_LIST:
+ return
+ # Make sure that there are pages in the container
+ if not pc._pagesInfoVec:
+ return
- def GetSelection(self):
- """ Returns the currently selected page, or -1 if none was selected. """
-
- return self._pages.GetSelection()
+ if pc._nArrowDownButtonStatus == FNB_BTN_HOVER:
+ downBmp = wx.BitmapFromXPMData(down_arrow_hilite_xpm)
+ elif pc._nArrowDownButtonStatus == FNB_BTN_PRESSED:
+ downBmp = wx.BitmapFromXPMData(down_arrow_pressed_xpm)
+ else:
+ downBmp = wx.BitmapFromXPMData(down_arrow_xpm)
+ downBmp.SetMask(wx.Mask(downBmp, MASK_COLOR))
- def AdvanceSelection(self, bForward=True):
- """
- Cycles through the tabs.
- The call to this function generates the page changing events.
- """
+ # erase old bitmap
+ posx = self.GetDropArrowButtonPos(pc)
+ dc.DrawBitmap(self._xBgBmp, posx, 6)
- self._pages.AdvanceSelection(bForward)
+ # Draw the new bitmap
+ dc.DrawBitmap(downBmp, posx, 6, True)
- def GetPageCount(self):
- """ Returns the number of pages in the FlatNotebook control. """
- return self._pages.GetPageCount()
+ def DrawX(self, pageContainer, dc):
+ """ Draw the 'X' navigation button in the navigation area. """
+ pc = pageContainer
+
+ # Check if this style is enabled
+ style = pc.GetParent().GetWindowStyleFlag()
+ if style & FNB_NO_X_BUTTON:
+ return
- def OnNavigationKey(self, event):
- """ Handles the wx.EVT_NAVIGATION_KEY event for FlatNotebook. """
+ # Make sure that there are pages in the container
+ if not pc._pagesInfoVec:
+ return
- if event.IsWindowChange():
- # change pages
- self.AdvanceSelection(event.GetDirection())
+ # 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:
- # pass to the parent
- if self.GetParent():
- event.SetCurrentFocus(self)
- self.GetParent().ProcessEvent(event)
-
-
- def GetPageShapeAngle(self, page_index):
- """ Returns the angle associated to a tab. """
+ xbmp = wx.BitmapFromXPMData(x_button_xpm)
- if page_index < 0 or page_index >= len(self._pages._pagesInfoVec):
- return None, False
+ xbmp.SetMask(wx.Mask(xbmp, MASK_COLOR))
- result = self._pages._pagesInfoVec[page_index].GetTabAngle()
- return result, True
+ # erase old bitmap
+ posx = self.GetXPos(pc)
+ dc.DrawBitmap(self._xBgBmp, posx, 6)
+ # Draw the new bitmap
+ dc.DrawBitmap(xbmp, posx, 6, True)
- def SetPageShapeAngle(self, page_index, angle):
- """ Sets the angle associated to a tab. """
- if page_index < 0 or page_index >= len(self._pages._pagesInfoVec):
- return
+ def DrawTabX(self, pageContainer, dc, rect, tabIdx, btnStatus):
+ """ Draws the 'X' in the selected tab. """
- if angle > 15:
+ pc = pageContainer
+ if not pc.HasFlag(FNB_X_ON_TAB):
return
- self._pages._pagesInfoVec[page_index].SetTabAngle(angle)
-
-
- def SetAllPagesShapeAngle(self, angle):
- """ Sets the angle associated to all the tab. """
-
- if angle > 15:
+ # We draw the 'x' on the active tab only
+ if tabIdx != pc.GetSelection() or tabIdx < 0:
return
- for ii in xrange(len(self._pages._pagesInfoVec)):
- self._pages._pagesInfoVec[ii].SetTabAngle(angle)
+ # Set the bitmap according to the button status
- self.Refresh()
+ 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 GetPageBestSize(self):
- """ Return the page best size. """
+ # erase old button
+ dc.DrawBitmap(self._tabXBgBmp, rect.x, rect.y)
- return self._pages.GetClientSize()
+ # Draw the new bitmap
+ dc.DrawBitmap(xBmp, rect.x, rect.y, True)
+ # Update the vector
+ rr = wx.Rect(rect.x, rect.y, 14, 13)
+ pc._pagesInfoVec[tabIdx].SetXRect(rr)
- def SetPageText(self, page, text):
- """ Sets the text for the given page. """
- bVal = self._pages.SetPageText(page, text)
- self._pages.Refresh()
+ def _GetBitmap(self, dc, rect, bmp):
- return bVal
+ mem_dc = wx.MemoryDC()
+ mem_dc.SelectObject(bmp)
+ mem_dc.Blit(0, 0, rect.width, rect.height, dc, rect.x, rect.y)
+ mem_dc.SelectObject(wx.NullBitmap)
+ return bmp
- def SetPadding(self, padding):
- """
- Sets the amount of space around each page's icon and label, in pixels.
- NB: only the horizontal padding is considered.
- """
+ def DrawTabsLine(self, pageContainer, dc):
+ """ Draws a line over the tabs. """
- self._nPadding = padding.GetWidth()
+ pc = pageContainer
+
+ clntRect = pc.GetClientRect()
+ clientRect3 = wx.Rect(0, 0, clntRect.width, clntRect.height)
+ if pc.HasFlag(FNB_BOTTOM):
+
+ clientRect = wx.Rect(0, 2, clntRect.width, clntRect.height - 2)
+ clientRect2 = wx.Rect(0, 1, clntRect.width, clntRect.height - 1)
+
+ else:
+
+ clientRect = wx.Rect(0, 0, clntRect.width, clntRect.height - 2)
+ clientRect2 = wx.Rect(0, 0, clntRect.width, clntRect.height - 1)
+
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+ dc.SetPen(wx.Pen(pc.GetSingleLineBorderColour()))
+ dc.DrawRectangleRect(clientRect2)
+ dc.DrawRectangleRect(clientRect3)
- def GetTabArea(self):
- """ Returns the associated page. """
+ dc.SetPen(wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)))
+ dc.DrawRectangleRect(clientRect)
- return self._pages
+ if not pc.HasFlag(FNB_TABS_BORDER_SIMPLE):
+
+ dc.SetPen(wx.Pen((pc.HasFlag(FNB_VC71) and [wx.Colour(247, 243, 233)] or [pc._tabAreaColor])[0]))
+ dc.DrawLine(0, 0, 0, clientRect.height+1)
+
+ if pc.HasFlag(FNB_BOTTOM):
+
+ dc.DrawLine(0, clientRect.height+1, clientRect.width, clientRect.height+1)
+
+ else:
+
+ dc.DrawLine(0, 0, clientRect.width, 0)
+
+ dc.DrawLine(clientRect.width - 1, 0, clientRect.width - 1, clientRect.height+1)
- def GetPadding(self):
- """ Returns the amount of space around each page's icon and label, in pixels. """
-
- return self._nPadding
+ 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 SetWindowStyleFlag(self, style):
- """ Sets the FlatNotebook window style flags. """
+ boldFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
+ boldFont.SetWeight(wx.FONTWEIGHT_BOLD)
- wx.Panel.SetWindowStyleFlag(self, style)
+ if pc.IsDefaultTabs():
+ shapePoints = int(tabHeight*math.tan(float(pc._pagesInfoVec[tabIdx].GetTabAngle())/180.0*math.pi))
- if self._pages:
+ # Calculate the text length using the bold font, so when selecting a tab
+ # its width will not change
+ dc.SetFont(boldFont)
+ width, pom = dc.GetTextExtent(pc.GetPageText(tabIdx))
+
+ # Set a minimum size to a tab
+ if width < 20:
+ width = 20
+
+ tabWidth = 2*pc._pParent.GetPadding() + width
+
+ # Style to add a small 'x' button on the top right
+ # of the tab
+ if pc.HasFlag(FNB_X_ON_TAB) and tabIdx == pc.GetSelection():
+ # The xpm image that contains the 'x' button is 9 pixels
+ spacer = 9
+ if pc.HasFlag(FNB_VC8):
+ spacer = 4
+
+ tabWidth += pc._pParent.GetPadding() + spacer
- # For changing the tab position (i.e. placing them top/bottom)
- # refreshing the tab container is not enough
- self.SetSelection(self._pages._iActivePage)
+ if pc.IsDefaultTabs():
+ # Default style
+ tabWidth += 2*shapePoints
+ hasImage = pc._ImageList != None and pc._pagesInfoVec[tabIdx].GetImageIndex() != -1
- def RemovePage(self, page):
- """ Deletes the specified page, without deleting the associated window. """
+ # For VC71 style, we only add the icon size (16 pixels)
+ if hasImage:
+
+ if not pc.IsDefaultTabs():
+ tabWidth += 16 + pc._pParent.GetPadding()
+ else:
+ # Default style
+ tabWidth += 16 + pc._pParent.GetPadding() + shapePoints/2
+
+ return tabWidth
- if page >= len(self._windows):
- return False
- # Fire a closing event
- event = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CLOSING, self.GetId())
- event.SetSelection(page)
- event.SetEventObject(self)
- self.GetEventHandler().ProcessEvent(event)
+ def CalcTabHeight(self, pageContainer):
+ """ Calculates the height of the input tab. """
- # The event handler allows it?
- if not event.IsAllowed():
- return False
+ if self._tabHeight:
+ return self._tabHeight
- self.Freeze()
+ pc = pageContainer
+ dc = wx.MemoryDC()
+ dc.SelectObject(wx.EmptyBitmap(1,1))
- # Remove the requested page
- pageRemoved = self._windows[page]
+ # 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
- # If the page is the current window, remove it from the sizer
- # as well
- if page == self._pages.GetSelection():
- self._mainSizer.Detach(pageRemoved)
+ if "__WXGTK__" in wx.PlatformInfo:
+ boldFont.SetWeight(wx.FONTWEIGHT_BOLD)
+ dc.SetFont(boldFont)
+
+ height = dc.GetCharHeight()
- # Remove it from the array as well
- self._windows.pop(page)
- self.Thaw()
+ 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
- self._pages.DoDeletePage(page)
+ self._tabHeight = tabHeight
+
+ return tabHeight
- return True
+ def DrawTabs(self, pageContainer, dc):
+ """ Actually draws the tabs in L{FlatNotebook}."""
- def SetRightClickMenu(self, menu):
- """ Sets the popup menu associated to a right click on a tab. """
+ 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()
- self._pages._pRightClickMenu = menu
+ # Calculate the number of rows required for drawing the tabs
+ rect = pc.GetClientRect()
+ clientWidth = rect.width
+ # Set the maximum client size
+ pc.SetSizeHints(self.GetButtonsAreaLength(pc), tabHeight)
+ borderPen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW))
- def GetPageText(self, page):
- """ Returns the tab caption. """
+ if style & FNB_VC71:
+ backBrush = wx.Brush(wx.Colour(247, 243, 233))
+ else:
+ backBrush = wx.Brush(pc._tabAreaColor)
- return self._pages.GetPageText(page)
+ noselBrush = wx.Brush(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNFACE))
+ selBrush = wx.Brush(pc._activeTabColor)
+ size = pc.GetSize()
- def SetGradientColors(self, fr, to, border):
- """ Sets the gradient colours for the tab. """
+ # Background
+ dc.SetTextBackground((style & FNB_VC71 and [wx.Colour(247, 243, 233)] or [pc.GetBackgroundColour()])[0])
+ dc.SetTextForeground(pc._activeTextColor)
+ dc.SetBrush(backBrush)
- self._pages._colorFrom = fr
- self._pages._colorTo = to
- self._pages._colorBorder = border
+ # If border style is set, set the pen to be border pen
+ if pc.HasFlag(FNB_TABS_BORDER_SIMPLE):
+ dc.SetPen(borderPen)
+ else:
+ colr = (pc.HasFlag(FNB_VC71) and [wx.Colour(247, 243, 233)] or [pc.GetBackgroundColour()])[0]
+ dc.SetPen(wx.Pen(colr))
+
+ dc.DrawRectangle(0, 0, size.x, size.y)
+
+ # Take 3 bitmaps for the background for the buttons
+
+ mem_dc = wx.MemoryDC()
+ #---------------------------------------
+ # X button
+ #---------------------------------------
+ rect = wx.Rect(self.GetXPos(pc), 6, 16, 14)
+ mem_dc.SelectObject(self._xBgBmp)
+ mem_dc.Blit(0, 0, rect.width, rect.height, dc, rect.x, rect.y)
+ mem_dc.SelectObject(wx.NullBitmap)
+ #---------------------------------------
+ # Right button
+ #---------------------------------------
+ rect = wx.Rect(self.GetRightButtonPos(pc), 6, 16, 14)
+ mem_dc.SelectObject(self._rightBgBmp)
+ mem_dc.Blit(0, 0, rect.width, rect.height, dc, rect.x, rect.y)
+ mem_dc.SelectObject(wx.NullBitmap)
- 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 SetPageImage(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.SetPageImage(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. """
+ # 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]
- self._pages._tabAreaColor = color
+ if style & FNB_VC71:
+ return self._renderers[FNB_VC71]
+ if style & FNB_FANCY_TABS:
+ return self._renderers[FNB_FANCY_TABS]
- def GetTabAreaColour(self):
- """ Returns the area behind the tabs colour. """
+ if style & FNB_VC8:
+ return self._renderers[FNB_VC8]
- return self._pages._tabAreaColor
+ # the default is to return the default renderer
+ return self._renderers[-1]
- def SetActiveTabColour(self, color):
- """ Sets the active tab colour. """
+#------------------------------------------
+# Default renderer
+#------------------------------------------
- self._pages._activeTabColor = color
+class FNBRendererDefault(FNBRenderer):
+ """
+ This class handles the drawing of tabs using the I{Standard} renderer.
+ """
+
+ def __init__(self):
+ """ Default class constructor. """
+ FNBRenderer.__init__(self)
+
- def GetActiveTabColour(self):
- """ Returns the active tab colour. """
+ def DrawTab(self, pageContainer, dc, posx, tabIdx, tabWidth, tabHeight, btnStatus):
+ """ Draws a tab using the I{Standard} style. """
- return self._pages._activeTabColor
+ # Default style
+ borderPen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW))
+ pc = pageContainer
+ tabPoints = [wx.Point() for ii in xrange(7)]
+ tabPoints[0].x = posx
+ tabPoints[0].y = (pc.HasFlag(FNB_BOTTOM) and [2] or [tabHeight - 2])[0]
-# ---------------------------------------------------------------------------- #
-# Class PageContainerBase
-# Acts as a container for the pages you add to FlatNotebook
-# ---------------------------------------------------------------------------- #
+ 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]
-class PageContainerBase(wx.Panel):
+ tabPoints[2].x = tabPoints[1].x+2
+ tabPoints[2].y = (pc.HasFlag(FNB_BOTTOM) and [tabHeight - VERTICAL_BORDER_PADDING] or [VERTICAL_BORDER_PADDING])[0]
- def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
- size=wx.DefaultSize, style=0):
+ tabPoints[3].x = int(posx+tabWidth-(tabHeight-2)*math.tan(float(pc._pagesInfoVec[tabIdx].GetTabAngle())/180.0*math.pi))-2
+ tabPoints[3].y = (pc.HasFlag(FNB_BOTTOM) and [tabHeight - VERTICAL_BORDER_PADDING] or [VERTICAL_BORDER_PADDING])[0]
+
+ tabPoints[4].x = tabPoints[3].x+2
+ tabPoints[4].y = (pc.HasFlag(FNB_BOTTOM) and [tabHeight - (VERTICAL_BORDER_PADDING+2)] or [(VERTICAL_BORDER_PADDING+2)])[0]
+
+ tabPoints[5].x = int(tabPoints[4].x+(tabHeight-2)*math.tan(float(pc._pagesInfoVec[tabIdx].GetTabAngle())/180.0*math.pi))
+ tabPoints[5].y = (pc.HasFlag(FNB_BOTTOM) and [2] or [tabHeight - 2])[0]
+
+ tabPoints[6].x = tabPoints[0].x
+ tabPoints[6].y = tabPoints[0].y
+
+ if tabIdx == pc.GetSelection():
+
+ # Draw the tab as rounded rectangle
+ dc.DrawPolygon(tabPoints)
+
+ else:
+
+ if tabIdx != pc.GetSelection() - 1:
+
+ # Draw a vertical line to the right of the text
+ pt1x = tabPoints[5].x
+ pt1y = (pc.HasFlag(FNB_BOTTOM) and [4] or [tabHeight - 6])[0]
+ pt2x = tabPoints[5].x
+ pt2y = (pc.HasFlag(FNB_BOTTOM) and [tabHeight - 4] or [4])[0]
+ dc.DrawLine(pt1x, pt1y, pt2x, pt2y)
+
+ if tabIdx == pc.GetSelection():
+
+ savePen = dc.GetPen()
+ whitePen = wx.Pen(wx.WHITE)
+ whitePen.SetWidth(1)
+ dc.SetPen(whitePen)
+
+ secPt = wx.Point(tabPoints[5].x + 1, tabPoints[5].y)
+ dc.DrawLine(tabPoints[0].x, tabPoints[0].y, secPt.x, secPt.y)
+
+ # Restore the pen
+ dc.SetPen(savePen)
+
+ # -----------------------------------
+ # Text and image drawing
+ # -----------------------------------
+
+ # Text drawing offset from the left border of the
+ # rectangle
+
+ # The width of the images are 16 pixels
+ padding = pc.GetParent().GetPadding()
+ shapePoints = int(tabHeight*math.tan(float(pc._pagesInfoVec[tabIdx].GetTabAngle())/180.0*math.pi))
+ hasImage = pc._pagesInfoVec[tabIdx].GetImageIndex() != -1
+ imageYCoord = (pc.HasFlag(FNB_BOTTOM) and [6] or [8])[0]
+
+ if hasImage:
+ textOffset = 2*pc._pParent._nPadding + 16 + shapePoints/2
+ else:
+ textOffset = pc._pParent._nPadding + shapePoints/2
+
+ textOffset += 2
+
+ if tabIdx != pc.GetSelection():
+
+ # Set the text background to be like the vertical lines
+ dc.SetTextForeground(pc._pParent.GetNonActiveTabTextColour())
+
+ if hasImage:
+
+ imageXOffset = textOffset - 16 - padding
+ pc._ImageList.Draw(pc._pagesInfoVec[tabIdx].GetImageIndex(), dc,
+ posx + imageXOffset, imageYCoord,
+ wx.IMAGELIST_DRAW_TRANSPARENT, True)
+
+ dc.DrawText(pc.GetPageText(tabIdx), posx + textOffset, imageYCoord)
+
+ # draw 'x' on tab (if enabled)
+ if pc.HasFlag(FNB_X_ON_TAB) and tabIdx == pc.GetSelection():
+
+ textWidth, textHeight = dc.GetTextExtent(pc.GetPageText(tabIdx))
+ tabCloseButtonXCoord = posx + textOffset + textWidth + 1
+
+ # take a bitmap from the position of the 'x' button (the x on tab button)
+ # this bitmap will be used later to delete old buttons
+ tabCloseButtonYCoord = imageYCoord
+ x_rect = wx.Rect(tabCloseButtonXCoord, tabCloseButtonYCoord, 16, 16)
+ self._tabXBgBmp = self._GetBitmap(dc, x_rect, self._tabXBgBmp)
+
+ # Draw the tab
+ self.DrawTabX(pc, dc, x_rect, tabIdx, btnStatus)
+
+
+#------------------------------------------------------------------
+# Visual studio 7.1
+#------------------------------------------------------------------
+
+class FNBRendererVC71(FNBRenderer):
+ """
+ This class handles the drawing of tabs using the I{VC71} renderer.
+ """
+
+ def __init__(self):
""" Default class constructor. """
+
+ FNBRenderer.__init__(self)
+
+
+ def DrawTab(self, pageContainer, dc, posx, tabIdx, tabWidth, tabHeight, btnStatus):
+ """ Draws a tab using the I{VC71} style. """
+
+ # Visual studio 7.1 style
+ borderPen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW))
+ pc = pageContainer
+
+ dc.SetPen((tabIdx == pc.GetSelection() and [wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE))] or [borderPen])[0])
+ dc.SetBrush((tabIdx == pc.GetSelection() and [wx.Brush(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE))] or [wx.Brush(wx.Colour(247, 243, 233))])[0])
+
+ if tabIdx == pc.GetSelection():
- 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)
+ 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)
- 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
+ # Draw a black line on the left side of the
+ # rectangle
+ dc.SetPen(wx.BLACK_PEN)
- self._pagesInfoVec = []
+ blackLineY1 = VERTICAL_BORDER_PADDING
+ blackLineY2 = tabH
+ dc.DrawLine(posx + tabWidth, blackLineY1, posx + tabWidth, blackLineY2)
- 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)
+ # 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
- self._nFrom = 0
- self._isdragging = False
+ pen = wx.Pen((pc.HasFlag(FNB_BOTTOM) and [wx.BLACK] or [wx.WHITE])[0])
+ dc.SetPen(pen)
+ whiteLinePosY = (pc.HasFlag(FNB_BOTTOM) and [blackLineY2] or [VERTICAL_BORDER_PADDING ])[0]
+ dc.DrawLine(posx , whiteLinePosY, posx + tabWidth + 1, whiteLinePosY)
+
+ # Draw a white vertical line to the left of the tab
+ dc.SetPen(wx.WHITE_PEN)
+ if not pc.HasFlag(FNB_BOTTOM):
+ blackLineY2 += 1
+
+ dc.DrawLine(posx, blackLineY1, posx, blackLineY2)
+
+ else:
- wx.Panel.__init__(self, parent, id, pos, size, style|wx.TAB_TRAVERSAL)
+ # We dont draw a rectangle for non selected tabs, but only
+ # vertical line on the left
- self._pDropTarget = FNBDropTarget(self)
- self.SetDropTarget(self._pDropTarget)
+ blackLineY1 = (pc.HasFlag(FNB_BOTTOM) and [VERTICAL_BORDER_PADDING + 2] or [VERTICAL_BORDER_PADDING + 1])[0]
+ blackLineY2 = pc.GetSize().y - 5
+ dc.DrawLine(posx + tabWidth, blackLineY1, posx + tabWidth, blackLineY2)
+
+ # -----------------------------------
+ # Text and image drawing
+ # -----------------------------------
- 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)
+ # 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]
+
+ if hasImage:
+ textOffset = 2*pc._pParent._nPadding + 16
+ else:
+ textOffset = pc._pParent._nPadding
+
+ if tabIdx != pc.GetSelection():
+
+ # Set the text background to be like the vertical lines
+ dc.SetTextForeground(pc._pParent.GetNonActiveTabTextColour())
+
+ if hasImage:
+
+ imageXOffset = textOffset - 16 - padding
+ pc._ImageList.Draw(pc._pagesInfoVec[tabIdx].GetImageIndex(), dc,
+ posx + imageXOffset, imageYCoord,
+ wx.IMAGELIST_DRAW_TRANSPARENT, True)
+
+ dc.DrawText(pc.GetPageText(tabIdx), posx + textOffset, imageYCoord)
+
+ # draw 'x' on tab (if enabled)
+ if pc.HasFlag(FNB_X_ON_TAB) and tabIdx == pc.GetSelection():
+
+ textWidth, textHeight = dc.GetTextExtent(pc.GetPageText(tabIdx))
+ tabCloseButtonXCoord = posx + textOffset + textWidth + 1
+ # take a bitmap from the position of the 'x' button (the x on tab button)
+ # this bitmap will be used later to delete old buttons
+ tabCloseButtonYCoord = imageYCoord
+ x_rect = wx.Rect(tabCloseButtonXCoord, tabCloseButtonYCoord, 16, 16)
+ self._tabXBgBmp = self._GetBitmap(dc, x_rect, self._tabXBgBmp)
- def GetButtonAreaWidth(self):
- """ Returns the width of the navigation button area. """
+ # Draw the tab
+ self.DrawTabX(pc, dc, x_rect, tabIdx, btnStatus)
- style = self.GetParent().GetWindowStyleFlag()
- btnareawidth = 2*self._pParent._nPadding
- if style & FNB_NO_X_BUTTON == 0:
- btnareawidth += BUTTON_SPACE
+#------------------------------------------------------------------
+# Fancy style
+#------------------------------------------------------------------
- if style & FNB_NO_NAV_BUTTONS == 0:
- btnareawidth += 2*BUTTON_SPACE
+class FNBRendererFancy(FNBRenderer):
+ """
+ This class handles the drawing of tabs using the I{Fancy} renderer.
+ """
- return btnareawidth
+ def __init__(self):
+ """ Default class constructor. """
+ FNBRenderer.__init__(self)
- def OnEraseBackground(self, event):
- """ Handles the wx.EVT_ERASE_BACKGROUND event for PageContainerBase (does nothing)."""
- pass
+ def DrawTab(self, pageContainer, dc, posx, tabIdx, tabWidth, tabHeight, btnStatus):
+ """ Draws a tab using the I{Fancy} style, similar to VC71 but with gradients. """
-
- def OnPaint(self, event):
- """ Handles the wx.EVT_PAINT event for PageContainerBase."""
+ # 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
- dc = wx.BufferedPaintDC(self)
+ pen = (tabIdx == pc.GetSelection() and [wx.Pen(pc._pParent.GetBorderColour())] or [wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE))])[0]
+
+ if tabIdx == pc.GetSelection():
+
+ posy = (pc.HasFlag(FNB_BOTTOM) and [2] or [VERTICAL_BORDER_PADDING])[0]
+ th = tabHeight - 5
+
+ rect = wx.Rect(posx, posy, tabWidth, th)
+
+ col2 = (pc.HasFlag(FNB_BOTTOM) and [pc._pParent.GetGradientColourTo()] or [pc._pParent.GetGradientColourFrom()])[0]
+ col1 = (pc.HasFlag(FNB_BOTTOM) and [pc._pParent.GetGradientColourFrom()] or [pc._pParent.GetGradientColourTo()])[0]
+
+ PaintStraightGradientBox(dc, rect, col1, col2)
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+ dc.SetPen(pen)
+ dc.DrawRectangleRect(rect)
+
+ # erase the bottom/top line of the rectangle
+ dc.SetPen(wx.Pen(pc._pParent.GetGradientColourFrom()))
+ if pc.HasFlag(FNB_BOTTOM):
+ dc.DrawLine(rect.x, 2, rect.x + rect.width, 2)
+ else:
+ dc.DrawLine(rect.x, rect.y + rect.height - 1, rect.x + rect.width, rect.y + rect.height - 1)
+
+ else:
+
+ # We dont draw a rectangle for non selected tabs, but only
+ # vertical line on the left
+ dc.SetPen(borderPen)
+ dc.DrawLine(posx + tabWidth, VERTICAL_BORDER_PADDING + 3, posx + tabWidth, tabHeight - 4)
+
+
+ # -----------------------------------
+ # Text and image drawing
+ # -----------------------------------
+
+ # Text drawing offset from the left border of the
+ # rectangle
+
+ # The width of the images are 16 pixels
+ padding = pc.GetParent().GetPadding()
+ hasImage = pc._pagesInfoVec[tabIdx].GetImageIndex() != -1
+ imageYCoord = (pc.HasFlag(FNB_BOTTOM) and [6] or [8])[0]
+
+ if hasImage:
+ textOffset = 2*pc._pParent._nPadding + 16
+ else:
+ textOffset = pc._pParent._nPadding
+
+ textOffset += 2
+
+ if tabIdx != pc.GetSelection():
+
+ # Set the text background to be like the vertical lines
+ dc.SetTextForeground(pc._pParent.GetNonActiveTabTextColour())
+
+ if hasImage:
+
+ imageXOffset = textOffset - 16 - padding
+ pc._ImageList.Draw(pc._pagesInfoVec[tabIdx].GetImageIndex(), dc,
+ posx + imageXOffset, imageYCoord,
+ wx.IMAGELIST_DRAW_TRANSPARENT, True)
+
+ dc.DrawText(pc.GetPageText(tabIdx), posx + textOffset, imageYCoord)
+
+ # draw 'x' on tab (if enabled)
+ if pc.HasFlag(FNB_X_ON_TAB) and tabIdx == pc.GetSelection():
+
+ textWidth, textHeight = dc.GetTextExtent(pc.GetPageText(tabIdx))
+ tabCloseButtonXCoord = posx + textOffset + textWidth + 1
+
+ # take a bitmap from the position of the 'x' button (the x on tab button)
+ # this bitmap will be used later to delete old buttons
+ tabCloseButtonYCoord = imageYCoord
+ x_rect = wx.Rect(tabCloseButtonXCoord, tabCloseButtonYCoord, 16, 16)
+ self._tabXBgBmp = self._GetBitmap(dc, x_rect, self._tabXBgBmp)
+
+ # Draw the tab
+ self.DrawTabX(pc, dc, x_rect, tabIdx, btnStatus)
+
+
+#------------------------------------------------------------------
+# Visual studio 2005 (VS8)
+#------------------------------------------------------------------
+class FNBRendererVC8(FNBRenderer):
+ """
+ This class handles the drawing of tabs using the I{VC8} renderer.
+ """
+
+ def __init__(self):
+ """ Default class constructor. """
+
+ FNBRenderer.__init__(self)
+ self._first = True
+ self._factor = 1
+
+
+ def DrawTabs(self, pageContainer, dc):
+ """ Draws all the tabs using VC8 style. Overloads The DrawTabs method in parent class. """
+
+ pc = pageContainer
if "__WXMAC__" in wx.PlatformInfo:
# Works well on MSW & GTK, however this lines should be skipped on MAC
- if len(self._pagesInfoVec) == 0 or self._nFrom >= len(self._pagesInfoVec):
- self.Hide()
- event.Skip()
+ if not pc._pagesInfoVec or pc._nFrom >= len(pc._pagesInfoVec):
+ pc.Hide()
return
-
+
# Get the text hight
- style = self.GetParent().GetWindowStyleFlag()
+ tabHeight = self.CalcTabHeight(pageContainer)
- # 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)
+ # 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)
-
- width, height = dc.GetTextExtent("Tp")
-
- tabHeight = height + FNB_HEIGHT_SPACER # We use 8 pixels as padding
# Calculate the number of rows required for drawing the tabs
- rect = self.GetClientRect()
- clientWidth = rect.width
+ rect = pc.GetClientRect()
# Set the maximum client size
- self.SetSizeHints(self.GetButtonsAreaLength(), 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(self._tabAreaColor)
-
- noselBrush = wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
- selBrush = wx.Brush(self._activeTabColor)
+ pc.SetSizeHints(self.GetButtonsAreaLength(pc), tabHeight)
+ borderPen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW))
- size = self.GetSize()
+ # 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((style & FNB_VC71 and [wx.Colour(247, 243, 233)] or [self.GetBackgroundColour()])[0])
- dc.SetTextForeground(self._activeTextColor)
- dc.SetBrush(backBrush)
-
+ dc.SetTextBackground(pc.GetBackgroundColour())
+ dc.SetTextForeground(pc._activeTextColor)
+
# If border style is set, set the pen to be border pen
- if style & FNB_TABS_BORDER_SIMPLE:
+ if pc.HasFlag(FNB_TABS_BORDER_SIMPLE):
dc.SetPen(borderPen)
else:
- pc = (self.HasFlag(FNB_VC71) and [wx.Colour(247, 243, 233)] or [self.GetBackgroundColour()])[0]
- dc.SetPen(wx.Pen(pc))
+ 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(), 6, 16, 14)
+ 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(), 6, 16, 14)
+ 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(), 6, 16, 14)
+ 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(dc)
+ self.DrawTabsLine(pc, dc)
# Restore the pen
dc.SetPen(borderPen)
- if self.HasFlag(FNB_VC71):
+ # 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()
- 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 visible tabs, in VC8 style, we draw them from right to left
+ vTabsInfo = self.NumberTabsCanFit(pc)
- pen = wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE))
- dc.SetPen(pen)
+ activeTabPosx = 0
+ activeTabWidth = 0
+ activeTabHeight = 0
- # Draw thik grey line between the windows area and
- # the tab area
-
- for num in xrange(3):
- dc.DrawLine(0, greyLineYVal + num, size.x, greyLineYVal + num)
-
- wbPen = wx.Pen((self.HasFlag(FNB_BOTTOM) and [wx.BLACK] or [wx.WHITE])[0])
- dc.SetPen(wbPen)
- dc.DrawLine(1, whiteLineYVal, size.x - 1, whiteLineYVal)
-
- # Restore the pen
- dc.SetPen(borderPen)
-
- 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()
-
- # 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()
-
- 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]
-
- #----------------------------------------------------------
- # Go over and draw the visible tabs
- #----------------------------------------------------------
-
- count = self._nFrom
-
- for ii in xrange(self._nFrom, len(self._pagesInfoVec)):
-
- if style != FNB_VC71:
- shapePoints = int(tabHeight*math.tan(float(self._pagesInfoVec[ii].GetTabAngle())/180.0*math.pi))
- else:
- shapePoints = 0
-
- dc.SetPen(borderPen)
- dc.SetBrush((ii==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(ii))
+ 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((ii==self.GetSelection() and [boldFont] or [normalFont])[0])
-
- # Set a minimum size to a tab
- if width < 20:
- width = 20
+ 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]
-
- # 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
-
- hasImage = self._ImageList != None and self._pagesInfoVec[ii].GetImageIndex() != -1
-
- # 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
+ tabWidth = self.CalcTabWidth(pageContainer, i, tabHeight)
+ posx = vTabsInfo[cur].x
# By default we clean the tab region
- self._pagesInfoVec[ii].GetRegion().Clear()
-
- # Clean the 'x' buttn on the tab
+ # 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
- self._pagesInfoVec[ii].GetXRect().SetSize(wx.Size(-1, -1))
+ pc._pagesInfoVec[i].GetXRect().SetSize(wx.Size(-1, -1))
# 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
-
- # 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
+ # Incase we are drawing the active tab
+ # we need to redraw so it will appear on top
+ # of all other tabs
- if ii != self.GetSelection():
- # Set the text background to be like the vertical lines
- dc.SetTextForeground(self._nonActiveTextColor)
+ # 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):
- if hasImage:
-
- imageXOffset = textOffset - 16 - self._pParent._nPadding
- self._ImageList.Draw(self._pagesInfoVec[ii].GetImageIndex(), dc,
- posx + imageXOffset, imageYCoord,
- wx.IMAGELIST_DRAW_TRANSPARENT, True)
-
- dc.DrawText(self.GetPageText(ii), posx + textOffset, imageYCoord)
-
- # From version 1.2 - a style to add 'x' button
- # on a tab
+ activeTabPosx = posx
+ activeTabWidth = tabWidth
+ activeTabHeight = tabHeight
- if self.HasFlag(FNB_X_ON_TAB) and ii == self.GetSelection():
+ else:
- textWidth, textHeight = dc.GetTextExtent(self.GetPageText(ii))
- 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)
- 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)
-
- # Draw the tab
- self.DrawTabX(dc, x_rect, ii)
+ self.DrawTab(pc, dc, posx, i, tabWidth, tabHeight, pc._nTabXButtonStatus)
# Restore the text forground
- dc.SetTextForeground(self._activeTextColor)
+ dc.SetTextForeground(pc._activeTextColor)
# 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
-
+ 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 ii in xrange(count, len(self._pagesInfoVec)):
+ for xx in xrange(pc._nFrom + len(vTabsInfo), len(pc._pagesInfoVec)):
- self._pagesInfoVec[ii].SetPosition(wx.Point(-1, -1))
- self._pagesInfoVec[ii].GetRegion().Clear()
+ pc._pagesInfoVec[xx].SetPosition(wx.Point(-1, -1))
+ pc._pagesInfoVec[xx].GetRegion().Clear()
- # Draw the left/right/close buttons
+ # Draw the left/right/close buttons
# Left arrow
- self.DrawLeftArrow(dc)
- self.DrawRightArrow(dc)
- self.DrawX(dc)
+ self.DrawLeftArrow(pc, dc)
+ self.DrawRightArrow(pc, dc)
+ self.DrawX(pc, dc)
+ self.DrawDropDownArrow(pc, dc)
- def DrawFancyTab(self, dc, posx, tabIdx, tabWidth, tabHeight):
- """
- 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))
- pen = (tabIdx==self.GetSelection() and [wx.Pen(self._colorBorder)] \
- or [wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE))])[0]
+ def DrawTab(self, pageContainer, dc, posx, tabIdx, tabWidth, tabHeight, btnStatus):
+ """ Draws a tab using VC8 style. """
- fnb_bottom = self.HasFlag(FNB_BOTTOM)
+ pc = pageContainer
+ borderPen = wx.Pen(pc._pParent.GetBorderColour())
+ tabPoints = [wx.Point() for ii in xrange(8)]
- if tabIdx == self.GetSelection():
-
- posy = (fnb_bottom and [2] or [VERTICAL_BORDER_PADDING])[0]
- th = (fnb_bottom and [tabHeight - 2] or [tabHeight - 5])[0]
+ # 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)
+ #
+ #
- rect = wx.Rect(posx, posy, tabWidth, th)
- self.FillGradientColor(dc, rect)
- dc.SetBrush(wx.TRANSPARENT_BRUSH)
- dc.SetPen(pen)
- dc.DrawRectangleRect(rect)
+ 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]
- # 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)
-
- else:
-
- # We dont draw a rectangle for non selected tabs, but only
- # vertical line on the left
- dc.SetPen(borderPen)
- dc.DrawLine(posx + tabWidth, VERTICAL_BORDER_PADDING + 3, posx + tabWidth, tabHeight - 4)
-
+ 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]
- def DrawVC71Tab(self, dc, posx, tabIdx, tabWidth, tabHeight):
- """ Draws tabs with VC71 style. """
+ tabPoints[2].x = tabPoints[1].x + 4
+ tabPoints[2].y = (pc.HasFlag(FNB_BOTTOM) and [tabHeight - VERTICAL_BORDER_PADDING] or [VERTICAL_BORDER_PADDING])[0]
- 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])
+ 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]
- if tabIdx == self.GetSelection():
-
- posy = (fnb_bottom and [0] or [VERTICAL_BORDER_PADDING])[0]
- dc.DrawRectangle(posx, posy, tabWidth, tabHeight - 1)
+ 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]
- # Draw a black line on the left side of the
- # rectangle
- dc.SetPen(wx.BLACK_PEN)
+ 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]
- 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)
+ tabPoints[6].x = tabPoints[2].x + tabWidth
+ tabPoints[6].y = tabPoints[0].y
- # 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
+ tabPoints[7].x = tabPoints[0].x
+ tabPoints[7].y = tabPoints[0].y
- 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)
+ pc._pagesInfoVec[tabIdx].SetRegion(tabPoints)
- # 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)
+ # 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):
- else:
+ # 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)
- # We dont draw a rectangle for non selected tabs, but only
- # vertical line on the left
+ # 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)
- 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)
+ # 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
- def DrawStandardTab(self, dc, posx, tabIdx, tabWidth, tabHeight):
- """ Draws tabs with standard style. """
+ # Draw the image for the tab if any
+ imageYCoord = (pc.HasFlag(FNB_BOTTOM) and [6] or [8])[0]
- fnb_bottom = self.HasFlag(FNB_BOTTOM)
+ if pc.TabHasImage(tabIdx):
- # Default style
- borderPen = wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW))
+ imageXOffset = textOffset - 16 - pc._pParent.GetPadding()
+ pc._ImageList.Draw(pc._pagesInfoVec[tabIdx].GetImageIndex(), dc,
+ posx + imageXOffset, imageYCoord,
+ wx.IMAGELIST_DRAW_TRANSPARENT, True)
- tabPoints = [wx.Point() for ii in xrange(7)]
- tabPoints[0].x = posx
- tabPoints[0].y = (fnb_bottom and [2] or [tabHeight - 2])[0]
+ 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)
- 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]
+ # draw 'x' on tab (if enabled)
+ if pc.HasFlag(FNB_X_ON_TAB) and tabIdx == pc.GetSelection():
- tabPoints[2].x = tabPoints[1].x+2
- tabPoints[2].y = (fnb_bottom and [tabHeight - VERTICAL_BORDER_PADDING] or [VERTICAL_BORDER_PADDING])[0]
+ textWidth, textHeight = dc.GetTextExtent(pc.GetPageText(tabIdx))
+ tabCloseButtonXCoord = posx + textOffset + textWidth + 1
- 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]
+ # 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)
- tabPoints[4].x = tabPoints[3].x+2
- tabPoints[4].y = (fnb_bottom and [tabHeight - (VERTICAL_BORDER_PADDING+2)] or [(VERTICAL_BORDER_PADDING+2)])[0]
- tabPoints[5].x = int(tabPoints[4].x+(tabHeight-2)*math.tan(float(self._pagesInfoVec[tabIdx].GetTabAngle())/180.0*math.pi))
- tabPoints[5].y = (fnb_bottom and [2] or [tabHeight - 2])[0]
+ def FillVC8GradientColour(self, pageContainer, dc, tabPoints, bSelectedTab, tabIdx):
+ """ Fills a tab with a gradient shading. """
- tabPoints[6].x = tabPoints[0].x
- tabPoints[6].y = tabPoints[0].y
+ # calculate gradient coefficients
+ pc = pageContainer
- if tabIdx == self.GetSelection():
-
- # Draw the tab as rounded rectangle
- dc.DrawPolygon(tabPoints)
+ 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)
- else:
+ 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 tabIdx != self.GetSelection() - 1:
+ if not pc._pagesInfoVec[tabIdx].GetColour():
- # 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)
+ # First time, generate color, and keep it in the vector
+ tabColor = RandomColour()
+ pc._pagesInfoVec[tabIdx].SetColour(tabColor)
- if tabIdx == self.GetSelection():
-
- savePen = dc.GetPen()
- whitePen = wx.Pen(wx.WHITE)
- whitePen.SetWidth(1)
- dc.SetPen(whitePen)
-
- secPt = wx.Point(tabPoints[5].x + 1, tabPoints[5].y)
- dc.DrawLine(tabPoints[0].x, tabPoints[0].y, secPt.x, secPt.y)
+ 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
- # Restore the pen
- dc.SetPen(savePen)
+ 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
- def AddPage(self, caption, selected=True, imgindex=-1):
- """
- Add a page to the FlatNotebook.
+ # 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)
- 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.
+ while 1:
- Return value:
- True if successful, False otherwise.
- """
+ 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)
- if selected:
-
- self._iActivePage = len(self._pagesInfoVec)
-
- # Create page info and add it to the vector
- pageInfo = PageInfo(caption, imgindex)
- self._pagesInfoVec.append(pageInfo)
+ dc.SetPen((bSelectedTab and [wx.Pen(pc._activeTabColor)] or [wx.Pen(currCol)])[0])
+ startX = self.GetStartX(tabPoints, y, pc.GetParent().GetWindowStyleFlag())
+ endX = self.GetEndX(tabPoints, y, pc.GetParent().GetWindowStyleFlag())
+ dc.DrawLine(startX, y, endX, y)
+
+ # Draw the border using the 'edge' point
+ dc.SetPen(wx.Pen((bSelectedTab and [wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)] or [pc._colorBorder])[0]))
+
+ dc.DrawPoint(startX, y)
+ dc.DrawPoint(endX, y)
+
+ # Progress the color
+ rf += rstep
+ gf += gstep
+ bf += bstep
+
+ if pc.HasFlag(FNB_BOTTOM):
+ y = y + 1
+ else:
+ y = y - 1
+
+
+ def GetStartX(self, tabPoints, y, style):
+ """ Returns the x start position of a tab. """
+
+ x1, x2, y1, y2 = 0.0, 0.0, 0.0, 0.0
+
+ # We check the 3 points to the left
+
+ bBottomStyle = (style & FNB_BOTTOM and [True] or [False])[0]
+ match = False
+
+ if bBottomStyle:
+
+ for i in xrange(3):
+
+ if y >= tabPoints[i].y and y < tabPoints[i+1].y:
+
+ x1 = tabPoints[i].x
+ x2 = tabPoints[i+1].x
+ y1 = tabPoints[i].y
+ y2 = tabPoints[i+1].y
+ match = True
+ break
+
+ else:
+
+ for i in xrange(3):
+
+ if y <= tabPoints[i].y and y > tabPoints[i+1].y:
+
+ x1 = tabPoints[i].x
+ x2 = tabPoints[i+1].x
+ y1 = tabPoints[i].y
+ y2 = tabPoints[i+1].y
+ match = True
+ break
+
+ if not match:
+ return tabPoints[2].x
+
+ # According to the equation y = ax + b => x = (y-b)/a
+ # We know the first 2 points
+
+ if x2 == x1:
+ return x2
+ else:
+ a = (y2 - y1)/(x2 - x1)
+
+ b = y1 - ((y2 - y1)/(x2 - x1))*x1
+
+ if a == 0:
+ return int(x1)
+
+ x = (y - b)/a
+
+ return int(x)
+
+
+ def GetEndX(self, tabPoints, y, style):
+ """ Returns the x end position of a tab. """
+
+ x1, x2, y1, y2 = 0.0, 0.0, 0.0, 0.0
+
+ # We check the 3 points to the left
+ bBottomStyle = (style & FNB_BOTTOM and [True] or [False])[0]
+ match = False
+
+ if bBottomStyle:
+
+ for i in xrange(7, 3, -1):
+
+ if y >= tabPoints[i].y and y < tabPoints[i-1].y:
+
+ x1 = tabPoints[i].x
+ x2 = tabPoints[i-1].x
+ y1 = tabPoints[i].y
+ y2 = tabPoints[i-1].y
+ match = True
+ break
+
+ else:
+
+ for i in xrange(7, 3, -1):
+
+ if y <= tabPoints[i].y and y > tabPoints[i-1].y:
+
+ x1 = tabPoints[i].x
+ x2 = tabPoints[i-1].x
+ y1 = tabPoints[i].y
+ y2 = tabPoints[i-1].y
+ match = True
+ break
+
+ if not match:
+ return tabPoints[3].x
+
+ # According to the equation y = ax + b => x = (y-b)/a
+ # We know the first 2 points
+
+ # Vertical line
+ if x1 == x2:
+ return int(x1)
+
+ a = (y2 - y1)/(x2 - x1)
+ b = y1 - ((y2 - y1)/(x2 - x1))*x1
+
+ if a == 0:
+ return int(x1)
+
+ x = (y - b)/a
+
+ return int(x)
+
+
+ def NumberTabsCanFit(self, pageContainer, fr=-1):
+ """ Returns the number of tabs that can fit in the visible area. """
+
+ pc = pageContainer
+
+ rect = pc.GetClientRect()
+ clientWidth = rect.width
+
+ # Empty results
+ vTabInfo = []
+ tabHeight = self.CalcTabHeight(pageContainer)
+
+ # The drawing starts from posx
+ posx = pc._pParent.GetPadding()
+
+ if fr < 0:
+ fr = pc._nFrom
+
+ for i in xrange(fr, len(pc._pagesInfoVec)):
+
+ vc8glitch = tabHeight + FNB_HEIGHT_SPACER
+ tabWidth = self.CalcTabWidth(pageContainer, i, tabHeight)
+
+ if posx + tabWidth + vc8glitch + self.GetButtonsAreaLength(pc) >= clientWidth:
+ break
+
+ # Add a result to the returned vector
+ tabRect = wx.Rect(posx, VERTICAL_BORDER_PADDING, tabWidth, tabHeight)
+ vTabInfo.append(tabRect)
+
+ # Advance posx
+ posx += tabWidth + FNB_HEIGHT_SPACER
+
+ return vTabInfo
+
+
+# ---------------------------------------------------------------------------- #
+# Class FlatNotebook
+# ---------------------------------------------------------------------------- #
+
+class FlatNotebook(wx.Panel):
+ """
+ Display one or more windows in a notebook.
+
+ B{Events}:
+ - B{EVT_FLATNOTEBOOK_PAGE_CHANGING}: sent when the active
+ page in the notebook is changing
+ - B{EVT_FLATNOTEBOOK_PAGE_CHANGED}: sent when the active
+ page in the notebook has changed
+ - B{EVT_FLATNOTEBOOK_PAGE_CLOSING}: sent when a page in the
+ notebook is closing
+ - B{EVT_FLATNOTEBOOK_PAGE_CLOSED}: sent when a page in the
+ notebook has been closed
+ - B{EVT_FLATNOTEBOOK_PAGE_CONTEXT_MENU}: sent when the user
+ clicks a tab in the notebook with the right mouse
+ button
+ """
+
+ def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
+ style=0, name="FlatNotebook"):
+ """
+ Default class constructor.
+
+ All the parameters are as in wxPython class construction, except the
+ 'style': this can be assigned to whatever combination of FNB_* styles.
+
+ """
+
+ self._bForceSelection = False
+ self._nPadding = 6
+ self._nFrom = 0
+ style |= wx.TAB_TRAVERSAL
+ self._pages = None
+ self._windows = []
+ self._popupWin = None
+
+ wx.Panel.__init__(self, parent, id, pos, size, style)
+
+ self._pages = PageContainer(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, style)
+
+ self.Bind(wx.EVT_NAVIGATION_KEY, self.OnNavigationKey)
+
+ self.Init()
+
+
+ def Init(self):
+ """ Initializes all the class attributes. """
+
+ self._pages._colorBorder = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)
+
+ self._mainSizer = wx.BoxSizer(wx.VERTICAL)
+ self.SetSizer(self._mainSizer)
+
+ # The child panels will inherit this bg color, so leave it at the default value
+ #self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_APPWORKSPACE))
+
+ # Set default page height
+ dc = wx.ClientDC(self)
+
+ 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)
+
+ height = dc.GetCharHeight()
+
+ tabHeight = height + FNB_HEIGHT_SPACER # We use 8 pixels as padding
+
+ if "__WXGTK__" in wx.PlatformInfo:
+ tabHeight += 6
+
+ self._pages.SetSizeHints(-1, tabHeight)
+ # Add the tab container to the sizer
+ self._mainSizer.Insert(0, self._pages, 0, wx.EXPAND)
+ self._mainSizer.Layout()
+
+ self._pages._nFrom = self._nFrom
+ self._pDropTarget = FNBDropTarget(self)
+ self.SetDropTarget(self._pDropTarget)
+
+
+ def SetActiveTabTextColour(self, textColour):
+ """ Sets the text colour for the active tab. """
+
+ self._pages._activeTextColor = textColour
+
+
+ def OnDropTarget(self, x, y, nTabPage, wnd_oldContainer):
+ """ Handles the drop action from a DND operation. """
+
+ return self._pages.OnDropTarget(x, y, nTabPage, wnd_oldContainer)
+
+
+ def GetPreviousSelection(self):
+ """ Returns the previous selection. """
+
+ return self._pages._iPreviousActivePage
+
+
+ def AddPage(self, page, text, select=True, imageId=-1):
+ """
+ Add a page to the L{FlatNotebook}.
+
+ @param page: Specifies the new page.
+ @param text: Specifies the text for the new page.
+ @param select: Specifies whether the page should be selected.
+ @param imageId: Specifies the optional image index for the new page.
+
+ Return value:
+ True if successful, False otherwise.
+ """
+
+ # sanity check
+ if not page:
+ return False
+
+ # reparent the window to us
+ page.Reparent(self)
+
+ # Add tab
+ bSelected = select or len(self._windows) == 0
+
+ if bSelected:
+
+ bSelected = False
+
+ # Check for selection and send events
+ oldSelection = self._pages._iActivePage
+ tabIdx = len(self._windows)
+
+ event = FlatNotebookEvent(wxEVT_FLATNOTEBOOK_PAGE_CHANGING, self.GetId())
+ event.SetSelection(tabIdx)
+ event.SetOldSelection(oldSelection)
+ event.SetEventObject(self)
+
+ if not self.GetEventHandler().ProcessEvent(event) or event.IsAllowed() or len(self._windows) == 0:
+ bSelected = True
+
+ curSel = self._pages.GetSelection()
+
+ if not self._pages.IsShown():
+ self._pages.Show()
+
+ self._pages.AddPage(text, bSelected, imageId)
+ self._windows.append(page)
+
+ self.Freeze()
+
+ # Check if a new selection was made
+ if bSelected:
+
+ if curSel >= 0:
+
+ # Remove the window from the main sizer
+ self._mainSizer.Detach(self._windows[curSel])
+ self._windows[curSel].Hide()
+
+ if self.GetWindowStyleFlag() & FNB_BOTTOM:
+
+ self._mainSizer.Insert(0, page, 1, wx.EXPAND)
+
+ else:
+
+ # We leave a space of 1 pixel around the window
+ self._mainSizer.Add(page, 1, wx.EXPAND)
+
+ # Fire a wxEVT_FLATNOTEBOOK_PAGE_CHANGED event
+ event.SetEventType(wxEVT_FLATNOTEBOOK_PAGE_CHANGED)
+ event.SetOldSelection(oldSelection)
+ self.GetEventHandler().ProcessEvent(event)
+
+ else:
+
+ # Hide the page
+ page.Hide()
+
+ self.Thaw()
+ self._mainSizer.Layout()
self.Refresh()
+ return True
+
- def InsertPage(self, indx, text, selected=True, imgindex=-1):
+ 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.
+ """
+
+ self._pages.SetImageList(imageList)
+
+
+ def GetImageList(self):
+ """ Returns the associated image list. """
+
+ return self._pages.GetImageList()
+
+
+ def InsertPage(self, indx, page, text, select=True, imageId=-1):
"""
Inserts a new page at the specified position.
- 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:
- 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()
+ def SetNonActiveTabTextColour(self, color):
+ """ Sets the non active tabs text colour. """
- if selection == -1:
- event.Skip()
- return
-
- if not self.IsTabVisible(selection):
- if selection == len(self._pagesInfoVec) - 1:
- if not self.CanFitToScreen(selection):
- event.Skip()
- return
- else:
- event.Skip()
- return
-
- self.DrawTabX(dc, self._pagesInfoVec[selection].GetXRect(), selection)
-
- event.Skip()
+ self._pages._nonActiveTextColor = color
- def OnMouseEnterWindow(self, event):
- """ Handles the wx.EVT_ENTER_WINDOW event for PageContainerBase. """
+ def SetTabAreaColour(self, color):
+ """ Sets the area behind the tabs colour. """
+
+ self._pages._tabAreaColor = color
- self._nLeftButtonStatus = FNB_BTN_NONE
- self._nXButtonStatus = FNB_BTN_NONE
- self._nRightButtonStatus = FNB_BTN_NONE
- self._nLeftClickZone = FNB_BTN_NONE
- event.Skip()
+ 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 SetPageImage(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:
+ @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.
- pSizer.Insert(0, pWindow, 1, wx.EXPAND)
-
- 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
+ self._nLeftClickZone, tabIdx = self.HitTest(event.GetPosition())
- def GetNumOfVisibleTabs(self):
- """ Returns the number of visible tabs. """
+ 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()
- count = 0
- for ii in xrange(self._nFrom, len(self._pagesInfoVec)):
- if self._pagesInfoVec[ii].GetPosition() == wx.Point(-1, -1):
- break
- count = count + 1
+ 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)
- return count
+ def OnLeftUp(self, event):
+ """ Handles the wx.EVT_LEFT_UP events for L{PageContainer}. """
- def GetEnabled(self, page):
- """ Returns whether a tab is enabled or not. """
+ # 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)
-# ---------------------------------------------------------------------------- #
-# Class FlatNotebook
-# Simple super class based on PageContainerBase
-# ---------------------------------------------------------------------------- #
+ # Thanks to Yiaanis AKA Mandrav
+ if self._iActivePage >= page:
+ self._iActivePage = self._iActivePage - 1
+ self._iPreviousActivePage = -1
-class FlatNotebook(FlatNotebookBase):
+ # 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
- def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
- style=0, name="FlatNotebook"):
- """
- Default class constructor.
+ # Refresh the tabs
+ if self._iActivePage >= 0:
+
+ book._bForceSelection = True
- It is better to use directly the StyledNotebook class (see below) and then
- assigning the style you wish instead of calling FlatNotebook.
- """
+ # 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())
- style |= wx.TAB_TRAVERSAL
-
- FlatNotebookBase.__init__(self, parent, id, pos, size, style, name)
- self._pages = self.CreatePageContainer()
+ book.SetSelection(self._iActivePage)
+ book._bForceSelection = False
+ # Fire a wxEVT_FLATNOTEBOOK_PAGE_CHANGED event
+ event.SetEventType(wxEVT_FLATNOTEBOOK_PAGE_CHANGED)
+ event.SetOldSelection(self._iPreviousActivePage)
+ self.GetParent().GetEventHandler().ProcessEvent(event)
+
+ if not self._pagesInfoVec:
+ # Erase the page container drawings
+ dc = wx.ClientDC(self)
+ dc.Clear()
+
- def 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. """
+
+ if self._nFrom < 0:
+ return -1
- tabHeight = height + FNB_HEIGHT_SPACER # We use 8 pixels
- # The drawing starts from posx
- posx = self._pParent.GetPadding()
+ 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. """
# 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
- 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
-
- 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)