Files
wxWidgets/wxPython/wx/lib/flatnotebook.py
Robin Dunn 6a64d5510c Give up on trying to fix all the buffered dc problems and just
check-in the FlatNotebook update from Andrea using self-buffering
instead of wx.BufferedPaintDC.


git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@41971 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
2006-10-12 08:35:24 +00:00

4581 lines
143 KiB
Python

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