+ # Incase we are drawing the selected tab, we draw the border of it as well
+ # but without the bottom (upper line incase of wxBOTTOM)
+ if tabIdx == pc.GetSelection():
+
+ borderPen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW))
+ dc.SetPen(borderPen)
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+ dc.DrawPolygon(tabPoints)
+
+ # Delete the bottom line (or the upper one, incase we use wxBOTTOM)
+ dc.SetPen(wx.WHITE_PEN)
+ dc.DrawLine(tabPoints[0].x, tabPoints[0].y, tabPoints[6].x, tabPoints[6].y)
+
+ self.FillVC8GradientColour(pc, dc, tabPoints, tabIdx == pc.GetSelection(), tabIdx)
+
+ # Draw a thin line to the right of the non-selected tab
+ if tabIdx != pc.GetSelection():
+
+ dc.SetPen(wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE)))
+ dc.DrawLine(tabPoints[4].x-1, tabPoints[4].y, tabPoints[5].x-1, tabPoints[5].y)
+ dc.DrawLine(tabPoints[5].x-1, tabPoints[5].y, tabPoints[6].x-1, tabPoints[6].y)
+
+ # Text drawing offset from the left border of the
+ # rectangle
+
+ # The width of the images are 16 pixels
+ vc8ShapeLen = tabHeight - VERTICAL_BORDER_PADDING - 2
+ if pc.TabHasImage(tabIdx):
+ textOffset = 2*pc._pParent.GetPadding() + 16 + vc8ShapeLen
+ else:
+ textOffset = pc._pParent.GetPadding() + vc8ShapeLen
+
+ # Draw the image for the tab if any
+ imageYCoord = (pc.HasFlag(FNB_BOTTOM) and [6] or [8])[0]
+
+ if pc.TabHasImage(tabIdx):
+
+ imageXOffset = textOffset - 16 - pc._pParent.GetPadding()
+ pc._ImageList.Draw(pc._pagesInfoVec[tabIdx].GetImageIndex(), dc,
+ posx + imageXOffset, imageYCoord,
+ wx.IMAGELIST_DRAW_TRANSPARENT, True)
+
+ boldFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
+
+ # if selected tab, draw text in bold
+ if tabIdx == pc.GetSelection():
+ boldFont.SetWeight(wx.FONTWEIGHT_BOLD)
+
+ dc.SetFont(boldFont)
+ dc.DrawText(pc.GetPageText(tabIdx), posx + textOffset, imageYCoord)
+
+ # draw 'x' on tab (if enabled)
+ if pc.HasFlag(FNB_X_ON_TAB) and tabIdx == pc.GetSelection():
+
+ textWidth, textHeight = dc.GetTextExtent(pc.GetPageText(tabIdx))
+ tabCloseButtonXCoord = posx + textOffset + textWidth + 1
+
+ # take a bitmap from the position of the 'x' button (the x on tab button)
+ # this bitmap will be used later to delete old buttons
+ tabCloseButtonYCoord = imageYCoord
+ x_rect = wx.Rect(tabCloseButtonXCoord, tabCloseButtonYCoord, 16, 16)
+ self._tabXBgBmp = self._GetBitmap(dc, x_rect, self._tabXBgBmp)
+ # Draw the tab
+ self.DrawTabX(pc, dc, x_rect, tabIdx, btnStatus)
+
+
+ def FillVC8GradientColour(self, pageContainer, dc, tabPoints, bSelectedTab, tabIdx):
+ """ Fills a tab with a gradient shading. """
+
+ # calculate gradient coefficients
+ pc = pageContainer
+
+ if self._first:
+ self._first = False
+ pc._colorTo = LightColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE), 0)
+ pc._colorFrom = LightColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE), 60)
+
+ col2 = pc._pParent.GetGradientColourTo()
+ col1 = pc._pParent.GetGradientColourFrom()
+
+ # If colorful tabs style is set, override the tab color
+ if pc.HasFlag(FNB_COLORFUL_TABS):
+
+ if not pc._pagesInfoVec[tabIdx].GetColour():
+
+ # First time, generate color, and keep it in the vector
+ tabColor = RandomColour()
+ pc._pagesInfoVec[tabIdx].SetColour(tabColor)
+
+ if pc.HasFlag(FNB_BOTTOM):
+
+ col2 = LightColour(pc._pagesInfoVec[tabIdx].GetColour(), 50)
+ col1 = LightColour(pc._pagesInfoVec[tabIdx].GetColour(), 80)
+
+ else:
+
+ col1 = LightColour(pc._pagesInfoVec[tabIdx].GetColour(), 50)
+ col2 = LightColour(pc._pagesInfoVec[tabIdx].GetColour(), 80)
+
+ size = abs(tabPoints[2].y - tabPoints[0].y) - 1
+
+ rf, gf, bf = 0, 0, 0
+ rstep = float(col2.Red() - col1.Red())/float(size)
+ gstep = float(col2.Green() - col1.Green())/float(size)
+ bstep = float(col2.Blue() - col1.Blue())/float(size)
+
+ y = tabPoints[0].y
+
+ # If we are drawing the selected tab, we need also to draw a line
+ # from 0.tabPoints[0].x and tabPoints[6].x . end, we achieve this
+ # by drawing the rectangle with transparent brush
+ # the line under the selected tab will be deleted by the drwaing loop
+ if bSelectedTab:
+ self.DrawTabsLine(pc, dc)
+
+ while 1:
+
+ if pc.HasFlag(FNB_BOTTOM):
+
+ if y > tabPoints[0].y + size:
+ break
+
+ else:
+
+ if y < tabPoints[0].y - size:
+ break
+
+ currCol = wx.Colour(col1.Red() + rf, col1.Green() + gf, col1.Blue() + bf)
+
+ dc.SetPen((bSelectedTab and [wx.Pen(pc._activeTabColor)] or [wx.Pen(currCol)])[0])
+ startX = self.GetStartX(tabPoints, y, pc.GetParent().GetWindowStyleFlag())
+ endX = self.GetEndX(tabPoints, y, pc.GetParent().GetWindowStyleFlag())
+ dc.DrawLine(startX, y, endX, y)
+
+ # Draw the border using the 'edge' point
+ dc.SetPen(wx.Pen((bSelectedTab and [wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)] or [pc._colorBorder])[0]))
+
+ dc.DrawPoint(startX, y)
+ dc.DrawPoint(endX, y)
+
+ # Progress the color
+ rf += rstep
+ gf += gstep
+ bf += bstep
+
+ if pc.HasFlag(FNB_BOTTOM):
+ y = y + 1
+ else:
+ y = y - 1
+
+
+ def GetStartX(self, tabPoints, y, style):
+ """ Returns the x start position of a tab. """
+
+ x1, x2, y1, y2 = 0.0, 0.0, 0.0, 0.0
+
+ # We check the 3 points to the left
+
+ bBottomStyle = (style & FNB_BOTTOM and [True] or [False])[0]
+ match = False
+
+ if bBottomStyle:
+
+ for i in xrange(3):
+
+ if y >= tabPoints[i].y and y < tabPoints[i+1].y:
+
+ x1 = tabPoints[i].x
+ x2 = tabPoints[i+1].x
+ y1 = tabPoints[i].y
+ y2 = tabPoints[i+1].y
+ match = True
+ break
+
+ else:
+
+ for i in xrange(3):
+
+ if y <= tabPoints[i].y and y > tabPoints[i+1].y:
+
+ x1 = tabPoints[i].x
+ x2 = tabPoints[i+1].x
+ y1 = tabPoints[i].y
+ y2 = tabPoints[i+1].y
+ match = True
+ break
+
+ if not match:
+ return tabPoints[2].x
+
+ # According to the equation y = ax + b => x = (y-b)/a
+ # We know the first 2 points
+
+ if x2 == x1:
+ return x2
+ else:
+ a = (y2 - y1)/(x2 - x1)
+
+ b = y1 - ((y2 - y1)/(x2 - x1))*x1
+
+ if a == 0:
+ return int(x1)
+
+ x = (y - b)/a
+
+ return int(x)
+
+
+ def GetEndX(self, tabPoints, y, style):
+ """ Returns the x end position of a tab. """
+
+ x1, x2, y1, y2 = 0.0, 0.0, 0.0, 0.0
+
+ # We check the 3 points to the left
+ bBottomStyle = (style & FNB_BOTTOM and [True] or [False])[0]
+ match = False
+
+ if bBottomStyle:
+
+ for i in xrange(7, 3, -1):
+
+ if y >= tabPoints[i].y and y < tabPoints[i-1].y:
+
+ x1 = tabPoints[i].x
+ x2 = tabPoints[i-1].x
+ y1 = tabPoints[i].y
+ y2 = tabPoints[i-1].y
+ match = True
+ break
+
+ else:
+
+ for i in xrange(7, 3, -1):
+
+ if y <= tabPoints[i].y and y > tabPoints[i-1].y:
+
+ x1 = tabPoints[i].x
+ x2 = tabPoints[i-1].x
+ y1 = tabPoints[i].y
+ y2 = tabPoints[i-1].y
+ match = True
+ break
+
+ if not match:
+ return tabPoints[3].x
+
+ # 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 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. """