"""
auibook contains a notebook control which implements many features common in
applications with dockable panes. Specifically, AuiNotebook implements functionality
which allows the user to rearrange tab order via drag-and-drop, split the tab window
into many different splitter configurations, and toggle through different themes to
customize the control's look and feel.

An effort has been made to try to maintain an API as similar to that of wx.Notebook.

The default theme that is used is AuiDefaultTabArt, which provides a modern, glossy
look and feel. The theme can be changed by calling AuiNotebook.SetArtProvider.
"""

__author__ = "Andrea Gavana <andrea.gavana@gmail.com>"
__date__ = "31 March 2009"


import wx
import types

import framemanager
import tabart as TA

from aui_utilities import LightColour, MakeDisabledBitmap, TabDragImage
from aui_constants import *

# AuiNotebook events
wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE = wx.NewEventType()
wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSED = wx.NewEventType()
wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGED = wx.NewEventType()
wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING = wx.NewEventType()
wxEVT_COMMAND_AUINOTEBOOK_BUTTON = wx.NewEventType()
wxEVT_COMMAND_AUINOTEBOOK_BEGIN_DRAG = wx.NewEventType()
wxEVT_COMMAND_AUINOTEBOOK_END_DRAG = wx.NewEventType()
wxEVT_COMMAND_AUINOTEBOOK_DRAG_MOTION = wx.NewEventType()
wxEVT_COMMAND_AUINOTEBOOK_ALLOW_DND = wx.NewEventType()
wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE = wx.NewEventType()
wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_DOWN = wx.NewEventType()
wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_UP = wx.NewEventType()
wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_DOWN = wx.NewEventType()
wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP = wx.NewEventType()
wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK = wx.NewEventType()
wxEVT_COMMAND_AUINOTEBOOK_TAB_DCLICK = wx.NewEventType()

# Define a new event for a drag cancelled
wxEVT_COMMAND_AUINOTEBOOK_CANCEL_DRAG = wx.NewEventType()

EVT_AUINOTEBOOK_PAGE_CLOSE = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE, 1)
EVT_AUINOTEBOOK_PAGE_CLOSED = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSED, 1)
EVT_AUINOTEBOOK_PAGE_CHANGED = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGED, 1)
EVT_AUINOTEBOOK_PAGE_CHANGING = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, 1)
EVT_AUINOTEBOOK_BUTTON = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BUTTON, 1)
EVT_AUINOTEBOOK_BEGIN_DRAG = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BEGIN_DRAG, 1)
EVT_AUINOTEBOOK_END_DRAG = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_END_DRAG, 1)
EVT_AUINOTEBOOK_DRAG_MOTION = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_DRAG_MOTION, 1)
EVT_AUINOTEBOOK_ALLOW_DND = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_ALLOW_DND, 1)
EVT_AUINOTEBOOK_DRAG_DONE = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, 1)
EVT_AUINOTEBOOK_TAB_MIDDLE_DOWN = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_DOWN, 1)
EVT_AUINOTEBOOK_TAB_MIDDLE_UP = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_UP, 1)
EVT_AUINOTEBOOK_TAB_RIGHT_DOWN = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_DOWN, 1)
EVT_AUINOTEBOOK_TAB_RIGHT_UP = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP, 1)
EVT_AUINOTEBOOK_BG_DCLICK = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK, 1)
EVT_AUINOTEBOOK_CANCEL_DRAG = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_CANCEL_DRAG, 1)
EVT_AUINOTEBOOK_TAB_DCLICK = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_DCLICK, 1)


# ----------------------------------------------------------------------

class AuiNotebookPage(object):
    """
    A simple class which holds information about tab captions, bitmaps and
    colours.
    """

    def __init__(self):
        """
        Default class constructor. Used internally, do not call it in your code!
        """

        self.window = None              # page's associated window
        self.caption = ""               # caption displayed on the tab
        self.bitmap = wx.NullBitmap     # tab's bitmap
        self.dis_bitmap = wx.NullBitmap # tab's disabled bitmap
        self.rect = wx.Rect()           # tab's hit rectangle
        self.active = False             # True if the page is currently active
        self.enabled = True             # True if the page is currently enabled
        self.text_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNTEXT)


# ----------------------------------------------------------------------

class AuiTabContainerButton(object):
    """
    A simple class which holds information about tab buttons and their state.
    """

    def __init__(self):
        """
        Default class constructor. Used internally, do not call it in your code!
        """

        self.id = -1                                      # button's id
        self.cur_state = AUI_BUTTON_STATE_NORMAL          # current state (normal, hover, pressed, etc.)
        self.location = wx.LEFT                           # buttons location (wxLEFT, wxRIGHT, or wxCENTER)
        self.bitmap = wx.NullBitmap                       # button's hover bitmap
        self.dis_bitmap = wx.NullBitmap                   # button's disabled bitmap
        self.rect = wx.Rect()                             # button's hit rectangle


# ----------------------------------------------------------------------

class CommandNotebookEvent(wx.PyCommandEvent):
    """ A specialized command event class for events sent by L{AuiNotebook}. """
    
    def __init__(self, command_type=None, win_id=0):
        """
        Default class constructor.

        :param `command_type`: the event kind or an instance of L{wx.PyCommandEvent}.
        :param `win_id`: the window identification number.
        """

        if type(command_type) == types.IntType:    
            wx.PyCommandEvent.__init__(self, command_type, win_id)
        else:
            wx.PyCommandEvent.__init__(self, command_type.GetEventType(), command_type.GetId())
            
        self.old_selection = -1
        self.selection = -1
        self.drag_source = None
        self.dispatched = 0


    def Clone(self):
        """
        Returns a copy of the event.

        Any event that is posted to the wxPython event system for later action (via
        L{wx.EvtHandler.AddPendingEvent} or L{wx.PostEvent}) must implement this method.
        All wxPython events fully implement this method, but any derived events
        implemented by the user should also implement this method just in case they
        (or some event derived from them) are ever posted.

        All wxPython events implement a copy constructor, so the easiest way of
        implementing the Clone function is to implement a copy constructor for a new
        event (call it MyEvent) and then define the Clone function like this::

            def Clone(self):
                return MyEvent(self)

        """
        
        return CommandNotebookEvent(self)


    def SetSelection(self, s):
        """
        Sets the selection member variable.

        :param `s`: the new selection.
        """

        self.selection = s
        self._commandInt = s

        
    def GetSelection(self):
        """ Returns the currently selected page, or -1 if none was selected. """

        return self.selection


    def SetOldSelection(self, s):
        """
        Sets the id of the page selected before the change.

        :param `s`: the old selection.
        """
        
        self.old_selection = s

        
    def GetOldSelection(self):
        """
        Returns the page that was selected before the change, or -1 if none was
        selected.
        """

        return self.old_selection
    

    def SetDragSource(self, s):
        """
        Sets the drag and drop source.

        :param `s`: the drag source.
        """

        self.drag_source = s

        
    def GetDragSource(self):
        """ Returns the drag and drop source. """

        return self.drag_source


    def SetDispatched(self, b):
        """
        Sets the event as dispatched (used for automatic AuiNotebooks).

        :param `b`: whether the event was dispatched or not.
        """

        self.dispatched = b

        
    def GetDispatched(self):
        """ Returns whether the event was dispatched (used for automatic AuiNotebooks). """

        return self.dispatched
    

# ----------------------------------------------------------------------

class AuiNotebookEvent(CommandNotebookEvent):
    """ A specialized command event class for events sent by L{AuiNotebook}. """
    
    def __init__(self, command_type=None, win_id=0):
        """
        Default class constructor.

        :param `command_type`: the event kind or an instance of L{wx.PyCommandEvent}.
        :param `win_id`: the window identification number.
        """

        CommandNotebookEvent.__init__(self, command_type, win_id)

        if type(command_type) == types.IntType:
            self.notify = wx.NotifyEvent(command_type, win_id)
        else:
            self.notify = wx.NotifyEvent(command_type.GetEventType(), command_type.GetId())


    def Clone(self):
        """
        Returns a copy of the event.

        Any event that is posted to the wxPython event system for later action (via
        L{wx.EvtHandler.AddPendingEvent} or L{wx.PostEvent}) must implement this method.
        All wxPython events fully implement this method, but any derived events
        implemented by the user should also implement this method just in case they
        (or some event derived from them) are ever posted.

        All wxPython events implement a copy constructor, so the easiest way of
        implementing the Clone function is to implement a copy constructor for a new
        event (call it MyEvent) and then define the Clone function like this::

            def Clone(self):
                return MyEvent(self)

        """
        
        return AuiNotebookEvent(self)

        
    def GetNotifyEvent(self):
        """ Returns the actual L{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()


# ---------------------------------------------------------------------------- #
# 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, icon=None):
        """
        Default class constructor. Used internally.

        :param `parent`: the TabNavigatorWindow parent;
        :param `icon`: the TabNavigatorWindow icon.
        """

        wx.Dialog.__init__(self, parent, wx.ID_ANY, "", style=0)

        self._selectedItem = -1
        self._indexMap = []
        
        if icon is None:
            self._bmp = Mondrian.GetBitmap()
        else:
            self._bmp = icon

        if self._bmp.GetSize() != (16, 16):
            img = self._bmp.ConvertToImage()
            img.Rescale(16, 16, wx.IMAGE_QUALITY_HIGH)
            self._bmp = wx.BitmapFromImage(img)
            
        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(1,1))
        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()

        # Set focus on the list box to avoid having to click on it to change
        # the tab selection under GTK.
        self._listBox.SetFocus()


    def OnKeyUp(self, event):
        """
        Handles the wx.EVT_KEY_UP for the L{TabNavigatorWindow}.

        :param `event`: a L{wx.KeyEvent} event to be processed.
        """
        
        if event.GetKeyCode() == wx.WXK_CONTROL:
            self.CloseDialog()


    def OnNavigationKey(self, event):
        """
        Handles the wx.EVT_NAVIGATION_KEY for the L{TabNavigatorWindow}.

        :param `event`: a L{wx.NavigationKeyEvent} event to be processed.
        """

        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.

        :param `book`: the actual L{AuiNotebook}.
        """

        selection = book.GetSelection()
        count = book.GetPageCount()
        
        self._listBox.Append(book.GetPageText(selection))
        self._indexMap.append(selection)
        
        for c in xrange(count):
        
            # Skip selected page
            if c == selection:
                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}.

        :param `event`: a L{wx.ListEvent} event to be processed.
        """

        self.CloseDialog()


    def CloseDialog(self):
        """ Closes the L{TabNavigatorWindow} dialog, setting selection in L{AuiNotebook}. """

        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.

        :param `event`: a L{wx.PaintEvent} event to be processed.
        """

        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)
        mem_dc.GradientFillLinear(rect, startColour, endColour, wx.SOUTH)

        # 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.
        This is intentionally empty, to reduce flicker.

        :param `event`: a L{wx.EraseEvent} event to be processed.        
        """

        pass


# ----------------------------------------------------------------------
# -- AuiTabContainer class implementation --

class AuiTabContainer(object):
    """
    AuiTabContainer is a class which contains information about each
    tab. It also can render an entire tab control to a specified DC.
    It's not a window class itself, because this code will be used by
    the AuiManager, where it is disadvantageous to have separate
    windows for each tab control in the case of "docked tabs".

    A derived class, AuiTabCtrl, is an actual wx.Window-derived window
    which can be used as a tab control in the normal sense.
    """

    def __init__(self):
        """
        Default class constructor. Used internally, do not call it in your code!
        """

        self._tab_offset = 0
        self._flags = 0
        self._art = TA.AuiDefaultTabArt()

        self._buttons = []
        self._pages = []
        self._tab_close_buttons = []
        
        self._rect = wx.Rect()
        
        self.AddButton(AUI_BUTTON_LEFT, wx.LEFT)
        self.AddButton(AUI_BUTTON_RIGHT, wx.RIGHT)
        self.AddButton(AUI_BUTTON_WINDOWLIST, wx.RIGHT)
        self.AddButton(AUI_BUTTON_CLOSE, wx.RIGHT)


    def SetArtProvider(self, art):
        """
        Instructs L{AuiTabContainer} to use art provider specified by parameter `art`
        for all drawing calls. This allows plugable look-and-feel features. The previous
        art provider object, if any, will be deleted by L{AuiTabContainer}.

        :param `art`: an art provider.
        """

        del self._art
        self._art = art

        if self._art:
            self._art.SetFlags(self._flags)
    

    def GetArtProvider(self):
        """ Returns the current art provider being used. """

        return self._art


    def SetFlags(self, flags):
        """
        Sets the tab art flags.

        :param `flags`: a combination of the following values:

        ==================================== ==================================
        Flag name                            Description
        ==================================== ==================================
        ``AUI_NB_TOP``                       With this style, tabs are drawn along the top of the notebook
        ``AUI_NB_LEFT``                      With this style, tabs are drawn along the left of the notebook. Not implemented yet.
        ``AUI_NB_RIGHT``                     With this style, tabs are drawn along the right of the notebook. Not implemented yet.
        ``AUI_NB_BOTTOM``                    With this style, tabs are drawn along the bottom of the notebook.
        ``AUI_NB_TAB_SPLIT``                 Allows the tab control to be split by dragging a tab
        ``AUI_NB_TAB_MOVE``                  Allows a tab to be moved horizontally by dragging
        ``AUI_NB_TAB_EXTERNAL_MOVE``         Allows a tab to be moved to another tab control
        ``AUI_NB_TAB_FIXED_WIDTH``           With this style, all tabs have the same width
        ``AUI_NB_SCROLL_BUTTONS``            With this style, left and right scroll buttons are displayed
        ``AUI_NB_WINDOWLIST_BUTTON``         With this style, a drop-down list of windows is available
        ``AUI_NB_CLOSE_BUTTON``              With this style, a close button is available on the tab bar
        ``AUI_NB_CLOSE_ON_ACTIVE_TAB``       With this style, a close button is available on the active tab
        ``AUI_NB_CLOSE_ON_ALL_TABS``         With this style, a close button is available on all tabs
        ``AUI_NB_MIDDLE_CLICK_CLOSE``        Allows to close AuiNotebook tabs by mouse middle button click
        ``AUI_NB_SUB_NOTEBOOK``              This style is used by AuiManager to create automatic AuiNotebooks
        ``AUI_NB_HIDE_ON_SINGLE_TAB``        Hides the tab window if only one tab is present
        ``AUI_NB_SMART_TABS``                Use Smart Tabbing, like Alt+Tab on Windows
        ``AUI_NB_USE_IMAGES_DROPDOWN``       Uses images on dropdown window list menu instead of check items
        ``AUI_NB_CLOSE_ON_TAB_LEFT``         Draws the tab close button on the left instead of on the right (a la Camino browser)
        ``AUI_NB_TAB_FLOAT``                 Allows the floating of single tabs. Known limitation: when the notebook is more or less full screen, tabs cannot be dragged far enough outside of the notebook to become floating pages
        ``AUI_NB_DRAW_DND_TAB``              Draws an image representation of a tab while dragging (on by default)
        ``AUI_NB_SASH_DCLICK_UNSPLIT``       Unsplit a splitted AuiNotebook when double-clicking on a sash.
        ==================================== ==================================
        
        """
        
        self._flags = flags

        # check for new close button settings
        self.RemoveButton(AUI_BUTTON_LEFT)
        self.RemoveButton(AUI_BUTTON_RIGHT)
        self.RemoveButton(AUI_BUTTON_WINDOWLIST)
        self.RemoveButton(AUI_BUTTON_CLOSE)

        if flags & AUI_NB_SCROLL_BUTTONS:
            self.AddButton(AUI_BUTTON_LEFT, wx.LEFT)
            self.AddButton(AUI_BUTTON_RIGHT, wx.RIGHT)
        
        if flags & AUI_NB_WINDOWLIST_BUTTON:
            self.AddButton(AUI_BUTTON_WINDOWLIST, wx.RIGHT)
        
        if flags & AUI_NB_CLOSE_BUTTON:
            self.AddButton(AUI_BUTTON_CLOSE, wx.RIGHT)

        if self._art:
            self._art.SetFlags(self._flags)
        

    def GetFlags(self):
        """
        Returns the tab art flags.

        See L{SetFlags} for a list of possible return values.
        """

        return self._flags


    def SetNormalFont(self, font):
        """
        Sets the normal font for drawing tab labels.

        :param `font`: a wx.Font object.
        """

        self._art.SetNormalFont(font)


    def SetSelectedFont(self, font):
        """
        Sets the selected tab font for drawing tab labels.

        :param `font`: a wx.Font object.
        """

        self._art.SetSelectedFont(font)


    def SetMeasuringFont(self, font):
        """
        Sets the font for calculating text measurements.

        :param `font`: a wx.Font object.
        """

        self._art.SetMeasuringFont(font)


    def SetTabRect(self, rect):
        """
        Sets the tab area rectangle.

        :param `rect`: an instance of wx.Rect, specifying the available area for L{AuiTabContainer}.
        """

        self._rect = rect

        if self._art:
            self._art.SetSizingInfo(rect.GetSize(), len(self._pages))


    def AddPage(self, page, info):
        """
        Adds a page to the tab control.

        :param `page`: the window associated with this tab;
        :param `info`: an instance of L{AuiNotebookPage}.
        """

        page_info = info
        page_info.window = page

        self._pages.append(page_info)

        # let the art provider know how many pages we have
        if self._art:
            self._art.SetSizingInfo(self._rect.GetSize(), len(self._pages))
        
        return True


    def InsertPage(self, page, info, idx):
        """
        Inserts a page in the tab control in the position specified by `idx`.

        :param `page`: the window associated with this tab;
        :param `info`: an instance of L{AuiNotebookPage};
        :param `idx`: the page insertion index.
        """
        
        page_info = info
        page_info.window = page

        if idx >= len(self._pages):
            self._pages.append(page_info)
        else:
            self._pages.insert(idx, page_info)

        # let the art provider know how many pages we have
        if self._art:
            self._art.SetSizingInfo(self._rect.GetSize(), len(self._pages))
        
        return True
    

    def MovePage(self, page, new_idx):
        """
        Moves a page in a new position specified by `new_idx`.

        :param `page`: the window associated with this tab;
        :param `new_idx`: the new page position.
        """
        
        idx = self.GetIdxFromWindow(page)
        if idx == -1:
            return False

        # get page entry, make a copy of it
        p = self.GetPage(idx)

        # remove old page entry
        self.RemovePage(page)

        # insert page where it should be
        self.InsertPage(page, p, new_idx)

        return True


    def RemovePage(self, wnd):
        """
        Removes a page from the tab control.

        :param `wnd`: an instance of wx.Window, a window associated with this tab.
        """

        for page in self._pages:
            if page.window == wnd:
                self._pages.remove(page)
                
                # let the art provider know how many pages we have
                if self._art:
                    self._art.SetSizingInfo(self._rect.GetSize(), len(self._pages))

                return True
            
        return False


    def SetActivePage(self, wndOrInt):
        """
        Sets the L{AuiTabContainer} active page.

        :param `wndOrInt`: an instance of wx.Window or an integer specifying a tab index.
        """

        if type(wndOrInt) == types.IntType:

            if wndOrInt >= len(self._pages):
                return False

            wnd = self._pages[wndOrInt].window

        else:
            wnd = wndOrInt
            
        found = False

        for indx, page in enumerate(self._pages):
            if page.window == wnd:
                page.active = True
                found = True
            else:
                page.active = False

        return found


    def SetNoneActive(self):
        """ Sets all the tabs as incative (non-selected). """

        for page in self._pages:        
            page.active = False
    

    def GetActivePage(self):
        """ Returns the current selected tab or wx.NOT_FOUND if none is selected. """

        for indx, page in enumerate(self._pages):
            if page.active:
                return indx
    
        return wx.NOT_FOUND


    def GetWindowFromIdx(self, idx):
        """
        Returns the window associated with the tab with index `idx`.

        :param `idx`: the tab index.
        """

        if idx >= len(self._pages):
            return None

        return self._pages[idx].window


    def GetIdxFromWindow(self, wnd):
        """
        Returns the tab index based on the window `wnd` associated with it.

        :param `wnd`: an instance of wx.Window.
        """
        
        for indx, page in enumerate(self._pages):
            if page.window == wnd:
                return indx

        return wx.NOT_FOUND


    def GetPage(self, idx):
        """
        Returns the page specified by the given index.

        :param `idx`: the tab index.
        """

        if idx < 0 or idx >= len(self._pages):
            raise Exception("Invalid Page index")

        return self._pages[idx]


    def GetPages(self):
        """ Returns a list of all the pages in this L{AuiTabContainer}. """

        return self._pages


    def GetPageCount(self):
        """ Returns the number of pages in the L{AuiTabContainer}. """

        return len(self._pages)


    def GetEnabled(self, idx):
        """
        Returns whether a tab is enabled or not.

        :param `idx`: the tab index.
        """
        
        if idx < 0 or idx >= len(self._pages):
            raise Exception("Invalid Page index")

        return self._pages[idx].enabled


    def EnableTab(self, idx, enable=True):
        """
        Enables/disables a tab in the L{AuiTabContainer}.

        :param `idx`: the tab index;
        :param `enable`: True to enable a tab, False to disable it.
        """
        
        if idx < 0 or idx >= len(self._pages):
            raise Exception("Invalid Page index")

        self._pages[idx].enabled = enable
        wnd = self.GetWindowFromIdx(idx)
        wnd.Enable(enable)

                
    def AddButton(self, id, location, normal_bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap):
        """
        Adds a button in the tab area.

        :param `id`: the button identifier. This can be one of the following:

        ==============================  =================================
        Button Identifier               Description
        ==============================  =================================
        ``AUI_BUTTON_CLOSE``            Shows a close button on the tab area
        ``AUI_BUTTON_WINDOWLIST``       Shows a window list button on the tab area
        ``AUI_BUTTON_LEFT``             Shows a left button on the tab area
        ``AUI_BUTTON_RIGHT``            Shows a right button on the tab area
        ==============================  =================================        

        :param `location`: the button location. Can be ``wx.LEFT`` or ``wx.RIGHT``;
        :param `normal_bitmap`: the bitmap for an enabled tab;
        :param `disabled_bitmap`: the bitmap for a disabled tab.
        """

        button = AuiTabContainerButton()
        button.id = id
        button.bitmap = normal_bitmap
        button.dis_bitmap = disabled_bitmap
        button.location = location
        button.cur_state = AUI_BUTTON_STATE_NORMAL

        self._buttons.append(button)


    def RemoveButton(self, id):
        """
        Removes a button from the tab area.

        :param `id`: the button identifier. See L{AddButton} for a list of button identifiers.
        """
        
        for button in self._buttons:
            if button.id == id:
                self._buttons.remove(button)
                return


    def GetTabOffset(self):
        """ Returns the tab offset. """

        return self._tab_offset


    def SetTabOffset(self, offset):
        """
        Sets the tab offset.

        :param `offset`: the tab offset.
        """
        
        self._tab_offset = offset


    def Render(self, raw_dc, wnd):
        """
        Render() renders the tab catalog to the specified DC.
        It is a virtual function and can be overridden to
        provide custom drawing capabilities.

        :param `raw_dc`: a L{wx.DC} device context;
        :param `wnd`: an instance of wx.Window derived window.
        """

        if not raw_dc or not raw_dc.IsOk():
            return

        dc = wx.MemoryDC()

        # use the same layout direction as the window DC uses to ensure that the
        # text is rendered correctly
        dc.SetLayoutDirection(raw_dc.GetLayoutDirection())

        page_count = len(self._pages)
        button_count = len(self._buttons)

        # create off-screen bitmap
        bmp = wx.EmptyBitmap(self._rect.GetWidth(), self._rect.GetHeight())
        dc.SelectObject(bmp)

        if not dc.IsOk():
            return
            
        # find out if size of tabs is larger than can be
        # afforded on screen
        total_width = visible_width = 0
        
        for i in xrange(page_count):
            page = self._pages[i]

            # determine if a close button is on this tab
            close_button = False
            if self._flags & AUI_NB_CLOSE_ON_ALL_TABS or \
               (self._flags & AUI_NB_CLOSE_ON_ACTIVE_TAB and page.active):
            
                close_button = True
            
            size, x_extent = self._art.GetTabSize(dc, wnd, page.caption, page.bitmap, page.active,
                                                  (close_button and [AUI_BUTTON_STATE_NORMAL] or \
                                                   [AUI_BUTTON_STATE_HIDDEN])[0])

            if i+1 < page_count:
                total_width += x_extent
            else:
                total_width += size[0]

            if i >= self._tab_offset:            
                if i+1 < page_count:
                    visible_width += x_extent
                else:
                    visible_width += size[0]

        if total_width > self._rect.GetWidth() or self._tab_offset != 0:
        
            # show left/right buttons
            for button in self._buttons:
                if button.id == AUI_BUTTON_LEFT or \
                   button.id == AUI_BUTTON_RIGHT:
                
                    button.cur_state &= ~AUI_BUTTON_STATE_HIDDEN
                
        else:
        
            # hide left/right buttons
            for button in self._buttons:
                if button.id == AUI_BUTTON_LEFT or \
                   button.id == AUI_BUTTON_RIGHT:
                    
                    button.cur_state |= AUI_BUTTON_STATE_HIDDEN

        # determine whether left button should be enabled
        for button in self._buttons:
            if button.id == AUI_BUTTON_LEFT:
                if self._tab_offset == 0:
                    button.cur_state |= AUI_BUTTON_STATE_DISABLED
                else:
                    button.cur_state &= ~AUI_BUTTON_STATE_DISABLED
            
            if button.id == AUI_BUTTON_RIGHT:
                if visible_width < self._rect.GetWidth() - 16*button_count:
                    button.cur_state |= AUI_BUTTON_STATE_DISABLED
                else:
                    button.cur_state &= ~AUI_BUTTON_STATE_DISABLED
            
        # draw background
        self._art.DrawBackground(dc, wnd, self._rect)

        # draw buttons
        left_buttons_width = 0
        right_buttons_width = 0

        # draw the buttons on the right side
        offset = self._rect.x + self._rect.width
        
        for i in xrange(button_count):        
            button = self._buttons[button_count - i - 1]

            if button.location != wx.RIGHT:
                continue
            if button.cur_state & AUI_BUTTON_STATE_HIDDEN:
                continue

            button_rect = wx.Rect(*self._rect)
            button_rect.SetY(1)
            button_rect.SetWidth(offset)

            button.rect = self._art.DrawButton(dc, wnd, button_rect, button, wx.RIGHT)

            offset -= button.rect.GetWidth()
            right_buttons_width += button.rect.GetWidth()
        
        offset = 0

        # draw the buttons on the left side
        for i in xrange(button_count):        
            button = self._buttons[button_count - i - 1]

            if button.location != wx.LEFT:
                continue
            if button.cur_state & AUI_BUTTON_STATE_HIDDEN:
                continue

            button_rect = wx.Rect(offset, 1, 1000, self._rect.height)

            button.rect = self._art.DrawButton(dc, wnd, button_rect, button, wx.LEFT)

            offset += button.rect.GetWidth()
            left_buttons_width += button.rect.GetWidth()
        
        offset = left_buttons_width

        if offset == 0:
            offset += self._art.GetIndentSize()

        # prepare the tab-close-button array
        # make sure tab button entries which aren't used are marked as hidden
        for i in xrange(page_count, len(self._tab_close_buttons)):
            self._tab_close_buttons[i].cur_state = AUI_BUTTON_STATE_HIDDEN

        # make sure there are enough tab button entries to accommodate all tabs
        while len(self._tab_close_buttons) < page_count:
            tempbtn = AuiTabContainerButton()
            tempbtn.id = AUI_BUTTON_CLOSE
            tempbtn.location = wx.CENTER
            tempbtn.cur_state = AUI_BUTTON_STATE_HIDDEN
            self._tab_close_buttons.append(tempbtn)

        # buttons before the tab offset must be set to hidden
        for i in xrange(self._tab_offset):
            self._tab_close_buttons[i].cur_state = AUI_BUTTON_STATE_HIDDEN
        
        # draw the tabs
        active = 999
        active_offset = 0
        
        rect = wx.Rect(*self._rect)
        rect.y = 0
        rect.height = self._rect.height

        for i in xrange(self._tab_offset, page_count):
        
            page = self._pages[i]
            tab_button = self._tab_close_buttons[i]

            # determine if a close button is on this tab
            if self._flags & AUI_NB_CLOSE_ON_ALL_TABS or \
               (self._flags & AUI_NB_CLOSE_ON_ACTIVE_TAB and page.active):
            
                if tab_button.cur_state == AUI_BUTTON_STATE_HIDDEN:
                
                    tab_button.id = AUI_BUTTON_CLOSE
                    tab_button.cur_state = AUI_BUTTON_STATE_NORMAL
                    tab_button.location = wx.CENTER
                
            else:
            
                tab_button.cur_state = AUI_BUTTON_STATE_HIDDEN
            
            rect.x = offset
            rect.width = self._rect.width - right_buttons_width - offset - 2

            if rect.width <= 0:
                break

            page.rect, tab_button.rect, x_extent = self._art.DrawTab(dc, wnd, page, rect, tab_button.cur_state)

            if page.active:
                active = i
                active_offset = offset
                active_rect = wx.Rect(*rect)

            offset += x_extent
        
        # make sure to deactivate buttons which are off the screen to the right
        for j in xrange(i+1, len(self._tab_close_buttons)):
            self._tab_close_buttons[j].cur_state = AUI_BUTTON_STATE_HIDDEN
        
        # draw the active tab again so it stands in the foreground
        if active >= self._tab_offset and active < len(self._pages):
        
            page = self._pages[active]
            tab_button = self._tab_close_buttons[active]

            rect.x = active_offset
            dummy = self._art.DrawTab(dc, wnd, page, active_rect, tab_button.cur_state)

        raw_dc.Blit(self._rect.x, self._rect.y, self._rect.GetWidth(), self._rect.GetHeight(), dc, 0, 0)


    def IsTabVisible(self, tabPage, tabOffset, dc, wnd):
        """
        Returns whether a tab is visible or not.

        :param `tabPage`: the tab index;
        :param `tabOffset`: the tab offset;
        :param `dc`: a L{wx.DC} device context;
        :param `wnd`: an instance of wx.Window derived window.
        """
        
        if not dc or not dc.IsOk():
            return False

        page_count = len(self._pages)
        button_count = len(self._buttons)
        self.Render(dc, wnd)
        
        # Hasn't been rendered yet assume it's visible
        if len(self._tab_close_buttons) < page_count:
            return True

        # First check if both buttons are disabled - if so, there's no need to
        # check further for visibility.
        arrowButtonVisibleCount = 0
        for i in xrange(button_count):
        
            button = self._buttons[i]
            if button.id == AUI_BUTTON_LEFT or \
               button.id == AUI_BUTTON_RIGHT:
            
                if button.cur_state & AUI_BUTTON_STATE_HIDDEN == 0:
                    arrowButtonVisibleCount += 1
            
        # Tab must be visible
        if arrowButtonVisibleCount == 0:
            return True

        # If tab is less than the given offset, it must be invisible by definition
        if tabPage < tabOffset:
            return False

        # draw buttons
        left_buttons_width = 0
        right_buttons_width = 0

        offset = 0

        # calculate size of the buttons on the right side
        offset = self._rect.x + self._rect.width
        
        for i in xrange(button_count):
            button = self._buttons[button_count - i - 1]

            if button.location != wx.RIGHT:
                continue
            if button.cur_state & AUI_BUTTON_STATE_HIDDEN:
                continue

            offset -= button.rect.GetWidth()
            right_buttons_width += button.rect.GetWidth()
        
        offset = 0

        # calculate size of the buttons on the left side
        for i in xrange(button_count):
            button = self._buttons[button_count - i - 1]

            if button.location != wx.LEFT:
                continue
            if button.cur_state & AUI_BUTTON_STATE_HIDDEN:
                continue

            offset += button.rect.GetWidth()
            left_buttons_width += button.rect.GetWidth()
        
        offset = left_buttons_width

        if offset == 0:
            offset += self._art.GetIndentSize()

        rect = wx.Rect(*self._rect)
        rect.y = 0
        rect.height = self._rect.height

        # See if the given page is visible at the given tab offset (effectively scroll position)
        for i in xrange(tabOffset, page_count):
        
            page = self._pages[i]
            tab_button = self._tab_close_buttons[i]

            rect.x = offset
            rect.width = self._rect.width - right_buttons_width - offset - 2

            if rect.width <= 0:
                return False # haven't found the tab, and we've run out of space, so return False

            size, x_extent = self._art.GetTabSize(dc, wnd, page.caption, page.bitmap, page.active, tab_button.cur_state)
            offset += x_extent

            if i == tabPage:
            
                # If not all of the tab is visible, and supposing there's space to display it all,
                # we could do better so we return False.
                if (self._rect.width - right_buttons_width - offset - 2) <= 0 and (self._rect.width - right_buttons_width - left_buttons_width) > x_extent:
                    return False
                else:
                    return True
            
        # Shouldn't really get here, but if it does, assume the tab is visible to prevent
        # further looping in calling code.
        return True


    def MakeTabVisible(self, tabPage, win):
        """
        Make the tab visible if it wasn't already.

        :param `tabPage`: the tab index;
        :param `win`: an instance of wx.Window derived window.
        """                

        dc = wx.ClientDC(win)
        
        if not self.IsTabVisible(tabPage, self.GetTabOffset(), dc, win):
            for i in xrange(len(self._pages)):
                if self.IsTabVisible(tabPage, i, dc, win):
                    self.SetTabOffset(i)
                    win.Refresh()
                    return


    def TabHitTest(self, x, y):
        """
        TabHitTest() tests if a tab was hit, passing the window pointer
        back if that condition was fulfilled.

        :param `x`: the mouse `x` position;
        :param `y`: the mouse `y` position.
        """

        if not self._rect.Contains((x,y)):
            return None

        btn = self.ButtonHitTest(x, y)
        if btn:
            if btn in self._buttons:
                return None

        for i in xrange(self._tab_offset, len(self._pages)):
            page = self._pages[i]
            if page.rect.Contains((x,y)):
                return page.window

        return None
    

    def ButtonHitTest(self, x, y):
        """
        ButtonHitTest() tests if a button was hit.

        :param `x`: the mouse `x` position;
        :param `y`: the mouse `y` position.
        """

        if not self._rect.Contains((x,y)):
            return None

        for button in self._buttons:
            if button.rect.Contains((x,y)) and \
               (button.cur_state not in [AUI_BUTTON_STATE_HIDDEN, AUI_BUTTON_STATE_DISABLED]):
                return button
            
        for button in self._tab_close_buttons:
            if button.rect.Contains((x,y)) and \
               (button.cur_state not in [AUI_BUTTON_STATE_HIDDEN, AUI_BUTTON_STATE_DISABLED]):
                return button            
            
        return None


    def DoShowHide(self):
        """
        This function shows the active window, then hides all of the other windows
        (in that order).
        """

        pages = self.GetPages()

        # show new active page first
        for page in pages:
            if page.active:
                page.window.Show(True)
                break
            
        # hide all other pages
        for page in pages:
            if not page.active:
                page.window.Show(False)


# ----------------------------------------------------------------------
# -- AuiTabCtrl class implementation --

class AuiTabCtrl(wx.PyControl, AuiTabContainer):
    """
    This is an actual wx.Window-derived window which can be used as a tab
    control in the normal sense.
    """

    def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
                 style=wx.NO_BORDER|wx.WANTS_CHARS):
        """
        Default class constructor. Used internally, do not call it in your code!

        :param `parent`: the L{AuiTabCtrl} parent;
        :param `id`: an identifier for the control: a value of -1 is taken to mean a default;
        :param `pos`: the control position. A value of (-1, -1) indicates a default position,
         chosen by either the windowing system or wxPython, depending on platform;
        :param `size`: the control size. A value of (-1, -1) indicates a default size,
         chosen by either the windowing system or wxPython, depending on platform;
        :param `style`: the window style.
        """

        wx.PyControl.__init__(self, parent, id, pos, size, style, name="AuiTabCtrl")
        AuiTabContainer.__init__(self)

        self._click_pt = wx.Point(-1, -1)
        self._is_dragging = False
        self._hover_button = None
        self._pressed_button = None
        self._drag_image = None

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
        self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
        self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
        self.Bind(wx.EVT_MIDDLE_DOWN, self.OnMiddleDown)
        self.Bind(wx.EVT_MIDDLE_UP, self.OnMiddleUp)
        self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
        self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp)
        self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
        self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
        self.Bind(wx.EVT_CHAR, self.OnChar)
        self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.OnCaptureLost)
        self.Bind(wx.EVT_MOTION, self.OnMotion)
        self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)
        self.Bind(EVT_AUINOTEBOOK_BUTTON, self.OnButton)


    def IsDragging(self):
        """ Returns whether the user is dragging a tab with the mouse or not. """

        return self._is_dragging


    def GetDefaultBorder(self):
        """ Returns the default border style for L{AuiTabCtrl}. """

        return wx.BORDER_NONE

    
    def OnPaint(self, event):
        """
        Handles the wx.EVT_PAINT event for L{AuiTabCtrl}.

        :param `event`: a L{wx.PaintEvent} event to be processed.
        """
        
        dc = wx.PaintDC(self)
        dc.SetFont(self.GetFont())

        if self.GetPageCount() > 0:
            self.Render(dc, self)


    def OnEraseBackground(self, event):
        """
        Handles the wx.EVT_ERASE_BACKGROUND event for L{AuiTabCtrl}.
        This is intentionally empty, to reduce flicker.

        :param `event`: a L{wx.EraseEvent} event to be processed.        
        """
        
        pass        


    def DoGetBestSize(self):
        """ Overridden from wx.PyControl. """

        return wx.Size(self._rect.width, self._rect.height)
    

    def OnSize(self, event):
        """
        Handles the wx.EVT_SIZE event for L{AuiTabCtrl}.

        :param `event`: a L{wx.SizeEvent} event to be processed.        
        """

        s = event.GetSize()
        self.SetTabRect(wx.Rect(0, 0, s.GetWidth(), s.GetHeight()))
                

    def OnLeftDown(self, event):
        """
        Handles the wx.EVT_LEFT_DOWN event for L{AuiTabCtrl}.

        :param `event`: a L{wx.MouseEvent} event to be processed.        
        """
        
        self.CaptureMouse()
        self._click_pt = wx.Point(-1, -1)
        self._is_dragging = False
        self._click_tab = None
        self._pressed_button = None

        wnd = self.TabHitTest(event.GetX(), event.GetY())
        
        if wnd is not None:
            new_selection = self.GetIdxFromWindow(wnd)

            # AuiNotebooks always want to receive this event
            # even if the tab is already active, because they may
            # have multiple tab controls
            if new_selection != self.GetActivePage() or isinstance(self.GetParent(), AuiNotebook):
            
                e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, self.GetId())
                e.SetSelection(new_selection)
                e.SetOldSelection(self.GetActivePage())
                e.SetEventObject(self)
                self.GetEventHandler().ProcessEvent(e)
            
            self._click_pt.x = event.GetX()
            self._click_pt.y = event.GetY()
            self._click_tab = wnd
        
        if self._hover_button:
            self._pressed_button = self._hover_button
            self._pressed_button.cur_state = AUI_BUTTON_STATE_PRESSED
            self.Refresh()
            self.Update()


    def OnCaptureLost(self, event):
        """
        Handles the wx.EVT_MOUSE_CAPTURE_LOST event for L{AuiTabCtrl}.

        :param `event`: a L{wx.MouseCaptureLostEvent} event to be processed.        
        """
        
        if self._is_dragging:
            self._is_dragging = False

            if self._drag_image:
                self._drag_image.EndDrag()
                del self._drag_image
                self._drag_image = None
                
            event = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_CANCEL_DRAG, self.GetId())
            event.SetSelection(self.GetIdxFromWindow(self._click_tab))
            event.SetOldSelection(event.GetSelection())
            event.SetEventObject(self)
            self.GetEventHandler().ProcessEvent(event) 


    def OnLeftUp(self, event):
        """
        Handles the wx.EVT_LEFT_UP event for L{AuiTabCtrl}.

        :param `event`: a L{wx.MouseEvent} event to be processed.        
        """
        
        if wx.Window.GetCapture() == self:
            self.ReleaseMouse()

        if self._is_dragging:
            
            self._is_dragging = False
            if self._drag_image:
                self._drag_image.EndDrag()
                del self._drag_image
                self._drag_image = None
                self.GetParent().Refresh()

            evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_END_DRAG, self.GetId())
            evt.SetSelection(self.GetIdxFromWindow(self._click_tab))
            evt.SetOldSelection(evt.GetSelection())
            evt.SetEventObject(self)
            self.GetEventHandler().ProcessEvent(evt)

            return
    
        if self._pressed_button:
        
            # make sure we're still clicking the button
            button = self.ButtonHitTest(event.GetX(), event.GetY())
            
            if button is None:
                return

            if button != self._pressed_button:
                self._pressed_button = None
                return
            
            self.Refresh()
            self.Update()

            if self._pressed_button.cur_state & AUI_BUTTON_STATE_DISABLED == 0:
            
                evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BUTTON, self.GetId())
                evt.SetSelection(self.GetIdxFromWindow(self._click_tab))
                evt.SetInt(self._pressed_button.id)
                evt.SetEventObject(self)
                self.GetEventHandler().ProcessEvent(evt)
            
            self._pressed_button = None
        
        self._click_pt = wx.Point(-1, -1)
        self._is_dragging = False
        self._click_tab = None


    def OnMiddleUp(self, event):
        """
        Handles the wx.EVT_MIDDLE_UP event for L{AuiTabCtrl}.

        :param `event`: a L{wx.MouseEvent} event to be processed.        
        """

        wnd = self.TabHitTest(event.GetX(), event.GetY())
        
        if wnd is None:
            return
            
        e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_UP, self.GetId())
        e.SetEventObject(self)
        e.SetSelection(self.GetIdxFromWindow(wnd))
        self.GetEventHandler().ProcessEvent(e)


    def OnMiddleDown(self, event):
        """
        Handles the wx.EVT_MIDDLE_DOWN event for L{AuiTabCtrl}.

        :param `event`: a L{wx.MouseEvent} event to be processed.        
        """
        
        wnd = self.TabHitTest(event.GetX(), event.GetY())
        
        if wnd is None:
            return
            
        e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_DOWN, self.GetId())
        e.SetEventObject(self)
        e.SetSelection(self.GetIdxFromWindow(wnd))
        self.GetEventHandler().ProcessEvent(e)


    def OnRightUp(self, event):
        """
        Handles the wx.EVT_RIGHT_UP event for L{AuiTabCtrl}.

        :param `event`: a L{wx.MouseEvent} event to be processed.        
        """

        wnd = self.TabHitTest(event.GetX(), event.GetY())
        
        if wnd is None:
            return
            
        e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP, self.GetId())
        e.SetEventObject(self)
        e.SetSelection(self.GetIdxFromWindow(wnd))
        self.GetEventHandler().ProcessEvent(e)


    def OnRightDown(self, event):
        """
        Handles the wx.EVT_RIGHT_DOWN event for L{AuiTabCtrl}.

        :param `event`: a L{wx.MouseEvent} event to be processed.        
        """
        
        wnd = self.TabHitTest(event.GetX(), event.GetY())
        
        if wnd is None:
            return
            
        e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_DOWN, self.GetId())
        e.SetEventObject(self)
        e.SetSelection(self.GetIdxFromWindow(wnd))
        self.GetEventHandler().ProcessEvent(e)


    def OnLeftDClick(self, event):
        """
        Handles the wx.EVT_LEFT_DCLICK event for L{AuiTabCtrl}.

        :param `event`: a L{wx.MouseEvent} event to be processed.        
        """
        
        x, y = event.GetX(), event.GetY()
        
        if not self.TabHitTest(x, y) and not self.ButtonHitTest(x, y):
    
            e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK, self.GetId())
            e.SetEventObject(self)
            self.GetEventHandler().ProcessEvent(e)

        if self.TabHitTest(x, y):
            e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_DCLICK, self.GetId())
            e.SetEventObject(self)
            self.GetEventHandler().ProcessEvent(e)

    
    def OnMotion(self, event):
        """
        Handles the wx.EVT_MOTION event for L{AuiTabCtrl}.

        :param `event`: a L{wx.MouseEvent} event to be processed.        
        """

        pos = event.GetPosition()

        # check if the mouse is hovering above a button

        button = self.ButtonHitTest(pos.x, pos.y)
        wnd = self.TabHitTest(pos.x, pos.y)

        if wnd is not None:
            mouse_tab = self.GetIdxFromWindow(wnd)
            if not self._pages[mouse_tab].enabled:
                self._hover_button = None
                return

        if button:
            
            if self._hover_button and button != self._hover_button:
                self._hover_button.cur_state = AUI_BUTTON_STATE_NORMAL
                self._hover_button = None
                self.Refresh()
                self.Update()
            
            if button.cur_state != AUI_BUTTON_STATE_HOVER:
                button.cur_state = AUI_BUTTON_STATE_HOVER
                self.Refresh()
                self.Update()
                self._hover_button = button
                return
                    
        else:
        
            if self._hover_button:
                self._hover_button.cur_state = AUI_BUTTON_STATE_NORMAL
                self._hover_button = None
                self.Refresh()
                self.Update()

        if not event.LeftIsDown() or self._click_pt == wx.Point(-1, -1):
            return

        if not self._is_dragging:

            drag_x_threshold = wx.SystemSettings.GetMetric(wx.SYS_DRAG_X)
            drag_y_threshold = wx.SystemSettings.GetMetric(wx.SYS_DRAG_Y)

            if abs(pos.x - self._click_pt.x) > drag_x_threshold or \
               abs(pos.y - self._click_pt.y) > drag_y_threshold:
                self._is_dragging = True

                if self._drag_image:
                    self._drag_image.EndDrag()
                    del self._drag_image
                    self._drag_image = None

                if self._flags & AUI_NB_DRAW_DND_TAB:
                    # Create the custom draw image from the icons and the text of the item
                    wnd = self.TabHitTest(pos.x, pos.y)
                    mouse_tab = self.GetIdxFromWindow(wnd)
                    page = self._pages[mouse_tab]
                    tab_button = self._tab_close_buttons[mouse_tab]
                    self._drag_image = TabDragImage(self, page, tab_button.cur_state, self._art)

                    if self._flags & AUI_NB_TAB_FLOAT:
                        self._drag_image.BeginDrag(wx.Point(0,0), self, fullScreen=True)
                    else:
                        self._drag_image.BeginDragBounded(wx.Point(0,0), self, self.GetParent())
                        
                    self._drag_image.Show()
                    self._drag_image.Move(pos)

        wnd = self.TabHitTest(pos.x, pos.y)
        if not wnd:
            evt2 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BEGIN_DRAG, self.GetId())
            evt2.SetSelection(self.GetIdxFromWindow(self._click_tab))
            evt2.SetOldSelection(evt2.GetSelection())
            evt2.SetEventObject(self)
            self.GetEventHandler().ProcessEvent(evt2)
            if evt2.GetDispatched():
                return
            
        evt3 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_DRAG_MOTION, self.GetId())
        evt3.SetSelection(self.GetIdxFromWindow(self._click_tab))
        evt3.SetOldSelection(evt3.GetSelection())
        evt3.SetEventObject(self)
        self.GetEventHandler().ProcessEvent(evt3)

        if self._drag_image:
            self._drag_image.Move(pos)
            

    def OnLeaveWindow(self, event):
        """
        Handles the wx.EVT_LEAVE_WINDOW event for L{AuiTabCtrl}.

        :param `event`: a L{wx.MouseEvent} event to be processed.        
        """
        
        if self._hover_button:
            self._hover_button.cur_state = AUI_BUTTON_STATE_NORMAL
            self._hover_button = None
            self.Refresh()
            self.Update()
    

    def OnButton(self, event):
        """
        Handles the EVT_AUINOTEBOOK_BUTTON event for L{AuiTabCtrl}.

        :param `event`: a EVT_AUINOTEBOOK_BUTTON event to be processed.        
        """
        
        button = event.GetInt()

        if button == AUI_BUTTON_LEFT or button == AUI_BUTTON_RIGHT:
            if button == AUI_BUTTON_LEFT:
                if self.GetTabOffset() > 0:
                
                    self.SetTabOffset(self.GetTabOffset()-1)
                    self.Refresh()
                    self.Update()
            else:
                self.SetTabOffset(self.GetTabOffset()+1)
                self.Refresh()
                self.Update()
            
        elif button == AUI_BUTTON_WINDOWLIST:
            idx = self.GetArtProvider().ShowDropDown(self, self._pages, self.GetActivePage())
            
            if idx != -1:
            
                e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, self.GetId())
                e.SetSelection(idx)
                e.SetOldSelection(self.GetActivePage())
                e.SetEventObject(self)
                self.GetEventHandler().ProcessEvent(e)
            
        else:
            event.Skip()
        

    def OnSetFocus(self, event):
        """
        Handles the wx.EVT_SET_FOCUS event for L{AuiTabCtrl}.

        :param `event`: a L{wx.FocusEvent} event to be processed.        
        """

        self.Refresh()


    def OnKillFocus(self, event):
        """
        Handles the wx.EVT_KILL_FOCUS event for L{AuiTabCtrl}.

        :param `event`: a L{wx.FocusEvent} event to be processed.        
        """

        self.Refresh()


    def OnChar(self, event):
        """
        Handles the wx.EVT_CHAR event for L{AuiTabCtrl}.

        :param `event`: a L{wx.KeyEvent} event to be processed.        
        """

        if self.GetActivePage() == -1:
            event.Skip()
            return
    
        # We can't leave tab processing to the system on Windows, tabs and keys
        # get eaten by the system and not processed properly if we specify both
        # wxTAB_TRAVERSAL and wxWANTS_CHARS. And if we specify just wxTAB_TRAVERSAL,
        # we don't key arrow key events.

        key = event.GetKeyCode()

        if key == wx.WXK_NUMPAD_PAGEUP:
            key = wx.WXK_PAGEUP
        if key == wx.WXK_NUMPAD_PAGEDOWN:
            key = wx.WXK_PAGEDOWN
        if key == wx.WXK_NUMPAD_HOME:
            key = wx.WXK_HOME
        if key == wx.WXK_NUMPAD_END:
            key = wx.WXK_END
        if key == wx.WXK_NUMPAD_LEFT:
            key = wx.WXK_LEFT
        if key == wx.WXK_NUMPAD_RIGHT:
            key = wx.WXK_RIGHT

        if key == wx.WXK_TAB or key == wx.WXK_PAGEUP or key == wx.WXK_PAGEDOWN:
        
            bCtrlDown = event.ControlDown()
            bShiftDown = event.ShiftDown()

            bForward = (key == wx.WXK_TAB and not bShiftDown) or (key == wx.WXK_PAGEDOWN)
            bWindowChange = (key == wx.WXK_PAGEUP) or (key == wx.WXK_PAGEDOWN) or bCtrlDown
            bFromTab = (key == wx.WXK_TAB)

            nb = self.GetParent()
            if not nb or not isinstance(nb, AuiNotebook):
                event.Skip()
                return
            
            keyEvent = wx.NavigationKeyEvent()
            keyEvent.SetDirection(bForward)
            keyEvent.SetWindowChange(bWindowChange)
            keyEvent.SetFromTab(bFromTab)
            keyEvent.SetEventObject(nb)

            if not nb.GetEventHandler().ProcessEvent(keyEvent):
            
                # Not processed? Do an explicit tab into the page.
                win = self.GetWindowFromIdx(self.GetActivePage())
                if win:
                    win.SetFocus()
            
            return
        
        if len(self._pages) < 2:
            event.Skip()
            return
        
        newPage = -1

        if self.GetLayoutDirection() == wx.Layout_RightToLeft:
            forwardKey = wx.WXK_LEFT
            backwardKey = wx.WXK_RIGHT
        else:
            forwardKey = wx.WXK_RIGHT
            backwardKey = wx.WXK_LEFT
        
        if key == forwardKey:
            if self.GetActivePage() == -1:
                newPage = 0
            elif self.GetActivePage() < len(self._pages) - 1:
                newPage = self.GetActivePage() + 1
            
        elif key == backwardKey:        
            if self.GetActivePage() == -1:
                newPage = len(self._pages) - 1
            elif self.GetActivePage() > 0:
                newPage = self.GetActivePage() - 1
            
        elif key == wx.WXK_HOME:
            newPage = 0
        
        elif key == wx.WXK_END:
            newPage = len(self._pages) - 1
        
        else:
            event.Skip()

        if newPage != -1:
            e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, self.GetId())
            e.SetSelection(newPage)
            e.SetOldSelection(newPage)
            e.SetEventObject(self)
            self.GetEventHandler().ProcessEvent(e)
        
        else:
            event.Skip()


# ----------------------------------------------------------------------

class TabFrame(wx.PyWindow):
    """
    TabFrame is an interesting case. It's important that all child pages
    of the multi-notebook control are all actually children of that control
    (and not grandchildren). TabFrame facilitates this. There is one
    instance of TabFrame for each tab control inside the multi-notebook.
    
    It's important to know that TabFrame is not a real window, but it merely
    used to capture the dimensions/positioning of the internal tab control and
    it's managed page windows.
    """

    def __init__(self):
        """
        Default class constructor. Used internally, do not call it in your code!
        """

        pre = wx.PrePyWindow()
        
        self._tabs = None
        self._rect = wx.Rect(0, 0, 200, 200)
        self._tab_ctrl_height = 20
        self._tab_rect = wx.Rect()        
        
        self.PostCreate(pre)
        

    def SetTabCtrlHeight(self, h):
        """
        Sets the tab control height.

        :param `h`: the tab area height.
        """
    
        self._tab_ctrl_height = h


    def DoSetSize(self, x, y, width, height, flags=wx.SIZE_AUTO):
        """
        Overridden from wx.PyControl.
        
        Sets the position and size of the window in pixels. The `flags`
        parameter indicates the interpretation of the other params if they are
        equal to -1.

        :param `x`: the window `x` position;
        :param `y`: the window `y` position;
        :param `width`: the window width;
        :param `height`: the window height;
        :param `flags`: may have one of this bit set:
   
        ===================================  ======================================
        Size Flags                           Description
        ===================================  ======================================
        ``wx.SIZE_AUTO``                     A -1 indicates that a class-specific default should be used.
        ``wx.SIZE_AUTO_WIDTH``               A -1 indicates that a class-specific default should be used for the width.
        ``wx.SIZE_AUTO_HEIGHT``              A -1 indicates that a class-specific default should be used for the height.
        ``wx.SIZE_USE_EXISTING``             Existing dimensions should be used if -1 values are supplied.
        ``wx.SIZE_ALLOW_MINUS_ONE``          Allow dimensions of -1 and less to be interpreted as real dimensions, not default values.
        ``wx.SIZE_FORCE``                    Normally, if the position and the size of the window are already the same as the parameters of this function, nothing is done. but with this flag a window resize may be forced even in this case (supported in wx 2.6.2 and later and only implemented for MSW and ignored elsewhere currently) 
        ===================================  ======================================
        """

        self._rect = wx.Rect(x, y, width, height)
        self.DoSizing()


    def DoGetSize(self):
        """
        Overridden from wx.PyControl.

        Returns the window size.
        """

        return self._rect.width, self._rect.height


    def DoGetClientSize(self):
        """
        Overridden from wx.PyControl.

        Returns the window client size.
        """
        
        return self._rect.width, self._rect.height
    

    def Show(self, show=True):
        """
        Overridden from wx.PyControl.

        Shows/hides the window.
        """
        
        return False


    def DoSizing(self):
        """ Does the actual sizing of the tab control. """
    
        if not self._tabs:
            return

        hideOnSingle = self._tabs.GetFlags() & AUI_NB_HIDE_ON_SINGLE_TAB
        
        if not hideOnSingle or (hideOnSingle and self._tabs.GetPageCount() > 1):
            tab_height = self._tab_ctrl_height
            
            self._tab_rect = wx.Rect(self._rect.x, self._rect.y, self._rect.width, self._tab_ctrl_height)
            
            if self._tabs.GetFlags() & AUI_NB_BOTTOM:        
                self._tab_rect = wx.Rect(self._rect.x, self._rect.y + self._rect.height - tab_height,
                                         self._rect.width, tab_height)
                self._tabs.SetDimensions(self._rect.x, self._rect.y + self._rect.height - tab_height,
                                         self._rect.width, tab_height)
                self._tabs.SetTabRect(wx.Rect(0, 0, self._rect.width, tab_height))
                
            else:

                self._tab_rect = wx.Rect(self._rect.x, self._rect.y, self._rect.width, tab_height)
                self._tabs.SetDimensions(self._rect.x, self._rect.y, self._rect.width, tab_height)
                self._tabs.SetTabRect(wx.Rect(0, 0, self._rect.width, tab_height))
            
            # TODO: elif (GetFlags() & AUI_NB_LEFT)
            # TODO: elif (GetFlags() & AUI_NB_RIGHT)

            self._tabs.Refresh()
            self._tabs.Update()
            
        else:
            
            tab_height = 0
            self._tabs.SetDimensions(self._rect.x, self._rect.y, self._rect.width, tab_height)
            self._tabs.SetTabRect(wx.Rect(0, 0, self._rect.width, tab_height))
            
        pages = self._tabs.GetPages()

        for page in pages:
        
            height = self._rect.height - tab_height
            
            if height < 0:            
                # avoid passing negative height to wx.Window.SetSize(), this
                # results in assert failures/GTK+ warnings
                height = 0
            
            if self._tabs.GetFlags() & AUI_NB_BOTTOM:
                page.window.SetDimensions(self._rect.x, self._rect.y, self._rect.width, height)
            
            else:
                page.window.SetDimensions(self._rect.x, self._rect.y + tab_height,
                                          self._rect.width, height)
            
            # TODO: elif (GetFlags() & AUI_NB_LEFT)
            # TODO: elif (GetFlags() & AUI_NB_RIGHT)
            
            if repr(page.window.__class__).find("AuiMDIChildFrame") >= 0:
                page.window.ApplyMDIChildFrameRect()            


    def Update(self):
        """
        Overridden from wx.PyControl.

        Calling this method immediately repaints the invalidated area of the window
        and all of its children recursively while this would usually only happen when
        the flow of control returns to the event loop.  

        @note: Notice that this function doesn't invalidate any area of the window so
        nothing happens if nothing has been invalidated (i.e. marked as requiring a redraw).
        Use `Refresh` first if you want to immediately redraw the window unconditionally.   
        """

        # does nothing
        pass


# ----------------------------------------------------------------------
# -- AuiNotebook class implementation --

class AuiNotebook(wx.PyControl):
    """
    AuiNotebook is a notebook control which implements many features common in
    applications with dockable panes. Specifically, AuiNotebook implements functionality
    which allows the user to rearrange tab order via drag-and-drop, split the tab window
    into many different splitter configurations, and toggle through different themes to
    customize the control's look and feel.

    An effort has been made to try to maintain an API as similar to that of wx.Notebook.

    The default theme that is used is AuiDefaultTabArt, which provides a modern, glossy
    look and feel. The theme can be changed by calling AuiNotebook.SetArtProvider.
    """

    def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
                 style=AUI_NB_DEFAULT_STYLE):
        """
        Default class constructor.

        :param `parent`: the L{AuiNotebook} parent;
        :param `id`: an identifier for the control: a value of -1 is taken to mean a default;
        :param `pos`: the control position. A value of (-1, -1) indicates a default position,
         chosen by either the windowing system or wxPython, depending on platform;
        :param `size`: the control size. A value of (-1, -1) indicates a default size,
         chosen by either the windowing system or wxPython, depending on platform;
        :param `style`: the window style. This can be a combination of the following bits:
        
        ==================================== ==================================
        Flag name                            Description
        ==================================== ==================================
        ``AUI_NB_TOP``                       With this style, tabs are drawn along the top of the notebook
        ``AUI_NB_LEFT``                      With this style, tabs are drawn along the left of the notebook. Not implemented yet.
        ``AUI_NB_RIGHT``                     With this style, tabs are drawn along the right of the notebook. Not implemented yet.
        ``AUI_NB_BOTTOM``                    With this style, tabs are drawn along the bottom of the notebook.
        ``AUI_NB_TAB_SPLIT``                 Allows the tab control to be split by dragging a tab
        ``AUI_NB_TAB_MOVE``                  Allows a tab to be moved horizontally by dragging
        ``AUI_NB_TAB_EXTERNAL_MOVE``         Allows a tab to be moved to another tab control
        ``AUI_NB_TAB_FIXED_WIDTH``           With this style, all tabs have the same width
        ``AUI_NB_SCROLL_BUTTONS``            With this style, left and right scroll buttons are displayed
        ``AUI_NB_WINDOWLIST_BUTTON``         With this style, a drop-down list of windows is available
        ``AUI_NB_CLOSE_BUTTON``              With this style, a close button is available on the tab bar
        ``AUI_NB_CLOSE_ON_ACTIVE_TAB``       With this style, a close button is available on the active tab
        ``AUI_NB_CLOSE_ON_ALL_TABS``         With this style, a close button is available on all tabs
        ``AUI_NB_MIDDLE_CLICK_CLOSE``        Allows to close AuiNotebook tabs by mouse middle button click
        ``AUI_NB_SUB_NOTEBOOK``              This style is used by AuiManager to create automatic AuiNotebooks
        ``AUI_NB_HIDE_ON_SINGLE_TAB``        Hides the tab window if only one tab is present
        ``AUI_NB_SMART_TABS``                Use Smart Tabbing, like Alt+Tab on Windows
        ``AUI_NB_USE_IMAGES_DROPDOWN``       Uses images on dropdown window list menu instead of check items
        ``AUI_NB_CLOSE_ON_TAB_LEFT``         Draws the tab close button on the left instead of on the right (a la Camino browser)
        ``AUI_NB_TAB_FLOAT``                 Allows the floating of single tabs. Known limitation: when the notebook is more or less full screen, tabs cannot be dragged far enough outside of the notebook to become floating pages
        ``AUI_NB_DRAW_DND_TAB``              Draws an image representation of a tab while dragging (on by default)
        ``AUI_NB_SASH_DCLICK_UNSPLIT``       Unsplit a splitted AuiNotebook when double-clicking on a sash.
        ==================================== ==================================

        Default value for `style` is:
        ``AUI_NB_DEFAULT_STYLE`` = ``AUI_NB_TOP`` |
                                   ``AUI_NB_TAB_SPLIT`` |
                                   ``AUI_NB_TAB_MOVE`` |
                                   ``AUI_NB_SCROLL_BUTTONS`` |
                                   ``AUI_NB_CLOSE_ON_ACTIVE_TAB`` |
                                   ``AUI_NB_MIDDLE_CLICK_CLOSE`` |
                                   ``AUI_NB_DRAW_DND_TAB``
        """

        self._curpage = -1
        self._tab_id_counter = AuiBaseTabCtrlId
        self._dummy_wnd = None
        self._tab_ctrl_height = 20
        self._requested_bmp_size = wx.Size(-1, -1)
        self._requested_tabctrl_height = -1

        wx.PyControl.__init__(self, parent, id, pos, size, style)
        self._mgr = framemanager.AuiManager()
        self._tabs = AuiTabContainer()

        self.InitNotebook(style)


    def InitNotebook(self, style):
        """
        InitNotebook() contains common initialization
        code called by all constructors.

        :param `style`: the notebook style. See L{__init__} for more details.
        """

        self.SetName("AuiNotebook")
        self._flags = style

        self._popupWin = None
        self._naviIcon = None
        self._imageList = None
        
        self._normal_font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
        self._selected_font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
        self._selected_font.SetWeight(wx.BOLD)

        self.SetArtProvider(TA.AuiDefaultTabArt())

        self._dummy_wnd = wx.Window(self, wx.ID_ANY, wx.Point(0, 0), wx.Size(0, 0))
        self._dummy_wnd.SetSize((200, 200))
        self._dummy_wnd.Show(False)

        self._mgr.SetManagedWindow(self)
        self._mgr.SetFlags(AUI_MGR_DEFAULT)
        self._mgr.SetDockSizeConstraint(1.0, 1.0) # no dock size constraint

        self._mgr.AddPane(self._dummy_wnd, framemanager.AuiPaneInfo().Name("dummy").Bottom().CaptionVisible(False).Show(False))
        self._mgr.Update()

        self.Bind(wx.EVT_SIZE, self.OnSize)
#        self.Bind(wx.EVT_CHILD_FOCUS, self.OnChildFocusNotebook)
        self.Bind(EVT_AUINOTEBOOK_PAGE_CHANGING, self.OnTabClicked,
                  id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
        self.Bind(EVT_AUINOTEBOOK_BEGIN_DRAG, self.OnTabBeginDrag,
                  id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
        self.Bind(EVT_AUINOTEBOOK_END_DRAG, self.OnTabEndDrag,
                  id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
        self.Bind(EVT_AUINOTEBOOK_DRAG_MOTION, self.OnTabDragMotion,
                  id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
        self.Bind(EVT_AUINOTEBOOK_CANCEL_DRAG, self.OnTabCancelDrag,
                  id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)        
        self.Bind(EVT_AUINOTEBOOK_BUTTON, self.OnTabButton,
                  id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
        self.Bind(EVT_AUINOTEBOOK_TAB_MIDDLE_DOWN, self.OnTabMiddleDown,
                  id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
        self.Bind(EVT_AUINOTEBOOK_TAB_MIDDLE_UP, self.OnTabMiddleUp,
                  id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
        self.Bind(EVT_AUINOTEBOOK_TAB_RIGHT_DOWN, self.OnTabRightDown,
                  id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
        self.Bind(EVT_AUINOTEBOOK_TAB_RIGHT_UP, self.OnTabRightUp,
                  id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
        self.Bind(EVT_AUINOTEBOOK_BG_DCLICK, self.OnTabBgDClick,
                  id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
        self.Bind(EVT_AUINOTEBOOK_TAB_DCLICK, self.OnTabDClick,
                  id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)

        self.Bind(wx.EVT_NAVIGATION_KEY, self.OnNavigationKeyNotebook)


    def SetArtProvider(self, art):
        """
        Sets the art provider to be used by the notebook.

        :param `art`: an art provider.
        """

        self._tabs.SetArtProvider(art)
        self.UpdateTabCtrlHeight(force=True)


    def SavePerspective(self):
        """
        Saves the entire user interface layout into an encoded string, which can then
        be stored by the application (probably using wx.Config). When a perspective
        is restored using L{LoadPerspective()}, the entire user interface will return
        to the state it was when the perspective was saved.
        """
        
        # Build list of panes/tabs
        tabs = ""
        all_panes = self._mgr.GetAllPanes()
        
        for pane in all_panes:

            if pane.name == "dummy":
                continue

            tabframe = pane.window
          
            if tabs:
                tabs += "|"
              
            tabs += pane.name + "="
          
            # add tab id's
            page_count = tabframe._tabs.GetPageCount()
          
            for p in xrange(page_count):
          
                page = tabframe._tabs.GetPage(p)
                page_idx = self._tabs.GetIdxFromWindow(page.window)
             
                if p:
                    tabs += ","

                if p == tabframe._tabs.GetActivePage():
                    tabs += "+"
                elif page_idx == self._curpage:
                    tabs += "*"
                    
                tabs += "%u"%page_idx
          
        tabs += "@"

        # Add frame perspective
        tabs += self._mgr.SavePerspective()

        return tabs


    def LoadPerspective(self, layout):
        """
        LoadPerspective() loads a layout which was saved with L{SavePerspective()}.

        :param `layout`: a string which contains a saved AuiNotebook layout.
        """
        
        # Remove all tab ctrls (but still keep them in main index)
        tab_count = self._tabs.GetPageCount()
        for i in xrange(tab_count):
            wnd = self._tabs.GetWindowFromIdx(i)

            # find out which onscreen tab ctrl owns this tab
            ctrl, ctrl_idx = self.FindTab(wnd)
            if not ctrl:
                return False

            # remove the tab from ctrl
            if not ctrl.RemovePage(wnd):
                return False

        self.RemoveEmptyTabFrames()

        sel_page = 0
        tabs = layout[0:layout.index("@")]
        to_break1 = False
        
        while 1:

            if "|" not in tabs:
                to_break1 = True
                tab_part = tabs
            else:
                tab_part = tabs[0:tabs.index('|')]
          
            # Get pane name
            pane_name = tab_part[0:tab_part.index("=")]

            # create a new tab frame
            new_tabs = TabFrame()
            self._tab_id_counter += 1
            new_tabs._tabs = AuiTabCtrl(self, self._tab_id_counter)
            new_tabs._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone())
            new_tabs.SetTabCtrlHeight(self._tab_ctrl_height)
            new_tabs._tabs.SetFlags(self._flags)
            dest_tabs = new_tabs._tabs

            # create a pane info structure with the information
            # about where the pane should be added
            pane_info = framemanager.AuiPaneInfo().Name(pane_name).Bottom().CaptionVisible(False)
            self._mgr.AddPane(new_tabs, pane_info)

            # Get list of tab id's and move them to pane
            tab_list = tab_part[tab_part.index("=")+1:]
            to_break2, active_found = False, False
            
            while 1:
                if "," not in tab_list:
                    to_break2 = True
                    tab = tab_list
                else:
                    tab = tab_list[0:tab_list.index(",")]                
                    tab_list = tab_list[tab_list.index(",")+1:]

                # Check if this page has an 'active' marker
                c = tab[0]
                if c in ['+', '*']:
                    tab = tab[1:]

                tab_idx = int(tab)
                if tab_idx >= self.GetPageCount():
                    continue

                # Move tab to pane
                page = self._tabs.GetPage(tab_idx)
                newpage_idx = dest_tabs.GetPageCount()
                dest_tabs.InsertPage(page.window, page, newpage_idx)

                if c == '+':
                    dest_tabs.SetActivePage(newpage_idx)
                    active_found = True
                elif c == '*':
                    sel_page = tab_idx

                if to_break2:
                    break

            if not active_found:
                dest_tabs.SetActivePage(0)

            new_tabs.DoSizing()
            dest_tabs.DoShowHide()
            dest_tabs.Refresh()
        
            if to_break1:
                break
            
            tabs = tabs[tabs.index('|')+1:]

        # Load the frame perspective
        frames = layout[layout.index('@')+1:]
        self._mgr.LoadPerspective(frames)

        # Force refresh of selection
        self._curpage = -1
        self.SetSelection(sel_page)

        return True


    def SetTabCtrlHeight(self, height):
        """
        Sets the tab height. By default, the tab control height is calculated
        by measuring the text height and bitmap sizes on the tab captions.
        Calling this method will override that calculation and set the tab control
        to the specified height parameter. A call to this method will override
        any call to L{SetUniformBitmapSize}. Specifying -1 as the height will
        return the control to its default auto-sizing behaviour.

        :param `height`: the tab control area height.        
        """
        
        self._requested_tabctrl_height = height

        # if window is already initialized, recalculate the tab height
        if self._dummy_wnd:
            self.UpdateTabCtrlHeight()
        

    def SetUniformBitmapSize(self, size):
        """
        SetUniformBitmapSize() ensures that all tabs will have the same height,
        even if some tabs don't have bitmaps. Passing wx.DefaultSize to this
        function will instruct the control to use dynamic tab height, which is
        the default behaviour. Under the default behaviour, when a tab with a
        large bitmap is added, the tab control's height will automatically
        increase to accommodate the larger bitmap.

        :param `size`: an instance of wx.Size specifying the tab bitmap size.        
        """

        self._requested_bmp_size = wx.Size(*size)

        # if window is already initialized, recalculate the tab height
        if self._dummy_wnd:
            self.UpdateTabCtrlHeight()
    

    def UpdateTabCtrlHeight(self, force=False):
        """
        UpdateTabCtrlHeight() does the actual tab resizing. It's meant
        to be used interally.

        :param `force`: force the tab art to repaint.
        """

        # get the tab ctrl height we will use
        height = self.CalculateTabCtrlHeight()

        # if the tab control height needs to change, update
        # all of our tab controls with the new height
        if self._tab_ctrl_height != height or force:
            art = self._tabs.GetArtProvider()

            self._tab_ctrl_height = height

            all_panes = self._mgr.GetAllPanes()
            for pane in all_panes:
    
                if pane.name == "dummy":
                    continue
                
                tab_frame = pane.window
                tabctrl = tab_frame._tabs
                tab_frame.SetTabCtrlHeight(self._tab_ctrl_height)
                tabctrl.SetArtProvider(art.Clone())
                tab_frame.DoSizing()
            

    def UpdateHintWindowSize(self):
        """ Updates the L{AuiManager} hint window size. """

        size = self.CalculateNewSplitSize()

        # the placeholder hint window should be set to this size
        info = self._mgr.GetPane("dummy")
        
        if info.IsOk():        
            info.MinSize(size)
            info.BestSize(size)
            self._dummy_wnd.SetSize(size)
        

    def CalculateNewSplitSize(self):
        """ Calculates the size of the new split. """

        # count number of tab controls
        tab_ctrl_count = 0
        all_panes = self._mgr.GetAllPanes()

        for pane in all_panes:                
            if pane.name == "dummy":
                continue
            
            tab_ctrl_count += 1
        
        # if there is only one tab control, the first split
        # should happen around the middle
        if tab_ctrl_count < 2:
            new_split_size = self.GetClientSize()
            new_split_size.x /= 2
            new_split_size.y /= 2
        
        else:
        
            # this is in place of a more complicated calculation
            # that needs to be implemented
            new_split_size = wx.Size(180, 180)
        
        return new_split_size


    def CalculateTabCtrlHeight(self):
        """ Calculates the tab control area height. """

        # if a fixed tab ctrl height is specified,
        # just return that instead of calculating a
        # tab height
        if self._requested_tabctrl_height != -1:
            return self._requested_tabctrl_height

        # find out new best tab height
        art = self._tabs.GetArtProvider()

        return art.GetBestTabCtrlSize(self, self._tabs.GetPages(), self._requested_bmp_size)


    def GetArtProvider(self):
        """ Returns the associated art provider. """

        return self._tabs.GetArtProvider()


    def SetWindowStyleFlag(self, style):
        """
        Overridden from wx.PyControl.
        Sets the style of the window.
        
        :param `style`: the new window style.

        @note: Please note that some styles cannot be changed after the window
        creation and that `Refresh` might need to be be called after changing the
        others for the change to take place immediately.
        """

        wx.PyControl.SetWindowStyleFlag(self, style)

        self._flags = style

        # if the control is already initialized
        if self._mgr.GetManagedWindow() == self:
        
            # let all of the tab children know about the new style

            all_panes = self._mgr.GetAllPanes()
            for pane in all_panes:
                if pane.name == "dummy":
                    continue

                tabframe = pane.window
                tabctrl = tabframe._tabs
                tabctrl.SetFlags(self._flags)
                tabframe.DoSizing()
                tabctrl.Refresh()
                tabctrl.Update()


    def AddPage(self, page, caption, select=False, bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap):
        """
        Adds a page. If the `select` parameter is True, calling this will generate a
        page change event.

        :param `page`: the page to be added;
        :param `caption`: specifies the text for the new page;
        :param `select`: specifies whether the page should be selected;
        :param `bitmap`: the wx.Bitmap to display in the enabled tab;
        :param `disabled_bitmap`: the wx.Bitmap to display in the disabled tab.
        """

        return self.InsertPage(self.GetPageCount(), page, caption, select, bitmap, disabled_bitmap)


    def InsertPage(self, page_idx, page, caption, select=False, bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap):
        """
        This is similar to L{AddPage}, but allows the ability to specify the insert location.

        :param `page_idx`: specifies the position for the new page;
        :param `page`: the page to be added;
        :param `caption`: specifies the text for the new page;
        :param `select`: specifies whether the page should be selected;
        :param `bitmap`: the wx.Bitmap to display in the enabled tab;
        :param `disabled_bitmap`: the wx.Bitmap to display in the disabled tab.
        """
        
        if not page:
            return False

        page.Reparent(self)
        info = AuiNotebookPage()
        info.window = page
        info.caption = caption
        info.bitmap = bitmap
        info.active = False

        if bitmap.IsOk() and not disabled_bitmap.IsOk():
            disabled_bitmap = MakeDisabledBitmap(bitmap)
            info.dis_bitmap = disabled_bitmap

        # if there are currently no tabs, the first added
        # tab must be active
        if self._tabs.GetPageCount() == 0:
            info.active = True
            
        self._tabs.InsertPage(page, info, page_idx)

        # if that was the first page added, even if
        # select is False, it must become the "current page"
        # (though no select events will be fired)
        if not select and self._tabs.GetPageCount() == 1:
            select = True

        active_tabctrl = self.GetActiveTabCtrl()
        if page_idx >= active_tabctrl.GetPageCount():
            active_tabctrl.AddPage(page, info)
        else:
            active_tabctrl.InsertPage(page, info, page_idx)

        self.UpdateTabCtrlHeight()
        self.DoSizing()
        active_tabctrl.DoShowHide()

        # adjust selected index
        if self._curpage >= page_idx:
            self._curpage += 1

        if select:
            self.SetSelectionToWindow(page)
        
        return True


    def DeletePage(self, page_idx):
        """
        Deletes a page at the given index. Calling this method will generate a page
        change event.

        :param `page_idx`: the page index to be deleted.

        @note: DeletePage() removes a tab from the multi-notebook, and destroys the window as well.
        """
        
        if page_idx >= self._tabs.GetPageCount():
            return False

        wnd = self._tabs.GetWindowFromIdx(page_idx)
        # hide the window in advance, as this will
        # prevent flicker
        wnd.Show(False)

        if not self.RemovePage(page_idx):
            return False

        wnd.Destroy()
        
        return True


    def RemovePage(self, page_idx):
        """
        Removes a page, without deleting the window pointer.

        :param `page_idx`: the page index to be removed.

        @note: DeletePage() removes a tab from the multi-notebook, but does not destroys the window.
        """
        
        # save active window pointer
        active_wnd = None
        if self._curpage >= 0:
            active_wnd = self._tabs.GetWindowFromIdx(self._curpage)

        # save pointer of window being deleted
        wnd = self._tabs.GetWindowFromIdx(page_idx)
        new_active = None

        # make sure we found the page
        if not wnd:
            return False

        # find out which onscreen tab ctrl owns this tab
        ctrl, ctrl_idx = self.FindTab(wnd)
        if not ctrl:
            return False

        is_curpage = (self._curpage == page_idx)
        is_active_in_split = ctrl.GetPage(ctrl_idx).active

        # remove the tab from main catalog
        if not self._tabs.RemovePage(wnd):
            return False

        # remove the tab from the onscreen tab ctrl
        ctrl.RemovePage(wnd)

        if is_active_in_split:
        
            ctrl_new_page_count = ctrl.GetPageCount()

            if ctrl_idx >= ctrl_new_page_count:
                ctrl_idx = ctrl_new_page_count - 1

            if ctrl_idx >= 0 and ctrl_idx < ctrl.GetPageCount():
            
                # set new page as active in the tab split
                ctrl.SetActivePage(ctrl_idx)

                # if the page deleted was the current page for the
                # entire tab control, then record the window
                # pointer of the new active page for activation
                if is_curpage:
                    new_active = ctrl.GetWindowFromIdx(ctrl_idx)
                
        else:
        
            # we are not deleting the active page, so keep it the same
            new_active = active_wnd
        
        if not new_active:
        
            # we haven't yet found a new page to active,
            # so select the next page from the main tab
            # catalogue

            if page_idx < self._tabs.GetPageCount():
                new_active = self._tabs.GetPage(page_idx).window
            
            if not new_active and self._tabs.GetPageCount() > 0:
                new_active = self._tabs.GetPage(0).window
            
        self.RemoveEmptyTabFrames()

        # set new active pane
        if new_active:
            if not self.IsBeingDeleted():
                self._curpage = -1
                self.SetSelectionToWindow(new_active)
        else:
            self._curpage = -1
            self._tabs.SetNoneActive()

        return True


    def GetPageIndex(self, page_wnd):
        """
        Returns the page index for the specified window. If the window is not
        found in the notebook, wx.NOT_FOUND is returned.
        """

        return self._tabs.GetIdxFromWindow(page_wnd)


    def SetPageText(self, page_idx, text):
        """
        Sets the tab label for the page.

        :param `page_idx`: the page index;
        :param `text`: the new tab label.
        """

        if page_idx >= self._tabs.GetPageCount():
            return False

        # update our own tab catalog
        page_info = self._tabs.GetPage(page_idx)
        page_info.caption = text

        # update what's on screen
        ctrl, ctrl_idx = self.FindTab(page_info.window)
        if not ctrl:
            return False
                
        info = ctrl.GetPage(ctrl_idx)
        info.caption = text
        ctrl.Refresh()
        ctrl.Update()
    
        return True


    def GetPageText(self, page_idx):
        """
        Returns the tab label for the page.

        :param `page_idx`: the page index.
        """
        
        if page_idx >= self._tabs.GetPageCount():
            return ""

        # update our own tab catalog
        page_info = self._tabs.GetPage(page_idx)
        return page_info.caption


    def SetPageBitmap(self, page_idx, bitmap):
        """
        Sets the tab bitmap for the page.

        :param `page_idx`: the page index;
        :param `bitmap`: an instance of wx.Bitmap.
        """
        
        if page_idx >= self._tabs.GetPageCount():
            return False

        # update our own tab catalog
        page_info = self._tabs.GetPage(page_idx)
        page_info.bitmap = bitmap
        if bitmap.IsOk() and not page_info.dis_bitmap.IsOk():
            page_info.dis_bitmap = MakeDisabledBitmap(bitmap)

        # tab height might have changed
        self.UpdateTabCtrlHeight()

        # update what's on screen
        ctrl, ctrl_idx = self.FindTab(page_info.window)
        if not ctrl:
            return False
        
        info = ctrl.GetPage(ctrl_idx)
        info.bitmap = bitmap
        info.dis_bitmap = page_info.dis_bitmap
        ctrl.Refresh()
        ctrl.Update()
        
        return True


    def GetPageBitmap(self, page_idx):
        """
        Returns the tab bitmap for the page.

        :param `page_idx`: the page index.
        """
        
        if page_idx >= self._tabs.GetPageCount():
            return wx.NullBitmap

        # update our own tab catalog
        page_info = self._tabs.GetPage(page_idx)
        return page_info.bitmap


    def SetImageList(self, imageList):
        """
        Sets the image list for the AuiNotebook control.

        :param `imageList`: an instance of L{wx.ImageList}.
        """

        self._imageList = imageList        
                

    def AssignImageList(self, imageList):
        """
        Sets the image list for the AuiNotebook control.

        :param `imageList`: an instance of L{wx.ImageList}.
        """

        self.SetImageList(imageList)


    def GetImageList(self):
        """ Returns the associated image list (if any). """

        return self._imageList        


    def SetPageImage(self, page, image):
        """
        Sets the image index for the given page.

        :param `page`: the page index;
        :param `image`: an index into the image list which was set with L{SetImageList}.
        """
        
        if page >= self._tabs.GetPageCount():
            return False

        if not isinstance(image, types.IntType):
            raise Exception("The image parameter must be an integer, you passed " \
                            "%s"%repr(image))
        
        if not self._imageList:
            raise Exception("To use SetPageImage you need to associate an image list " \
                            "Using SetImageList or AssignImageList")

        if image >= self._imageList.GetImageCount():
            raise Exception("Invalid image index (%d), the image list contains only" \
                            " (%d) bitmaps"%(image, self._imageList.GetImageCount()))

        if image == -1:
            self.SetPageBitmap(page, wx.NullBitmap)
            return
        
        bitmap = self._imageList.GetBitmap(image)
        self.SetPageBitmap(page, bitmap)


    def GetPageImage(self, page):
        """
        Returns the image index for the given page.

        :param `page`: the given page for which to retrieve the image index.
        """

        if page >= self._tabs.GetPageCount():
            return False

        bitmap = self.GetPageBitmap(page)
        for indx in xrange(self._imageList.GetImageCount()):
            imgListBmp = self._imageList.GetBitmap(indx)
            if imgListBmp == bitmap:
                return indx

        return wx.NOT_FOUND


    def SetPageTextColour(self, page_idx, colour):
        """
        Sets the tab text colour for the page.

        :param `page_idx`: the page index;
        :param `colour`: an instance of wx.Colour.
        """
        
        if page_idx >= self._tabs.GetPageCount():
            return False

        # update our own tab catalog
        page_info = self._tabs.GetPage(page_idx)
        page_info.text_colour = colour

        # update what's on screen
        ctrl, ctrl_idx = self.FindTab(page_info.window)
        if not ctrl:
            return False
        
        info = ctrl.GetPage(ctrl_idx)
        info.text_colour = page_info.text_colour
        ctrl.Refresh()
        ctrl.Update()
        
        return True


    def GetPageTextColour(self, page_idx):
        """
        Returns the tab text colour for the page.

        :param `page_idx`: the page index.
        """
        
        if page_idx >= self._tabs.GetPageCount():
            return wx.NullColour

        # update our own tab catalog
        page_info = self._tabs.GetPage(page_idx)
        return page_info.text_colour
                
        
    def GetSelection(self):
        """ Returns the index of the currently active page. """
        
        return self._curpage


    def GetCurrentPage(self):
        """ Returns the currently active page (not the index). """

        if self._curpage >= 0 and self._curpage < self._tabs.GetPageCount():
            return self.GetPage(self._curpage)

        return None
    

    def EnsureVisible(self, indx):
        """
        Ensures the input page index `indx` is visible.
        
        :param `indx`: the page index.
        """

        self._tabs.MakeTabVisible(indx, self)
        

    def SetSelection(self, new_page, force=False):
        """
        Sets the page selection. Calling this method will generate a page change event.

        :param `new_page`: the index of the new selection;
        :param `force`: whether to force the selection or not.
        """
        
        wnd = self._tabs.GetWindowFromIdx(new_page)
        if not wnd or not self.GetEnabled(new_page):
            return self._curpage

        # don't change the page unless necessary
        # however, clicking again on a tab should give it the focus.
        if new_page == self._curpage and not force:
        
            ctrl, ctrl_idx = self.FindTab(wnd)            
            if wx.Window.FindFocus() != ctrl:
                ctrl.SetFocus()
            
            return self._curpage
        
        evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, self.GetId())
        evt.SetSelection(new_page)
        evt.SetOldSelection(self._curpage)
        evt.SetEventObject(self)

        if not self.GetEventHandler().ProcessEvent(evt) or evt.IsAllowed():
        
            old_curpage = self._curpage
            self._curpage = new_page

            # program allows the page change
            evt.SetEventType(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGED)
            self.GetEventHandler().ProcessEvent(evt)

            if not evt.IsAllowed(): # event is no longer allowed after handler
                return self._curpage
        
            ctrl, ctrl_idx = self.FindTab(wnd)
            
            if ctrl:
                self._tabs.SetActivePage(wnd)
                ctrl.SetActivePage(ctrl_idx)
                self.DoSizing()
                ctrl.DoShowHide()
                ctrl.MakeTabVisible(ctrl_idx, ctrl)

                # set fonts
                all_panes = self._mgr.GetAllPanes()
                for pane in all_panes:
                    if pane.name == "dummy":
                        continue
                    
                    tabctrl = pane.window._tabs
                    if tabctrl != ctrl:
                        tabctrl.SetSelectedFont(self._normal_font)
                    else:
                        tabctrl.SetSelectedFont(self._selected_font)
                        
                    tabctrl.Refresh()
                    tabctrl.Update()
                
                # Set the focus to the page if we're not currently focused on the tab.
                # This is Firefox-like behaviour.
                if wnd.IsShownOnScreen() and wx.Window.FindFocus() != ctrl:
                    wnd.SetFocus()

                return old_curpage
            
        return self._curpage


    def SetSelectionToWindow(self, win):
        """
        Sets the selection based on the input window `win`.

        :param `win`: a wx.Window derived window.
        """

        idx = self._tabs.GetIdxFromWindow(win)
        
        if idx == wx.NOT_FOUND:
            raise Exception("invalid notebook page")

        if not self.GetEnabled(idx):
            return
        
        # since a tab was clicked, let the parent know that we received
        # the focus, even if we will assign that focus immediately
        # to the child tab in the SetSelection call below
        # (the child focus event will also let AuiManager, if any,
        # know that the notebook control has been activated)

        parent = self.GetParent()
        if parent:
            eventFocus = wx.ChildFocusEvent(self)
            parent.GetEventHandler().ProcessEvent(eventFocus)

        self.SetSelection(idx)


    def SetSelectionToPage(self, page):
        """
        Sets the selection based on the input page.

        :param `page`: an instance of L{AuiNotebookPage}.
        """
        
        self.SetSelectionToWindow(page.window)


    def GetPageCount(self):
        """ Returns the number of pages in the notebook. """

        return self._tabs.GetPageCount()


    def GetPage(self, page_idx):
        """
        Returns the page specified by the given index.

        :param `page_idx`: the page index.
        """

        if page_idx >= self._tabs.GetPageCount():
            raise Exception("invalid notebook page")

        return self._tabs.GetWindowFromIdx(page_idx)


    def GetEnabled(self, page_idx):
        """
        Returns whether the page specified by the index `page_idx` is enabled.

        :param `page_idx`: the page index.
        """
        
        return self._tabs.GetEnabled(page_idx)


    def EnableTab(self, page_idx, enable=True):
        """
        Enables/disables a page in the notebook.

        :param `page_idx`: the page index;
        :param `enable`: True to enable the page, False to disable it.
        """

        self._tabs.EnableTab(page_idx, enable)
        self.Refresh()

    
    def DoSizing(self):
        """ Performs all sizing operations in each tab control. """

        all_panes = self._mgr.GetAllPanes()
        for pane in all_panes:
            if pane.name == "dummy":
                continue

            tabframe = pane.window
            tabframe.DoSizing()
        

    def GetAuiManager(self):
        """ Returns the associated L{AuiManager}. """

        return self._mgr
    

    def GetActiveTabCtrl(self):
        """
        GetActiveTabCtrl() returns the active tab control. It is
        called to determine which control gets new windows being added.
        """

        if self._curpage >= 0 and self._curpage < self._tabs.GetPageCount():

            # find the tab ctrl with the current page
            ctrl, idx = self.FindTab(self._tabs.GetPage(self._curpage).window)
            if ctrl:            
                return ctrl
        
        # no current page, just find the first tab ctrl
        all_panes = self._mgr.GetAllPanes()
        for pane in all_panes:
            if pane.name == "dummy":
                continue

            tabframe = pane.window
            return tabframe._tabs
        
        # If there is no tabframe at all, create one
        tabframe = TabFrame()
        tabframe.SetTabCtrlHeight(self._tab_ctrl_height)
        self._tab_id_counter += 1
        tabframe._tabs = AuiTabCtrl(self, self._tab_id_counter)
        
        tabframe._tabs.SetFlags(self._flags)
        tabframe._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone())
        self._mgr.AddPane(tabframe, framemanager.AuiPaneInfo().Center().CaptionVisible(False).
                          PaneBorder((self._flags & AUI_NB_SUB_NOTEBOOK) == 0))

        self._mgr.Update()

        return tabframe._tabs


    def FindTab(self, page):
        """
        FindTab() finds the tab control that currently contains the window as well
        as the index of the window in the tab control. It returns True if the
        window was found, otherwise False.

        :param `page`: an instance of L{AuiNotebookPage}.        
        """

        all_panes = self._mgr.GetAllPanes()
        for pane in all_panes:
            if pane.name == "dummy":
                continue

            tabframe = pane.window

            page_idx = tabframe._tabs.GetIdxFromWindow(page)
            
            if page_idx != -1:
            
                ctrl = tabframe._tabs
                idx = page_idx
                return ctrl, idx
            
        return None, wx.NOT_FOUND


    def Split(self, page, direction):
        """
        Split performs a split operation programmatically.

        :param `page`: indicates the page that will be split off. This page will also become
         the active page after the split.
        :param `direction`: specifies where the pane should go, it should be one of the
         following: ``wx.TOP``, ``wx.BOTTOM``, ``wx.LEFT``, or ``wx.RIGHT``.
        """
        
        cli_size = self.GetClientSize()

        # get the page's window pointer
        wnd = self.GetPage(page)
        if not wnd:
            return

        # notebooks with 1 or less pages can't be split
        if self.GetPageCount() < 2:
            return

        # find out which tab control the page currently belongs to

        src_tabs, src_idx = self.FindTab(wnd)
        if not src_tabs:
            return
        
        # choose a split size
        if self.GetPageCount() > 2:
            split_size = self.CalculateNewSplitSize()
        else:        
            # because there are two panes, always split them
            # equally
            split_size = self.GetClientSize()
            split_size.x /= 2
            split_size.y /= 2
        
        # create a new tab frame
        new_tabs = TabFrame()
        new_tabs._rect = wx.RectPS(wx.Point(0, 0), split_size)
        new_tabs.SetTabCtrlHeight(self._tab_ctrl_height)
        self._tab_id_counter += 1
        new_tabs._tabs = AuiTabCtrl(self, self._tab_id_counter)
        
        new_tabs._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone())
        new_tabs._tabs.SetFlags(self._flags)
        dest_tabs = new_tabs._tabs

        # create a pane info structure with the information
        # about where the pane should be added
        pane_info = framemanager.AuiPaneInfo().Bottom().CaptionVisible(False)

        if direction == wx.LEFT:
        
            pane_info.Left()
            mouse_pt = wx.Point(0, cli_size.y/2)
        
        elif direction == wx.RIGHT:
        
            pane_info.Right()
            mouse_pt = wx.Point(cli_size.x, cli_size.y/2)
        
        elif direction == wx.TOP:
        
            pane_info.Top()
            mouse_pt = wx.Point(cli_size.x/2, 0)
        
        elif direction == wx.BOTTOM:
        
            pane_info.Bottom()
            mouse_pt = wx.Point(cli_size.x/2, cli_size.y)
        
        self._mgr.AddPane(new_tabs, pane_info, mouse_pt)
        self._mgr.Update()

        # remove the page from the source tabs
        page_info = src_tabs.GetPage(src_idx)
        page_info.active = False
        src_tabs.RemovePage(page_info.window)
        
        if src_tabs.GetPageCount() > 0:
            src_tabs.SetActivePage(0)
            src_tabs.DoShowHide()
            src_tabs.Refresh()
        
        # add the page to the destination tabs
        dest_tabs.InsertPage(page_info.window, page_info, 0)

        if src_tabs.GetPageCount() == 0:
            self.RemoveEmptyTabFrames()
        
        self.DoSizing()
        dest_tabs.DoShowHide()
        dest_tabs.Refresh()

        # force the set selection function reset the selection
        self._curpage = -1

        # set the active page to the one we just split off
        self.SetSelectionToPage(page_info)

        self.UpdateHintWindowSize()


    def UnSplit(self):
        """ Restores original view after Tab Splits. """

        self.Freeze()
        
        # remember the tab now selected
        nowSelected = self.GetSelection()
        # select first tab as destination
        self.SetSelection(0)
        # iterate all other tabs
        for idx in xrange(1, self.GetPageCount()):
            # get win reference
            win = self.GetPage(idx)
            # get tab title
            title = self.GetPageText(idx)
            # get page bitmap
            bmp = self.GetPageBitmap(idx)
            # remove from notebook
            self.RemovePage(idx)
            # re-add in the same position so it will tab
            self.InsertPage(idx, win, title, False, bmp)
        # restore orignial selected tab
        self.SetSelection(nowSelected)

        self.Thaw()


    def UnsplitDClick(self, part, sash_size, pos):
        """
        Unsplit the L{AuiNotebook} on sash double-click.

        :param `part`: an UI part representing the sash;
        :param `sash_size`: the sash size;
        :param `pos`: the double-click mouse position.
        """

        if not self._flags & AUI_NB_SASH_DCLICK_UNSPLIT:
            # Unsplit not allowed
            return

        pos1 = wx.Point(*pos)
        pos2 = wx.Point(*pos)
        if part.orientation == wx.HORIZONTAL:
            pos1.y -= 2*sash_size
            pos2.y += 2*sash_size + self.GetTabCtrlHeight()
        elif part.orientation == wx.VERTICAL:
            pos1.x -= 2*sash_size
            pos2.x += 2*sash_size
        else:
            raise Exception("Invalid UI part orientation")

        pos1, pos2 = self.ClientToScreen(pos1), self.ClientToScreen(pos2)
        win1, win2 = wx.FindWindowAtPoint(pos1), wx.FindWindowAtPoint(pos2)

        if not win1 or not win2:
            # How did we get here?
            return

        if isinstance(win1, AuiNotebook) or isinstance(win2, AuiNotebook):
            # This is a bug on MSW, for diabled pages wx.FindWindowAtPoint
            # returns the wrong window.
            # See http://trac.wxwidgets.org/ticket/2942
            return

        tab_frame1, tab_frame2 = self.GetTabFrameFromWindow(win1), self.GetTabFrameFromWindow(win2)

        if not tab_frame1 or not tab_frame2:
            return

        tab_ctrl_1, tab_ctrl_2 = tab_frame1._tabs, tab_frame2._tabs

        if tab_ctrl_1.GetPageCount() > tab_ctrl_2.GetPageCount():
            src_tabs = tab_ctrl_2
            dest_tabs = tab_ctrl_1
        else:
            src_tabs = tab_ctrl_1
            dest_tabs = tab_ctrl_2

        selection = -1
        page_count = dest_tabs.GetPageCount()
        
        for page in xrange(src_tabs.GetPageCount()):
            # remove the page from the source tabs
            page_info = src_tabs.GetPage(page)
            if page_info.active:
                selection = page_count + page
            src_tabs.RemovePage(page_info.window)

            # add the page to the destination tabs
            dest_tabs.AddPage(page_info.window, page_info)
        
        self.RemoveEmptyTabFrames()

        dest_tabs.DoShowHide()
        self.DoSizing()
        dest_tabs.Refresh()
        if selection > 0:
            wx.CallAfter(dest_tabs.MakeTabVisible, selection, self)
        
    
    def OnSize(self, event):
        """
        Handles the wx.EVT_SIZE event for L{AuiNotebook}.

        :param `event`: a L{wx.SizeEvent} event to be processed.        
        """
        
        self.UpdateHintWindowSize()
        event.Skip()


    def OnTabClicked(self, event):
        """
        Handles the EVT_AUINOTEBOOK_PAGE_CHANGING event for L{AuiNotebook}.

        :param `event`: a L{wx.EVT_AUINOTEBOOK_PAGE_CHANGING} event to be processed.        
        """
        
        ctrl = event.GetEventObject()
        assert ctrl != None

        wnd = ctrl.GetWindowFromIdx(event.GetSelection())
        assert wnd != None

        self.SetSelectionToWindow(wnd)


    def OnTabBgDClick(self, event):
        """
        Handles the EVT_AUINOTEBOOK_BG_DCLICK event for L{AuiNotebook}.

        :param `event`: a L{wx.EVT_AUINOTEBOOK_BG_DCLICK} event to be processed.        
        """
        
        # notify owner that the tabbar background has been double-clicked
        e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK, self.GetId())
        e.SetEventObject(self)
        self.GetEventHandler().ProcessEvent(e)


    def OnTabDClick(self, event):
        """
        Handles the EVT_AUINOTEBOOK_TAB_DCLICK event for L{AuiNotebook}.

        :param `event`: a L{wx.EVT_AUINOTEBOOK_TAB_DCLICK} event to be processed.        
        """

        # notify owner that the tabbar background has been double-clicked
        e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_DCLICK, self.GetId())
        e.SetEventObject(self)
        self.GetEventHandler().ProcessEvent(e)
        

    def OnTabBeginDrag(self, event):
        """
        Handles the EVT_AUINOTEBOOK_BEGIN_DRAG event for L{AuiNotebook}.

        :param `event`: a L{wx.EVT_AUINOTEBOOK_BEGIN_DRAG} event to be processed.        
        """

        tabs = event.GetEventObject()
        if not tabs.GetEnabled(event.GetSelection()):
            return

        self._last_drag_x = 0


    def OnTabDragMotion(self, event):
        """
        Handles the EVT_AUINOTEBOOK_DRAG_MOTION event for L{AuiNotebook}.

        :param `event`: a L{wx.EVT_AUINOTEBOOK_DRAG_MOTION} event to be processed.        
        """

        tabs = event.GetEventObject()
        if not tabs.GetEnabled(event.GetSelection()):
            return

        screen_pt = wx.GetMousePosition()
        client_pt = self.ScreenToClient(screen_pt)
        zero = wx.Point(0, 0)

        src_tabs = event.GetEventObject()
        dest_tabs = self.GetTabCtrlFromPoint(client_pt)
        
        if dest_tabs == src_tabs:
            if src_tabs:
                src_tabs.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
            
            # always hide the hint for inner-tabctrl drag
            self._mgr.HideHint()

            # if tab moving is not allowed, leave
            if not self._flags & AUI_NB_TAB_MOVE:
                return
            
            pt = dest_tabs.ScreenToClient(screen_pt)

            # this is an inner-tab drag/reposition
            dest_location_tab = dest_tabs.TabHitTest(pt.x, pt.y)
            
            if dest_location_tab:
            
                src_idx = event.GetSelection()
                dest_idx = dest_tabs.GetIdxFromWindow(dest_location_tab)

                # prevent jumpy drag
                if (src_idx == dest_idx) or dest_idx == -1 or \
                   (src_idx > dest_idx and self._last_drag_x <= pt.x) or \
                   (src_idx < dest_idx and self._last_drag_x >= pt.x):
                
                    self._last_drag_x = pt.x
                    return
                
                src_tab = dest_tabs.GetWindowFromIdx(src_idx)
                dest_tabs.MovePage(src_tab, dest_idx)
                dest_tabs.SetActivePage(dest_idx)
                dest_tabs.DoShowHide()
                dest_tabs.Refresh()
                self._last_drag_x = pt.x

            return

        # if external drag is allowed, check if the tab is being dragged
        # over a different AuiNotebook control
        if self._flags & AUI_NB_TAB_EXTERNAL_MOVE:
        
            tab_ctrl = wx.FindWindowAtPoint(screen_pt)

            # if we aren't over any window, stop here
            if not tab_ctrl:
                if self._flags & AUI_NB_TAB_FLOAT:
                    if self.IsMouseWellOutsideWindow():
                        hintRect = wx.RectPS(screen_pt, (400, 300))
                        # Use CallAfter so we overwrite the hint that might be 
                        # shown by our superclass: 
                        wx.CallAfter(self._mgr.ShowHint, hintRect) 
                return

            # make sure we are not over the hint window
            if not isinstance(tab_ctrl, wx.Frame):
                while tab_ctrl:
                    if isinstance(tab_ctrl, AuiTabCtrl):
                        break
                    
                    tab_ctrl = tab_ctrl.GetParent()
                
                if tab_ctrl:
                    nb = tab_ctrl.GetParent()

                    if nb != self:
                    
                        hint_rect = tab_ctrl.GetClientRect()
                        hint_rect.x, hint_rect.y = tab_ctrl.ClientToScreenXY(hint_rect.x, hint_rect.y)
                        self._mgr.ShowHint(hint_rect)
                        return
                    
            else:
            
                if not dest_tabs:
                    # we are either over a hint window, or not over a tab
                    # window, and there is no where to drag to, so exit
                    return

        if self._flags & AUI_NB_TAB_FLOAT:
            if self.IsMouseWellOutsideWindow():
                hintRect = wx.RectPS(screen_pt, (400, 300))
                # Use CallAfter so we overwrite the hint that might be 
                # shown by our superclass: 
                wx.CallAfter(self._mgr.ShowHint, hintRect)
                return
                        
        # if there are less than two panes, split can't happen, so leave
        if self._tabs.GetPageCount() < 2:
            return

        # if tab moving is not allowed, leave
        if not self._flags & AUI_NB_TAB_SPLIT:
            return

        if src_tabs:
            src_tabs.SetCursor(wx.StockCursor(wx.CURSOR_SIZING))
        
        if dest_tabs:
            
            hint_rect = dest_tabs.GetRect()
            hint_rect.x, hint_rect.y = self.ClientToScreenXY(hint_rect.x, hint_rect.y)
            self._mgr.ShowHint(hint_rect)
        
        else:
            rect = self._mgr.CalculateHintRect(self._dummy_wnd, client_pt, zero)
            if rect.IsEmpty():
                self._mgr.HideHint()
                return
            
            hit_wnd = wx.FindWindowAtPoint(screen_pt)
            if hit_wnd and not isinstance(hit_wnd, AuiNotebook):
                tab_frame = self.GetTabFrameFromWindow(hit_wnd)
                if tab_frame:
                    hint_rect = wx.Rect(*tab_frame._rect)
                    hint_rect.x, hint_rect.y = self.ClientToScreenXY(hint_rect.x, hint_rect.y)
                    rect.Intersect(hint_rect)
                    self._mgr.ShowHint(rect)
                else:
                    self._mgr.DrawHintRect(self._dummy_wnd, client_pt, zero)
            else:
                self._mgr.DrawHintRect(self._dummy_wnd, client_pt, zero)
        

    def OnTabEndDrag(self, event):
        """
        Handles the EVT_AUINOTEBOOK_END_DRAG event for L{AuiNotebook}.

        :param `event`: a L{wx.EVT_AUINOTEBOOK_END_DRAG} event to be processed.        
        """

        tabs = event.GetEventObject()
        if not tabs.GetEnabled(event.GetSelection()):
            return

        self._mgr.HideHint()

        src_tabs = event.GetEventObject()
        if not src_tabs:
            raise Exception("no source object?")

        src_tabs.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))

        # get the mouse position, which will be used to determine the drop point
        mouse_screen_pt = wx.GetMousePosition()
        mouse_client_pt = self.ScreenToClient(mouse_screen_pt)

        # check for an external move
        if self._flags & AUI_NB_TAB_EXTERNAL_MOVE:
            tab_ctrl = wx.FindWindowAtPoint(mouse_screen_pt)

            while tab_ctrl:
            
                if isinstance(tab_ctrl, AuiTabCtrl):
                    break
                
                tab_ctrl = tab_ctrl.GetParent()
            
            if tab_ctrl:
            
                nb = tab_ctrl.GetParent()

                if nb != self:
                
                    # find out from the destination control
                    # if it's ok to drop this tab here
                    e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_ALLOW_DND, self.GetId())
                    e.SetSelection(event.GetSelection())
                    e.SetOldSelection(event.GetSelection())
                    e.SetEventObject(self)
                    e.SetDragSource(self)
                    e.Veto() # dropping must be explicitly approved by control owner

                    nb.GetEventHandler().ProcessEvent(e)

                    if not e.IsAllowed():
                    
                        # no answer or negative answer
                        self._mgr.HideHint()
                        return
                    
                    # drop was allowed
                    src_idx = event.GetSelection()
                    src_page = src_tabs.GetWindowFromIdx(src_idx)

                    # Check that it's not an impossible parent relationship
                    p = nb
                    while p and not p.IsTopLevel():
                        if p == src_page:
                            return
                        
                        p = p.GetParent()

                    # get main index of the page
                    main_idx = self._tabs.GetIdxFromWindow(src_page)
                    if main_idx == wx.NOT_FOUND:
                        raise Exception("no source page?")

                    # make a copy of the page info
                    page_info = self._tabs.GetPage(main_idx)

                    # remove the page from the source notebook
                    self.RemovePage(main_idx)

                    # reparent the page
                    src_page.Reparent(nb)

                    # found out the insert idx
                    dest_tabs = tab_ctrl
                    pt = dest_tabs.ScreenToClient(mouse_screen_pt)

                    target = dest_tabs.TabHitTest(pt.x, pt.y)
                    insert_idx = -1
                    if target:
                        insert_idx = dest_tabs.GetIdxFromWindow(target)

                    # add the page to the new notebook
                    if insert_idx == -1:
                        insert_idx = dest_tabs.GetPageCount()
                        
                    dest_tabs.InsertPage(page_info.window, page_info, insert_idx)
                    nb._tabs.AddPage(page_info.window, page_info)

                    nb.DoSizing()
                    dest_tabs.DoShowHide()
                    dest_tabs.Refresh()

                    # set the selection in the destination tab control
                    nb.SetSelectionToPage(page_info)

                    # notify owner that the tab has been dragged
                    e2 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, self.GetId())
                    e2.SetSelection(event.GetSelection())
                    e2.SetOldSelection(event.GetSelection())
                    e2.SetEventObject(self)
                    self.GetEventHandler().ProcessEvent(e2)

                    return

        if self._flags & AUI_NB_TAB_FLOAT:
            self._mgr.HideHint() 
            if self.IsMouseWellOutsideWindow(): 
                # Use CallAfter so we our superclass can deal with the event first
                wx.CallAfter(self.FloatPage, self.GetSelection())
                event.Skip()
                return
        
        # only perform a tab split if it's allowed
        dest_tabs = None

        if self._flags & AUI_NB_TAB_SPLIT and self._tabs.GetPageCount() >= 2:
        
            # If the pointer is in an existing tab frame, do a tab insert
            hit_wnd = wx.FindWindowAtPoint(mouse_screen_pt)
            tab_frame = self.GetTabFrameFromTabCtrl(hit_wnd)
            insert_idx = -1
            
            if tab_frame:
            
                dest_tabs = tab_frame._tabs

                if dest_tabs == src_tabs:
                    return

                pt = dest_tabs.ScreenToClient(mouse_screen_pt)
                target = dest_tabs.TabHitTest(pt.x, pt.y)
                
                if target:                
                    insert_idx = dest_tabs.GetIdxFromWindow(target)
                
            else:
            
                zero = wx.Point(0, 0)
                rect = self._mgr.CalculateHintRect(self._dummy_wnd, mouse_client_pt, zero)
                
                if rect.IsEmpty():
                    # there is no suitable drop location here, exit out
                    return
                
                # If there is no tabframe at all, create one
                new_tabs = TabFrame()
                new_tabs._rect = wx.RectPS(wx.Point(0, 0), self.CalculateNewSplitSize())
                new_tabs.SetTabCtrlHeight(self._tab_ctrl_height)
                self._tab_id_counter += 1
                new_tabs._tabs = AuiTabCtrl(self, self._tab_id_counter)
                new_tabs._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone())
                new_tabs._tabs.SetFlags(self._flags)

                self._mgr.AddPane(new_tabs, framemanager.AuiPaneInfo().Bottom().CaptionVisible(False), mouse_client_pt)
                self._mgr.Update()
                dest_tabs = new_tabs._tabs
            
            # remove the page from the source tabs
            page_info = src_tabs.GetPage(event.GetSelection())
            page_info.active = False
            src_tabs.RemovePage(page_info.window)

            if src_tabs.GetPageCount() > 0:            
                src_tabs.SetActivePage(0)
                src_tabs.DoShowHide()
                src_tabs.Refresh()

            # add the page to the destination tabs
            if insert_idx == -1:
                insert_idx = dest_tabs.GetPageCount()
                
            dest_tabs.InsertPage(page_info.window, page_info, insert_idx)
            
            if src_tabs.GetPageCount() == 0:
                self.RemoveEmptyTabFrames()

            self.DoSizing()
            dest_tabs.DoShowHide()
            dest_tabs.Refresh()

            # force the set selection function reset the selection
            self._curpage = -1

            # set the active page to the one we just split off
            self.SetSelectionToPage(page_info)

            self.UpdateHintWindowSize()
        
        # notify owner that the tab has been dragged
        e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, self.GetId())
        e.SetSelection(event.GetSelection())
        e.SetOldSelection(event.GetSelection())
        e.SetEventObject(self)
        self.GetEventHandler().ProcessEvent(e)


    def OnTabCancelDrag(self, event):
        """
        Handles the EVT_AUINOTEBOOK_CANCEL_DRAG event for L{AuiNotebook}.

        :param `event`: a L{wx.EVT_AUINOTEBOOK_CANCEL_DRAG} event to be processed.        
        """

        tabs = event.GetEventObject()
        if not tabs.GetEnabled(event.GetSelection()):
            return

        self._mgr.HideHint()

        src_tabs = event.GetEventObject()
        if not src_tabs:
            raise Exception("no source object?")

        src_tabs.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))


    def IsMouseWellOutsideWindow(self):
        """ Returns whether the mouse is well outside the AuiNotebook screen rectangle. """
        
        screen_rect = self.GetScreenRect() 
        screen_rect.Inflate(50, 50)
        
        return not screen_rect.Contains(wx.GetMousePosition())


    def FloatPage(self, page_index):
        """
        Float the page in `page_index` by reparenting it to a floating frame.

        :param `page_index`: the index of the page to be floated.
        """

        root_manager = framemanager.GetManager(self)
        page_title = self.GetPageText(page_index) 
        page_contents = self.GetPage(page_index)
        page_bitmap = self.GetPageBitmap(page_index)
        text_colour = self.GetPageTextColour(page_index)
        
        self.RemovePage(page_index)
        self.RemoveEmptyTabFrames()
        
        if root_manager and root_manager != self._mgr:
            root_manager = framemanager.GetManager(self)

            if hasattr(page_contents, "__floating_size__"):
                floating_size = page_contents.__floating_size__
            else:
                floating_size = page_contents.GetBestSize()
                if floating_size == wx.DefaultSize:
                    floating_size = wx.Size(300, 200)

            page_contents.__page_index__ = page_index
            page_contents.__aui_notebook__ = self
            page_contents.__text_colour__ = text_colour
            
            pane_info = framemanager.AuiPaneInfo().Float().FloatingPosition(wx.GetMousePosition()). \
                        FloatingSize(floating_size).Name("__floating__%s"%page_title). \
                        Caption(page_title).Icon(page_bitmap)
            root_manager.AddPane(page_contents, pane_info)
            root_manager.Bind(framemanager.EVT_AUI_PANE_CLOSE, self.OnCloseFloatingPage)
            self.GetActiveTabCtrl().DoShowHide()
            self.DoSizing()
            root_manager.Update()
            
        else:
            frame = wx.Frame(self, title=page_title,
                             style=wx.DEFAULT_FRAME_STYLE|wx.FRAME_TOOL_WINDOW|
                                   wx.FRAME_FLOAT_ON_PARENT | wx.FRAME_NO_TASKBAR) 

            frame.bitmap = page_bitmap            
            frame.page_index = page_index
            frame.text_colour = text_colour
            page_contents.Reparent(frame) 
            frame.Bind(wx.EVT_CLOSE, self.OnCloseFloatingPage) 
            frame.Move(wx.GetMousePosition()) 
            frame.Show()


    def OnCloseFloatingPage(self, event):
        """
        Handles the wx.EVT_CLOSE event for a floating page in L{AuiNotebook}.

        :param `event`: a L{wx.CloseEvent} event to be processed.        
        """

        root_manager = framemanager.GetManager(self)
        if root_manager and root_manager != self._mgr:
            pane = event.pane
            if pane.name.startswith("__floating__"):
                self.ReDockPage(pane)
                return
                
            event.Skip()
        else:
            event.Skip()
            frame = event.GetEventObject() 
            page_title = frame.GetTitle() 
            page_contents = frame.GetChildren()[0] 
            page_contents.Reparent(self) 
            self.InsertPage(frame.page_index, page_contents, page_title, select=True, bitmap=frame.bitmap)
            self.SetPageTextColour(frame.page_index, frame.text_colour)


    def ReDockPage(self, pane):
        """
        Re-docks a floating L{AuiNotebook} tab in the original position, when possible.

        :param `pane`: an instance of L{framemanager.AuiPaneInfo}.
        """

        root_manager = framemanager.GetManager(self)        

        pane.window.__floating_size__ = pane.floating_size
        page_index = pane.window.__page_index__
        text_colour = pane.window.__text_colour__
        
        root_manager.DetachPane(pane.window)

        self.InsertPage(page_index, pane.window, pane.caption, True, pane.icon)
        self.SetPageTextColour(page_index, text_colour)
        
        self.GetActiveTabCtrl().DoShowHide()
        self.DoSizing()
        self._mgr.Update()
        root_manager.Update()
        
        
    def GetTabCtrlFromPoint(self, pt):
        """
        Returns the tab control at the specified point.

        :param `pt`: a wx.Point object.
        """

        # if we've just removed the last tab from the source
        # tab set, the remove the tab control completely
        all_panes = self._mgr.GetAllPanes()
        for pane in all_panes:
            if pane.name == "dummy":
                continue

            tabframe = pane.window
            if tabframe._tab_rect.Contains(pt):
                return tabframe._tabs
        
        return None


    def GetTabFrameFromTabCtrl(self, tab_ctrl):
        """
        Returns the tab frame associated with a tab control.

        :param `tab_ctrl`: an instance of L{AuiTabCtrl}.
        """

        # if we've just removed the last tab from the source
        # tab set, the remove the tab control completely
        all_panes = self._mgr.GetAllPanes()
        for pane in all_panes:
            if pane.name == "dummy":
                continue

            tabframe = pane.window
            if tabframe._tabs == tab_ctrl:            
                return tabframe
            
        return None


    def GetTabFrameFromWindow(self, wnd):
        """
        Returns the tab frame associated with a window.

        :param `wnd`: an instance of L{wx.Window}.
        """

        all_panes = self._mgr.GetAllPanes()
        for pane in all_panes:
            if pane.name == "dummy":
                continue

            tabframe = pane.window
            for page in tabframe._tabs.GetPages():
                if wnd == page.window:
                    return tabframe
            
        return None
    
        
    def RemoveEmptyTabFrames(self):
        """ Removes all the empty tab frames. """

        # if we've just removed the last tab from the source
        # tab set, the remove the tab control completely
        all_panes = self._mgr.GetAllPanes()

        for indx in xrange(len(all_panes)-1, -1, -1):
            pane = all_panes[indx]
            if pane.name == "dummy":
                continue

            tab_frame = pane.window
            if tab_frame._tabs.GetPageCount() == 0:
                self._mgr.DetachPane(tab_frame)
                tab_frame._tabs.Destroy()
                tab_frame._tabs = None
                del tab_frame

        # check to see if there is still a center pane
        # if there isn't, make a frame the center pane
        first_good = None
        center_found = False

        all_panes = self._mgr.GetAllPanes()
        for pane in all_panes:
            if pane.name == "dummy":
                continue
       
            if pane.dock_direction == AUI_DOCK_CENTRE:
                center_found = True
            if not first_good:
                first_good = pane.window
        
        if not center_found and first_good:
            self._mgr.GetPane(first_good).Centre()

        if not self.IsBeingDeleted():
            self._mgr.Update()


    def OnChildFocusNotebook(self, event):
        """
        Handles the wx.EVT_CHILD_FOCUS event for L{AuiNotebook}.

        :param `event`: a L{wx.ChildFocusEvent} event to be processed.        
        """
        
        # if we're dragging a tab, don't change the current selection.
        # This code prevents a bug that used to happen when the hint window
        # was hidden.  In the bug, the focus would return to the notebook
        # child, which would then enter this handler and call
        # SetSelection, which is not desired turn tab dragging.

        all_panes = self._mgr.GetAllPanes()
        for pane in all_panes:
            if pane.name == "dummy":
                continue
            tabframe = pane.window
            if tabframe._tabs.IsDragging():
                return

        # change the tab selection to the child
        # which was focused
        idx = self._tabs.GetIdxFromWindow(event.GetWindow())
        if idx != -1 and idx != self._curpage:
            self.SetSelection(idx)
        

    def SetNavigatorIcon(self, bmp):
        """
        Sets the icon used by the L{TabNavigatorWindow}.

        :param `bitmap`: an instance of wx.Bitmap.
        """
        
        if isinstance(bmp, wx.Bitmap) and bmp.IsOk():
            # Make sure image is proper size
            if bmp.GetSize() != (16, 16):
                img = bmp.ConvertToImage()
                img.Rescale(16, 16, wx.IMAGE_QUALITY_HIGH)
                bmp = wx.BitmapFromImage(img)
            self._naviIcon = bmp
        else:
            raise TypeError, "SetNavigatorIcon requires a valid bitmap"

        
    def OnNavigationKeyNotebook(self, event):
        """
        Handles the wx.EVT_NAVIGATION_KEY event for L{AuiNotebook}.

        :param `event`: a L{wx.NavigationKeyEvent} event to be processed.        
        """

        if event.IsWindowChange():
            if self.HasFlag(AUI_NB_SMART_TABS):
                if not self._popupWin:
                    self._popupWin = TabNavigatorWindow(self, self._naviIcon)
                    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
                # FIXME: the problem with this is that if we have a split notebook,
                # we selection may go all over the place.
                self.AdvanceSelection(event.GetDirection())
        
        else:
            # we get this event in 3 cases
            #
            # a) one of our pages might have generated it because the user TABbed
            # out from it in which case we should propagate the event upwards and
            # our parent will take care of setting the focus to prev/next sibling
            #
            # or
            #
            # b) the parent panel wants to give the focus to us so that we
            # forward it to our selected page. We can't deal with this in
            # OnSetFocus() because we don't know which direction the focus came
            # from in this case and so can't choose between setting the focus to
            # first or last panel child
            #
            # or
            #
            # c) we ourselves (see MSWTranslateMessage) generated the event
            #
            parent = self.GetParent()

            # the wxObject* casts are required to avoid MinGW GCC 2.95.3 ICE
            isFromParent = event.GetEventObject() == parent
            isFromSelf = event.GetEventObject() == self

            if isFromParent or isFromSelf:
            
                # no, it doesn't come from child, case (b) or (c): forward to a
                # page but only if direction is backwards (TAB) or from ourselves,
                if self.GetSelection() != wx.NOT_FOUND and (not event.GetDirection() or isFromSelf):
                
                    # so that the page knows that the event comes from it's parent
                    # and is being propagated downwards
                    event.SetEventObject(self)

                    page = self.GetPage(self.GetSelection())
                    if not page.GetEventHandler().ProcessEvent(event):                    
                        page.SetFocus()
                    
                    #else: page manages focus inside it itself
                
                else: # otherwise set the focus to the notebook itself
                
                    self.SetFocus()
                
            else:
            
                # send this event back for the 'wraparound' focus.
                winFocus = event.GetCurrentFocus()

                if winFocus:
                    event.SetEventObject(self)
                    winFocus.GetEventHandler().ProcessEvent(event)


    def OnTabButton(self, event):
        """
        Handles the EVT_AUINOTEBOOK_BUTTON event for L{AuiNotebook}.

        :param `event`: a EVT_AUINOTEBOOK_BUTTON event to be processed.        
        """

        tabs = event.GetEventObject()
        button_id = event.GetInt()

        if button_id == AUI_BUTTON_CLOSE:
            if not tabs.GetEnabled(event.GetSelection()):
                return

            selection = event.GetSelection()

            if selection == -1:
            
                # if the close button is to the right, use the active
                # page selection to determine which page to close
                selection = tabs.GetActivePage()
            
            if selection != -1:
            
                close_wnd = tabs.GetWindowFromIdx(selection)

                # ask owner if it's ok to close the tab
                e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE, self.GetId())
                idx = self._tabs.GetIdxFromWindow(close_wnd)
                e.SetSelection(idx)
                e.SetOldSelection(event.GetSelection())
                e.SetEventObject(self)
                self.GetEventHandler().ProcessEvent(e)
                if not e.IsAllowed():
                    return

                if repr(close_wnd.__class__).find("AuiMDIChildFrame") >= 0:
                    close_wnd.Close()
                
                else:
                    main_idx = self._tabs.GetIdxFromWindow(close_wnd)
                    self.DeletePage(main_idx)
                
                # notify owner that the tab has been closed
                e2 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSED, self.GetId())
                e2.SetSelection(idx)
                e2.SetEventObject(self)
                self.GetEventHandler().ProcessEvent(e2)

                if self.GetPageCount() == 0:
                    mgr = self.GetAuiManager()
                    win = mgr.GetManagedWindow()
                    win.SendSizeEvent()
            

    def OnTabMiddleDown(self, event):
        """
        Handles the EVT_AUINOTEBOOK_TAB_MIDDLE_DOWN event for L{AuiNotebook}.

        :param `event`: a EVT_AUINOTEBOOK_TAB_MIDDLE_DOWN event to be processed.        
        """
        
        tabs = event.GetEventObject()
        if not tabs.GetEnabled(event.GetSelection()):
            return

        # patch event through to owner
        wnd = tabs.GetWindowFromIdx(event.GetSelection())

        e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_DOWN, self.GetId())
        e.SetSelection(self._tabs.GetIdxFromWindow(wnd))
        e.SetEventObject(self)
        self.GetEventHandler().ProcessEvent(e)


    def OnTabMiddleUp(self, event):
        """
        Handles the EVT_AUINOTEBOOK_TAB_MIDDLE_UP event for L{AuiNotebook}.

        :param `event`: a EVT_AUINOTEBOOK_TAB_MIDDLE_UP event to be processed.        
        """
        
        tabs = event.GetEventObject()
        if not tabs.GetEnabled(event.GetSelection()):
            return

        # if the AUI_NB_MIDDLE_CLICK_CLOSE is specified, middle
        # click should act like a tab close action.  However, first
        # give the owner an opportunity to handle the middle up event
        # for custom action

        wnd = tabs.GetWindowFromIdx(event.GetSelection())

        e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_UP, self.GetId())
        e.SetSelection(self._tabs.GetIdxFromWindow(wnd))
        e.SetEventObject(self)
        if self.GetEventHandler().ProcessEvent(e):
            return
        if not e.IsAllowed():
            return

        # check if we are supposed to close on middle-up
        if self._flags & AUI_NB_MIDDLE_CLICK_CLOSE == 0:
            return

        # simulate the user pressing the close button on the tab
        event.SetInt(AUI_BUTTON_CLOSE)
        self.OnTabButton(event)


    def OnTabRightDown(self, event):
        """
        Handles the EVT_AUINOTEBOOK_TAB_RIGHT_DOWN event for L{AuiNotebook}.

        :param `event`: a EVT_AUINOTEBOOK_TAB_RIGHT_DOWN event to be processed.        
        """
        
        tabs = event.GetEventObject()
        if not tabs.GetEnabled(event.GetSelection()):
            return

        # patch event through to owner
        wnd = tabs.GetWindowFromIdx(event.GetSelection())

        e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_DOWN, self.GetId())
        e.SetSelection(self._tabs.GetIdxFromWindow(wnd))
        e.SetEventObject(self)
        self.GetEventHandler().ProcessEvent(e)


    def OnTabRightUp(self, event):
        """
        Handles the EVT_AUINOTEBOOK_TAB_RIGHT_UP event for L{AuiNotebook}.

        :param `event`: a EVT_AUINOTEBOOK_TAB_RIGHT_UP event to be processed.        
        """

        tabs = event.GetEventObject()
        if not tabs.GetEnabled(event.GetSelection()):
            return

        # patch event through to owner
        wnd = tabs.GetWindowFromIdx(event.GetSelection())

        e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP, self.GetId())
        e.SetSelection(self._tabs.GetIdxFromWindow(wnd))
        e.SetEventObject(self)
        self.GetEventHandler().ProcessEvent(e)


    def SetNormalFont(self, font):
        """
        Sets the normal font for drawing tab labels.

        :param `font`: a wx.Font object.
        """

        self._normal_font = font
        self.GetArtProvider().SetNormalFont(font)


    def SetSelectedFont(self, font):
        """
        Sets the selected tab font for drawing tab labels.

        :param `font`: a wx.Font object.
        """

        self._selected_font = font
        self.GetArtProvider().SetSelectedFont(font)


    def SetMeasuringFont(self, font):
        """
        Sets the font for calculating text measurements.

        :param `font`: a wx.Font object.
        """

        self.GetArtProvider().SetMeasuringFont(font)


    def SetFont(self, font):
        """
        Overridden from wx.PyControl.
        Sets the tab font.

        :param `font`: a wx.Font object.
        """
    
        wx.PyControl.SetFont(self, font)

        selectedFont = wx.Font(font.GetPointSize(), font.GetFamily(),
                               font.GetStyle(), wx.BOLD, font.GetUnderlined(),
                               font.GetFaceName(), font.GetEncoding())

        self.SetNormalFont(font)
        self.SetSelectedFont(selectedFont)
        self.SetMeasuringFont(selectedFont)

        return True


    def GetTabCtrlHeight(self):
        """ Returns the tab control height. """

        return self._tab_ctrl_height

    
    def GetHeightForPageHeight(self, pageHeight):
        """
        Gets the height of the notebook for a given page height.

        :param `pageHeight`: the given page height.
        """

        self.UpdateTabCtrlHeight()

        tabCtrlHeight = self.GetTabCtrlHeight()
        decorHeight = 2
        return tabCtrlHeight + pageHeight + decorHeight


    def AdvanceSelection(self, forward=True):
        """
        Cycles through the tabs.
        The call to this function generates the page changing events.

        :param `forward`: whether to advance forward or backward.        
        """

        tabCtrl = self.GetActiveTabCtrl()
        newPage = -1

        focusWin = tabCtrl.FindFocus()
        activePage = tabCtrl.GetActivePage()
        lenPages = len(tabCtrl.GetPages())
        
        if forward:
            if lenPages > 1:
            
                if activePage == -1 or activePage == lenPages - 1:
                    newPage = 0
                elif activePage < lenPages - 1:
                    newPage = activePage + 1
            
        else:
        
            if lenPages > 1:
                if activePage == -1 or activePage == 0:
                    newPage = lenPages - 1
                
                elif activePage > 0:
                    newPage = activePage - 1

        
        if newPage != -1:
            if not self.GetEnabled(newPage):
                return

            e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, tabCtrl.GetId())
            e.SetSelection(newPage)
            e.SetOldSelection(activePage)
            e.SetEventObject(tabCtrl)
            self.GetEventHandler().ProcessEvent(e)
        
        if focusWin:
            focusWin.SetFocus()


    def ShowWindowMenu(self):
        """
        Shows the window menu for the active tab control associated with this
        notebook, and returns True if a selection was made.
        """
        
        tabCtrl = self.GetActiveTabCtrl()
        idx = tabCtrl.GetArtProvider().ShowDropDown(tabCtrl, tabCtrl.GetPages(), tabCtrl.GetActivePage())

        if not self.GetEnabled(idx):
            return False

        if idx != -1:
            e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, tabCtrl.GetId())
            e.SetSelection(idx)
            e.SetOldSelection(tabCtrl.GetActivePage())
            e.SetEventObject(tabCtrl)
            self.GetEventHandler().ProcessEvent(e)

            return True
        
        else:
            
            return False


    def AddTabAreaButton(self, id, location, normal_bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap):
        """
        Adds a button in the tab area.

        :param `id`: the button identifier. This can be one of the following:

        ==============================  =================================
        Button Identifier               Description
        ==============================  =================================
        ``AUI_BUTTON_CLOSE``            Shows a close button on the tab area
        ``AUI_BUTTON_WINDOWLIST``       Shows a window list button on the tab area
        ``AUI_BUTTON_LEFT``             Shows a left button on the tab area
        ``AUI_BUTTON_RIGHT``            Shows a right button on the tab area
        ==============================  =================================        

        :param `location`: the button location. Can be ``wx.LEFT`` or ``wx.RIGHT``;
        :param `normal_bitmap`: the bitmap for an enabled tab;
        :param `disabled_bitmap`: the bitmap for a disabled tab.
        """

        active_tabctrl = self.GetActiveTabCtrl()
        active_tabctrl.AddButton(id, location, normal_bitmap, disabled_bitmap)


    def RemoveTabAreaButton(self, id):
        """
        Removes a button from the tab area.

        :param `id`: the button identifier. See L{AddButton} for a list of button identifiers.
        """

        active_tabctrl = self.GetActiveTabCtrl()
        active_tabctrl.RemoveButton(id)
        
        
    def HasMultiplePages(self):
        """ Overridden from wx.PyControl. """

        return True


    def GetDefaultBorder(self):
        """ Returns the default border style for L{AuiNotebook}. """

        return wx.BORDER_NONE

