--- /dev/null
+"""
+auibook contains a notebook control which implements many features common in
+applications with dockable panes. Specifically, L{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 L{AuiDefaultTabArt}, which provides a modern, glossy
+look and feel. The theme can be changed by calling L{AuiNotebook.SetArtProvider}.
+"""
+
+__author__ = "Andrea Gavana <andrea.gavana@gmail.com>"
+__date__ = "31 March 2009"
+
+
+import wx
+import types
+import datetime
+
+from wx.lib.expando import ExpandoTextCtrl
+
+import framemanager
+import tabart as TA
+
+from aui_utilities import LightColour, MakeDisabledBitmap, TabDragImage
+from aui_utilities import TakeScreenShot, RescaleScreenShot
+
+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_TAB_DCLICK = wx.NewEventType()
+wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_DOWN = wx.NewEventType()
+wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_UP = wx.NewEventType()
+wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_DOWN = wx.NewEventType()
+wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_UP = wx.NewEventType()
+wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK = wx.NewEventType()
+
+# Define a new event for a drag cancelled
+wxEVT_COMMAND_AUINOTEBOOK_CANCEL_DRAG = wx.NewEventType()
+
+# Define events for editing a tab label
+wxEVT_COMMAND_AUINOTEBOOK_BEGIN_LABEL_EDIT = wx.NewEventType()
+wxEVT_COMMAND_AUINOTEBOOK_END_LABEL_EDIT = wx.NewEventType()
+
+# Create event binders
+EVT_AUINOTEBOOK_PAGE_CLOSE = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE, 1)
+""" A tab in `AuiNotebook` is being closed. Can be vetoed by calling `Veto()`. """
+EVT_AUINOTEBOOK_PAGE_CLOSED = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSED, 1)
+""" A tab in `AuiNotebook` has been closed. """
+EVT_AUINOTEBOOK_PAGE_CHANGED = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGED, 1)
+""" The page selection was changed. """
+EVT_AUINOTEBOOK_PAGE_CHANGING = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, 1)
+""" The page selection is being changed. """
+EVT_AUINOTEBOOK_BUTTON = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BUTTON, 1)
+""" The user clicked on a button in the `AuiNotebook` tab area. """
+EVT_AUINOTEBOOK_BEGIN_DRAG = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BEGIN_DRAG, 1)
+""" A drag-and-drop operation on a notebook tab has started. """
+EVT_AUINOTEBOOK_END_DRAG = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_END_DRAG, 1)
+""" A drag-and-drop operation on a notebook tab has finished. """
+EVT_AUINOTEBOOK_DRAG_MOTION = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_DRAG_MOTION, 1)
+""" A drag-and-drop operation on a notebook tab is ongoing. """
+EVT_AUINOTEBOOK_ALLOW_DND = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_ALLOW_DND, 1)
+""" Fires an event asking if it is OK to drag and drop a tab. """
+EVT_AUINOTEBOOK_DRAG_DONE = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, 1)
+""" A drag-and-drop operation on a notebook tab has finished. """
+EVT_AUINOTEBOOK_TAB_MIDDLE_DOWN = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_DOWN, 1)
+""" The user clicked with the middle mouse button on a tab. """
+EVT_AUINOTEBOOK_TAB_MIDDLE_UP = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_UP, 1)
+""" The user clicked with the middle mouse button on a tab. """
+EVT_AUINOTEBOOK_TAB_RIGHT_DOWN = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_DOWN, 1)
+""" The user clicked with the right mouse button on a tab. """
+EVT_AUINOTEBOOK_TAB_RIGHT_UP = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP, 1)
+""" The user clicked with the right mouse button on a tab. """
+EVT_AUINOTEBOOK_BG_MIDDLE_DOWN = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_DOWN, 1)
+""" The user middle-clicked in the tab area but not over a tab or a button. """
+EVT_AUINOTEBOOK_BG_MIDDLE_UP = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_UP, 1)
+""" The user middle-clicked in the tab area but not over a tab or a button. """
+EVT_AUINOTEBOOK_BG_RIGHT_DOWN = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_DOWN, 1)
+""" The user right-clicked in the tab area but not over a tab or a button. """
+EVT_AUINOTEBOOK_BG_RIGHT_UP = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_UP, 1)
+""" The user right-clicked in the tab area but not over a tab or a button. """
+EVT_AUINOTEBOOK_BG_DCLICK = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK, 1)
+""" The user left-clicked on the tab area not occupied by `AuiNotebook` tabs. """
+EVT_AUINOTEBOOK_CANCEL_DRAG = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_CANCEL_DRAG, 1)
+""" A drag and drop operation has been cancelled. """
+EVT_AUINOTEBOOK_TAB_DCLICK = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_DCLICK, 1)
+""" The user double-clicked with the left mouse button on a tab. """
+EVT_AUINOTEBOOK_BEGIN_LABEL_EDIT = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BEGIN_LABEL_EDIT, 1)
+""" The user double-clicked with the left mouse button on a tab which text is editable. """
+EVT_AUINOTEBOOK_END_LABEL_EDIT = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_END_LABEL_EDIT, 1)
+""" The user finished editing a tab label. """
+
+
+# -----------------------------------------------------------------------------
+# Auxiliary class: TabTextCtrl
+# This is the temporary ExpandoTextCtrl created when you edit the text of a tab
+# -----------------------------------------------------------------------------
+
+class TabTextCtrl(ExpandoTextCtrl):
+ """ Control used for in-place edit. """
+
+ def __init__(self, owner, tab, page_index):
+ """
+ Default class constructor.
+ For internal use: do not call it in your code!
+
+ :param `owner`: the L{AuiTabCtrl} owning the tab;
+ :param `tab`: the actual L{AuiNotebookPage} tab;
+ :param `page_index`: the L{AuiNotebook} page index for the tab.
+ """
+
+ self._owner = owner
+ self._tabEdited = tab
+ self._pageIndex = page_index
+ self._startValue = tab.caption
+ self._finished = False
+ self._aboutToFinish = False
+ self._currentValue = self._startValue
+
+ x, y, w, h = self._tabEdited.rect
+
+ wnd = self._tabEdited.control
+ if wnd:
+ x += wnd.GetSize()[0] + 2
+ h = 0
+
+ image_h = 0
+ image_w = 0
+
+ image = tab.bitmap
+
+ if image.IsOk():
+ image_w, image_h = image.GetWidth(), image.GetHeight()
+ image_w += 6
+
+ dc = wx.ClientDC(self._owner)
+ h = max(image_h, dc.GetMultiLineTextExtent(tab.caption)[1])
+ h = h + 2
+
+ # FIXME: what are all these hardcoded 4, 8 and 11s really?
+ x += image_w
+ w -= image_w + 4
+
+ y = (self._tabEdited.rect.height - h)/2 + 1
+
+ expandoStyle = wx.WANTS_CHARS
+ if wx.Platform in ["__WXGTK__", "__WXMAC__"]:
+ expandoStyle |= wx.SIMPLE_BORDER
+ xSize, ySize = w + 2, h
+ else:
+ expandoStyle |= wx.SUNKEN_BORDER
+ xSize, ySize = w + 2, h+2
+
+ ExpandoTextCtrl.__init__(self, self._owner, wx.ID_ANY, self._startValue,
+ wx.Point(x, y), wx.Size(xSize, ySize),
+ expandoStyle)
+
+ if wx.Platform == "__WXMAC__":
+ self.SetFont(owner.GetFont())
+ bs = self.GetBestSize()
+ self.SetSize((-1, bs.height))
+
+ self.Bind(wx.EVT_CHAR, self.OnChar)
+ self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
+ self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
+
+
+ def AcceptChanges(self):
+ """ Accepts/refuses the changes made by the user. """
+
+ value = self.GetValue()
+ notebook = self._owner.GetParent()
+
+ if value == self._startValue:
+ # nothing changed, always accept
+ # when an item remains unchanged, the owner
+ # needs to be notified that the user decided
+ # not to change the tree item label, and that
+ # the edit has been cancelled
+ notebook.OnRenameCancelled(self._pageIndex)
+ return True
+
+ if not notebook.OnRenameAccept(self._pageIndex, value):
+ # vetoed by the user
+ return False
+
+ # accepted, do rename the item
+ notebook.SetPageText(self._pageIndex, value)
+
+ return True
+
+
+ def Finish(self):
+ """ Finish editing. """
+
+ if not self._finished:
+
+ notebook = self._owner.GetParent()
+
+ self._finished = True
+ self._owner.SetFocus()
+ notebook.ResetTextControl()
+
+
+ def OnChar(self, event):
+ """
+ Handles the ``wx.EVT_CHAR`` event for L{TabTextCtrl}.
+
+ :param `event`: a `wx.KeyEvent` event to be processed.
+ """
+
+ keycode = event.GetKeyCode()
+ shiftDown = event.ShiftDown()
+
+ if keycode == wx.WXK_RETURN:
+ if shiftDown and self._tabEdited.IsMultiline():
+ event.Skip()
+ else:
+ self._aboutToFinish = True
+ self.SetValue(self._currentValue)
+ # Notify the owner about the changes
+ self.AcceptChanges()
+ # Even if vetoed, close the control (consistent with MSW)
+ wx.CallAfter(self.Finish)
+
+ elif keycode == wx.WXK_ESCAPE:
+ self.StopEditing()
+
+ else:
+ event.Skip()
+
+
+ def OnKeyUp(self, event):
+ """
+ Handles the ``wx.EVT_KEY_UP`` event for L{TabTextCtrl}.
+
+ :param `event`: a `wx.KeyEvent` event to be processed.
+ """
+
+ if not self._finished:
+
+ # auto-grow the textctrl:
+ mySize = self.GetSize()
+
+ dc = wx.ClientDC(self)
+ sx, sy, dummy = dc.GetMultiLineTextExtent(self.GetValue() + "M")
+
+ self.SetSize((sx, -1))
+ self._currentValue = self.GetValue()
+
+ event.Skip()
+
+
+ def OnKillFocus(self, event):
+ """
+ Handles the ``wx.EVT_KILL_FOCUS`` event for L{TabTextCtrl}.
+
+ :param `event`: a `wx.FocusEvent` event to be processed.
+ """
+
+ if not self._finished and not self._aboutToFinish:
+
+ # We must finish regardless of success, otherwise we'll get
+ # focus problems:
+ if not self.AcceptChanges():
+ self._owner.GetParent().OnRenameCancelled(self._pageIndex)
+
+ # We must let the native text control handle focus, too, otherwise
+ # it could have problems with the cursor (e.g., in wxGTK).
+ event.Skip()
+ wx.CallAfter(self._owner.GetParent().ResetTextControl)
+
+
+ def StopEditing(self):
+ """ Suddenly stops the editing. """
+
+ self._owner.GetParent().OnRenameCancelled(self._pageIndex)
+ self.Finish()
+
+
+ def item(self):
+ """ Returns the item currently edited. """
+
+ return self._tabEdited
+
+
+# ----------------------------------------------------------------------
+
+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.hasCloseButton = True # True if the page has a close button using the style
+ # AUI_NB_CLOSE_ON_ALL_TABS
+ self.control = None # A control can now be inside a tab
+ self.renamable = False # If True, a tab can be renamed by a left double-click
+
+ self.text_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNTEXT)
+
+ self.access_time = datetime.datetime.now() # Last time this page was selected
+
+
+ def IsMultiline(self):
+ """ Returns whether the tab contains multiline text. """
+
+ return "\n" in self.caption
+
+
+# ----------------------------------------------------------------------
+
+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 `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
+ self.label = ""
+ self.editCancelled = False
+
+
+ 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 L{AuiNotebook} ).
+
+ :param `b`: whether the event was dispatched or not.
+ """
+
+ self.dispatched = b
+
+
+ def GetDispatched(self):
+ """ Returns whether the event was dispatched (used for automatic L{AuiNotebook} ). """
+
+ return self.dispatched
+
+
+ def IsEditCancelled(self):
+ """ Returns the edit cancel flag (for ``EVT_AUINOTEBOOK_BEGIN`` | ``END_LABEL_EDIT`` only)."""
+
+ return self.editCancelled
+
+
+ def SetEditCanceled(self, editCancelled):
+ """
+ Sets the edit cancel flag (for ``EVT_AUINOTEBOOK_BEGIN`` | ``END_LABEL_EDIT`` only).
+
+ :param `editCancelled`: whether the editing action has been cancelled or not.
+ """
+
+ self.editCancelled = editCancelled
+
+
+ def GetLabel(self):
+ """Returns the label-itemtext (for ``EVT_AUINOTEBOOK_BEGIN`` | ``END_LABEL_EDIT`` only)."""
+
+ return self.label
+
+
+ def SetLabel(self, label):
+ """
+ Sets the label. Useful only for ``EVT_AUINOTEBOOK_END_LABEL_EDIT``.
+
+ :param `label`: the new label.
+ """
+
+ self.label = label
+
+
+# ----------------------------------------------------------------------
+
+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 `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 GetNotifyEvent(self):
+ """ Returns the actual `wx.NotifyEvent`. """
+
+ return self.notify
+
+
+ def IsAllowed(self):
+ """ Returns whether the event is allowed or not. """
+
+ return self.notify.IsAllowed()
+
+
+ def Veto(self):
+ """
+ Prevents the change announced by this event from happening.
+
+ It is in general a good idea to notify the user about the reasons for
+ vetoing the change because otherwise the applications behaviour (which
+ just refuses to do what the user wants) might be quite surprising.
+ """
+
+ self.notify.Veto()
+
+
+ def Allow(self):
+ """
+ This is the opposite of L{Veto}: it explicitly allows the event to be
+ processed. For most events it is not necessary to call this method as the
+ events are allowed anyhow but some are forbidden by default (this will
+ be mentioned in the corresponding event description).
+ """
+
+ 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 L{TabNavigatorWindow} parent;
+ :param `icon`: the L{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 `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 `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}.
+ """
+ # Index of currently selected page
+ selection = book.GetSelection()
+ # Total number of pages
+ count = book.GetPageCount()
+ # List of (index, AuiNotebookPage)
+ pages = list(enumerate(book.GetTabContainer().GetPages()))
+ if book.GetAGWWindowStyleFlag() & AUI_NB_ORDER_BY_ACCESS:
+ # Sort pages using last access time. Most recently used is the
+ # first in line
+ pages.sort(
+ key = lambda element: element[1].access_time,
+ reverse = True
+ )
+ else:
+ # Manually add the current selection as first item
+ # Remaining ones are added in the next loop
+ del pages[selection]
+ self._listBox.Append(book.GetPageText(selection))
+ self._indexMap.append(selection)
+
+ for (index, page) in pages:
+ self._listBox.Append(book.GetPageText(index))
+ self._indexMap.append(index)
+
+ # 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 `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()
+ self.EndModal(wx.ID_OK)
+
+
+ def GetSelectedPage(self):
+ """ Gets the page index that was selected when the dialog was closed. """
+
+ return self._indexMap[self._selectedItem]
+
+
+ def OnPanelPaint(self, event):
+ """
+ Handles the ``wx.EVT_PAINT`` event for L{TabNavigatorWindow} top panel.
+
+ :param `event`: a `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.
+
+ :param `event`: a `wx.EraseEvent` event to be processed.
+
+ :note: This is intentionally empty, to reduce flicker.
+ """
+
+ 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 L{AuiManager}, where it is disadvantageous to have separate
+ windows for each tab control in the case of "docked tabs".
+
+ A derived class, L{AuiTabCtrl}, is an actual `wx.Window`-derived window
+ which can be used as a tab control in the normal sense.
+ """
+
+ def __init__(self, auiNotebook):
+ """
+ Default class constructor.
+ Used internally, do not call it in your code!
+
+ :param `auiNotebook`: the parent L{AuiNotebook} window.
+ """
+
+ self._tab_offset = 0
+ self._agwFlags = 0
+ self._art = TA.AuiDefaultTabArt()
+
+ self._buttons = []
+ self._pages = []
+ self._tab_close_buttons = []
+
+ self._rect = wx.Rect()
+ self._auiNotebook = auiNotebook
+
+ 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.
+
+ :param `art`: an art provider.
+
+ :note: The previous art provider object, if any, will be deleted by L{AuiTabContainer}.
+ """
+
+ del self._art
+ self._art = art
+
+ if self._art:
+ self._art.SetAGWFlags(self._agwFlags)
+
+
+ def GetArtProvider(self):
+ """ Returns the current art provider being used. """
+
+ return self._art
+
+
+ def SetAGWFlags(self, agwFlags):
+ """
+ Sets the tab art flags.
+
+ :param `agwFlags`: 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 L{AuiNotebook} tabs by mouse middle button click
+ ``AUI_NB_SUB_NOTEBOOK`` This style is used by L{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_ORDER_BY_ACCESS`` Tab navigation order by last access time for the tabs
+ ``AUI_NB_NO_TAB_FOCUS`` Don't draw tab focus rectangle
+ ==================================== ==================================
+
+ :todo: Implementation of flags ``AUI_NB_RIGHT`` and ``AUI_NB_LEFT``.
+
+ """
+
+ self._agwFlags = agwFlags
+
+ # 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 agwFlags & AUI_NB_SCROLL_BUTTONS:
+ self.AddButton(AUI_BUTTON_LEFT, wx.LEFT)
+ self.AddButton(AUI_BUTTON_RIGHT, wx.RIGHT)
+
+ if agwFlags & AUI_NB_WINDOWLIST_BUTTON:
+ self.AddButton(AUI_BUTTON_WINDOWLIST, wx.RIGHT)
+
+ if agwFlags & AUI_NB_CLOSE_BUTTON:
+ self.AddButton(AUI_BUTTON_CLOSE, wx.RIGHT)
+
+ if self._art:
+ self._art.SetAGWFlags(self._agwFlags)
+
+
+ def GetAGWFlags(self):
+ """
+ Returns the tab art flags.
+
+ See L{SetAGWFlags} for a list of possible return values.
+
+ :see: L{SetAGWFlags}
+ """
+
+ return self._agwFlags
+
+
+ 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:
+ minMaxTabWidth = self._auiNotebook.GetMinMaxTabWidth()
+ self._art.SetSizingInfo(rect.GetSize(), len(self._pages), minMaxTabWidth)
+
+
+ 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:
+ minMaxTabWidth = self._auiNotebook.GetMinMaxTabWidth()
+ self._art.SetSizingInfo(self._rect.GetSize(), len(self._pages), minMaxTabWidth)
+
+ 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:
+ minMaxTabWidth = self._auiNotebook.GetMinMaxTabWidth()
+ self._art.SetSizingInfo(self._rect.GetSize(), len(self._pages), minMaxTabWidth)
+
+ 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.
+ """
+
+ minMaxTabWidth = self._auiNotebook.GetMinMaxTabWidth()
+
+ for page in self._pages:
+ if page.window == wnd:
+ self._pages.remove(page)
+ self._tab_offset = min(self._tab_offset, len(self._pages) - 1)
+
+ # let the art provider know how many pages we have
+ if self._art:
+ self._art.SetSizingInfo(self._rect.GetSize(), len(self._pages), minMaxTabWidth)
+
+ 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 inactive (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):
+ return False
+
+ 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.
+
+ :see: L{AddButton}
+ """
+
+ 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 MinimizeTabOffset(self, dc, wnd, max_width):
+ """
+ Minimize `self._tab_offset` to fit as many tabs as possible in the available space.
+
+ :param `dc`: a `wx.DC` device context;
+ :param `wnd`: an instance of `wx.Window`;
+ :param `max_width`: the maximum available width for the tabs.
+ """
+
+ total_width = 0
+
+ for i, page in reversed(list(enumerate(self._pages))):
+
+ tab_button = self._tab_close_buttons[i]
+ (tab_width, tab_height), x_extent = self._art.GetTabSize(dc, wnd, page.caption, page.bitmap, page.active, tab_button.cur_state, page.control)
+ total_width += tab_width
+
+ if total_width > max_width:
+
+ tab_offset = i + 1
+
+ if tab_offset < self._tab_offset and tab_offset < len(self._pages):
+ self._tab_offset = tab_offset
+
+ break
+
+ if i == 0:
+ self._tab_offset = 0
+
+
+ def Render(self, raw_dc, wnd):
+ """
+ Renders the tab catalog to the specified `wx.DC`.
+
+ It is a virtual function and can be overridden to provide custom drawing
+ capabilities.
+
+ :param `raw_dc`: a `wx.DC` device context;
+ :param `wnd`: an instance of `wx.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._agwFlags & AUI_NB_CLOSE_ON_ALL_TABS and page.hasCloseButton) or \
+ (self._agwFlags & AUI_NB_CLOSE_ON_ACTIVE_TAB and page.active and page.hasCloseButton):
+
+ close_button = True
+
+ control = page.control
+ if control:
+ try:
+ control.GetSize()
+ except wx.PyDeadObjectError:
+ page.control = None
+
+ 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], page.control)
+
+ 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
+ if self._pages[i].control:
+ if self._pages[i].control.IsShown():
+ self._pages[i].control.Hide()
+
+ self.MinimizeTabOffset(dc, wnd, self._rect.GetWidth() - right_buttons_width - offset - 2)
+
+ # 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._agwFlags & AUI_NB_CLOSE_ON_ALL_TABS and page.hasCloseButton) or \
+ (self._agwFlags & AUI_NB_CLOSE_ON_ACTIVE_TAB and page.active and page.hasCloseButton):
+
+ 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
+
+ lenPages = len(self._pages)
+ # 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
+ if j > 0 and j <= lenPages:
+ if self._pages[j-1].control:
+ if self._pages[j-1].control.IsShown():
+ self._pages[j-1].control.Hide()
+
+ # 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 `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
+
+ if self._agwFlags & AUI_NB_SCROLL_BUTTONS:
+ # 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, page.control)
+ 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):
+ """
+ Tests if a button was hit.
+
+ :param `x`: the mouse `x` position;
+ :param `y`: the mouse `y` position.
+
+ :returns: and instance of L{AuiTabContainerButton} if a button was hit, ``None`` otherwise.
+ """
+
+ if not self._rect.Contains((x,y)):
+ return None
+
+ for button in self._buttons:
+ if button.rect.Contains((x,y)) and \
+ (button.cur_state & (AUI_BUTTON_STATE_HIDDEN|AUI_BUTTON_STATE_DISABLED)) == 0:
+ return button
+
+ for button in self._tab_close_buttons:
+ if button.rect.Contains((x,y)) and \
+ (button.cur_state & (AUI_BUTTON_STATE_HIDDEN|AUI_BUTTON_STATE_DISABLED)) == 0:
+ 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|wx.TAB_TRAVERSAL):
+ """
+ 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, parent)
+
+ self._click_pt = wx.Point(-1, -1)
+ self._is_dragging = False
+ self._hover_button = None
+ self._pressed_button = None
+ self._drag_image = None
+ self._drag_img_offset = (0, 0)
+ self._on_button = False
+
+ 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_KEY_DOWN, self.OnKeyDown)
+ 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 `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}.
+
+ :param `event`: a `wx.EraseEvent` event to be processed.
+
+ :note: This is intentionally empty, to reduce flicker.
+ """
+
+ pass
+
+
+ def DoGetBestSize(self):
+ """
+ Gets the size which best suits the window: for a control, it would be the
+ minimal size which doesn't truncate the control, for a panel - the same
+ size as it would have after a call to `Fit()`.
+
+ :note: 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 `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 `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
+ else:
+ page_index = self.GetActivePage()
+ if page_index != wx.NOT_FOUND:
+ self.GetWindowFromIdx(page_index).SetFocus()
+
+ if self._hover_button:
+ self._pressed_button = self._hover_button
+ self._pressed_button.cur_state = AUI_BUTTON_STATE_PRESSED
+ self._on_button = True
+ self.Refresh()
+ self.Update()
+
+
+ def OnCaptureLost(self, event):
+ """
+ Handles the ``wx.EVT_MOUSE_CAPTURE_LOST`` event for L{AuiTabCtrl}.
+
+ :param `event`: a `wx.MouseCaptureLostEvent` event to be processed.
+ """
+
+ if self._is_dragging:
+ self._is_dragging = False
+ self._on_button = 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 `wx.MouseEvent` event to be processed.
+ """
+
+ self._on_button = False
+
+ if self._is_dragging:
+
+ if self.HasCapture():
+ self.ReleaseMouse()
+
+ 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
+
+ self.GetParent()._mgr.HideHint()
+
+ if self.HasCapture():
+ self.ReleaseMouse()
+
+ if self._hover_button:
+ self._pressed_button = self._hover_button
+
+ 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)
+ eventHandler = self.GetEventHandler()
+
+ if eventHandler is not None:
+ eventHandler.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 `wx.MouseEvent` event to be processed.
+ """
+
+ eventHandler = self.GetEventHandler()
+ if not isinstance(eventHandler, AuiTabCtrl):
+ event.Skip()
+ return
+
+ x, y = event.GetX(), event.GetY()
+ wnd = self.TabHitTest(x, y)
+
+ if wnd:
+ e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_UP, self.GetId())
+ e.SetEventObject(self)
+ e.SetSelection(self.GetIdxFromWindow(wnd))
+ self.GetEventHandler().ProcessEvent(e)
+ elif not self.ButtonHitTest(x, y):
+ e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_UP, self.GetId())
+ e.SetEventObject(self)
+ self.GetEventHandler().ProcessEvent(e)
+
+
+ def OnMiddleDown(self, event):
+ """
+ Handles the ``wx.EVT_MIDDLE_DOWN`` event for L{AuiTabCtrl}.
+
+ :param `event`: a `wx.MouseEvent` event to be processed.
+ """
+
+ eventHandler = self.GetEventHandler()
+ if not isinstance(eventHandler, AuiTabCtrl):
+ event.Skip()
+ return
+
+ x, y = event.GetX(), event.GetY()
+ wnd = self.TabHitTest(x, y)
+
+ if wnd:
+ e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_DOWN, self.GetId())
+ e.SetEventObject(self)
+ e.SetSelection(self.GetIdxFromWindow(wnd))
+ self.GetEventHandler().ProcessEvent(e)
+ elif not self.ButtonHitTest(x, y):
+ e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_DOWN, self.GetId())
+ e.SetEventObject(self)
+ self.GetEventHandler().ProcessEvent(e)
+
+
+ def OnRightUp(self, event):
+ """
+ Handles the ``wx.EVT_RIGHT_UP`` event for L{AuiTabCtrl}.
+
+ :param `event`: a `wx.MouseEvent` event to be processed.
+ """
+
+ x, y = event.GetX(), event.GetY()
+ wnd = self.TabHitTest(x, y)
+
+ if wnd:
+ e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP, self.GetId())
+ e.SetEventObject(self)
+ e.SetSelection(self.GetIdxFromWindow(wnd))
+ self.GetEventHandler().ProcessEvent(e)
+ elif not self.ButtonHitTest(x, y):
+ e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_UP, self.GetId())
+ e.SetEventObject(self)
+ self.GetEventHandler().ProcessEvent(e)
+
+
+ def OnRightDown(self, event):
+ """
+ Handles the ``wx.EVT_RIGHT_DOWN`` event for L{AuiTabCtrl}.
+
+ :param `event`: a `wx.MouseEvent` event to be processed.
+ """
+
+ x, y = event.GetX(), event.GetY()
+ wnd = self.TabHitTest(x, y)
+
+ if wnd:
+ e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_DOWN, self.GetId())
+ e.SetEventObject(self)
+ e.SetSelection(self.GetIdxFromWindow(wnd))
+ self.GetEventHandler().ProcessEvent(e)
+ elif not self.ButtonHitTest(x, y):
+ e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_DOWN, self.GetId())
+ e.SetEventObject(self)
+ self.GetEventHandler().ProcessEvent(e)
+
+
+ def OnLeftDClick(self, event):
+ """
+ Handles the ``wx.EVT_LEFT_DCLICK`` event for L{AuiTabCtrl}.
+
+ :param `event`: a `wx.MouseEvent` event to be processed.
+ """
+
+ x, y = event.GetX(), event.GetY()
+ wnd = self.TabHitTest(x, y)
+
+ if wnd:
+ e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_DCLICK, self.GetId())
+ e.SetEventObject(self)
+ e.SetSelection(self.GetIdxFromWindow(wnd))
+ self.GetEventHandler().ProcessEvent(e)
+ elif not self.ButtonHitTest(x, y):
+ e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_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 `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 self._on_button:
+ 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.HasCapture():
+ return
+
+ wnd = self.TabHitTest(pos.x, pos.y)
+
+ 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._agwFlags & AUI_NB_DRAW_DND_TAB:
+ # Create the custom draw image from the icons and the text of the item
+ 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._agwFlags & 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())
+
+ # Capture the mouse cursor position offset relative to
+ # The tab image location
+ self._drag_img_offset = (pos[0] - page.rect.x,
+ pos[1] - page.rect.y)
+
+ self._drag_image.Show()
+
+ 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:
+ # Apply the drag images offset
+ pos -= self._drag_img_offset
+ self._drag_image.Move(pos)
+
+
+ def OnLeaveWindow(self, event):
+ """
+ Handles the ``wx.EVT_LEAVE_WINDOW`` event for L{AuiTabCtrl}.
+
+ :param `event`: a `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 L{AuiNotebookEvent} 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 `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 `wx.FocusEvent` event to be processed.
+ """
+
+ self.Refresh()
+
+
+ def OnKeyDown(self, event):
+ """
+ Handles the ``wx.EVT_KEY_DOWN`` event for L{AuiTabCtrl}.
+
+ :param `event`: a `wx.KeyEvent` event to be processed.
+ """
+
+ key = event.GetKeyCode()
+ nb = self.GetParent()
+
+ if key == wx.WXK_LEFT:
+ nb.AdvanceSelection(False)
+ self.SetFocus()
+
+ elif key == wx.WXK_RIGHT:
+ nb.AdvanceSelection(True)
+ self.SetFocus()
+
+ elif key == wx.WXK_HOME:
+ newPage = 0
+ nb.SetSelection(newPage)
+ self.SetFocus()
+
+ elif key == wx.WXK_END:
+ newPage = nb.GetPageCount() - 1
+ nb.SetSelection(newPage)
+ self.SetFocus()
+
+ elif key == wx.WXK_TAB:
+ if not event.ControlDown():
+ flags = 0
+ if not event.ShiftDown(): flags |= wx.NavigationKeyEvent.IsForward
+ if event.CmdDown(): flags |= wx.NavigationKeyEvent.WinChange
+ self.Navigate(flags)
+ else:
+
+ if not nb or not isinstance(nb, AuiNotebook):
+ event.Skip()
+ return
+
+ bForward = bWindowChange = 0
+ if not event.ShiftDown(): bForward |= wx.NavigationKeyEvent.IsForward
+ if event.CmdDown(): bWindowChange |= wx.NavigationKeyEvent.WinChange
+
+ keyEvent = wx.NavigationKeyEvent()
+ keyEvent.SetDirection(bForward)
+ keyEvent.SetWindowChange(bWindowChange)
+ keyEvent.SetFromTab(True)
+ 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()
+
+ self.SetFocus()
+
+ return
+
+ else:
+ event.Skip()
+
+
+ def OnKeyDown2(self, event):
+ """
+ Deprecated.
+
+ Handles the ``wx.EVT_KEY_DOWN`` event for L{AuiTabCtrl}.
+
+ :param `event`: a `wx.KeyEvent` event to be processed.
+
+ :warning: This method implementation is now deprecated. Refer to L{OnKeyDown}
+ for the correct one.
+ """
+
+ 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, parent):
+ """
+ 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._parent = parent
+
+ 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):
+ """
+ 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)
+ =================================== ======================================
+
+ :note: Overridden from `wx.PyControl`.
+ """
+
+ self._rect = wx.Rect(x, y, max(1, width), max(1, height))
+ self.DoSizing()
+
+
+ def DoGetSize(self):
+ """
+ Returns the window size.
+
+ :note: Overridden from `wx.PyControl`.
+ """
+
+ return self._rect.width, self._rect.height
+
+
+ def DoGetClientSize(self):
+ """
+ Returns the window client size.
+
+ :note: Overridden from `wx.PyControl`.
+ """
+
+ return self._rect.width, self._rect.height
+
+
+ def Show(self, show=True):
+ """
+ Shows/hides the window.
+
+ :param `show`: ``True`` to show the window, ``False`` otherwise.
+
+ :note: Overridden from `wx.PyControl`, this method always returns ``False`` as
+ L{TabFrame} should never be phisically shown on screen.
+ """
+
+ return False
+
+
+ def DoSizing(self):
+ """ Does the actual sizing of the tab control. """
+
+ if not self._tabs:
+ return
+
+ hideOnSingle = ((self._tabs.GetAGWFlags() & AUI_NB_HIDE_ON_SINGLE_TAB) and \
+ self._tabs.GetPageCount() <= 1)
+
+ if not hideOnSingle and not self._parent._hide_tabs:
+ 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.GetAGWFlags() & 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 (GetAGWFlags() & AUI_NB_LEFT)
+ # TODO: elif (GetAGWFlags() & 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.GetAGWFlags() & 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 (GetAGWFlags() & AUI_NB_LEFT)
+ # TODO: elif (GetAGWFlags() & AUI_NB_RIGHT)
+
+ if repr(page.window.__class__).find("AuiMDIChildFrame") >= 0:
+ page.window.ApplyMDIChildFrameRect()
+
+
+ def Update(self):
+ """
+ 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.
+
+ :note: Overridden from `wx.PyControl`.
+ """
+
+ # does nothing
+ pass
+
+
+# ----------------------------------------------------------------------
+# -- AuiNotebook class implementation --
+
+class AuiNotebook(wx.PyPanel):
+ """
+ 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 L{AuiDefaultTabArt}, which provides a modern, glossy
+ look and feel. The theme can be changed by calling L{AuiNotebook.SetArtProvider}.
+ """
+
+ def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
+ style=0, agwStyle=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 underlying `wx.PyPanel` window style;
+ :param `agwStyle`: the AGW-specific 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 L{AuiNotebook} tabs by mouse middle button click
+ ``AUI_NB_SUB_NOTEBOOK`` This style is used by L{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_ORDER_BY_ACCESS`` Tab navigation order by last access time for the tabs
+ ``AUI_NB_NO_TAB_FOCUS`` Don't draw tab focus rectangle
+ ==================================== ==================================
+
+ Default value for `agwStyle` 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._hide_tabs = False
+ self._sash_dclick_unsplit = False
+ self._tab_ctrl_height = 20
+ self._requested_bmp_size = wx.Size(-1, -1)
+ self._requested_tabctrl_height = -1
+ self._textCtrl = None
+ self._tabBounds = (-1, -1)
+ self._click_tab = None
+
+ wx.PyPanel.__init__(self, parent, id, pos, size, style|wx.BORDER_NONE|wx.TAB_TRAVERSAL)
+ self._mgr = framemanager.AuiManager()
+ self._tabs = AuiTabContainer(self)
+
+ self.InitNotebook(agwStyle)
+
+
+ def GetTabContainer(self):
+ """ Returns the instance of L{AuiTabContainer}. """
+
+ return self._tabs
+
+
+ def InitNotebook(self, agwStyle):
+ """
+ Contains common initialization code called by all constructors.
+
+ :param `agwStyle`: the notebook style.
+
+ :see: L{__init__}
+ """
+
+ self.SetName("AuiNotebook")
+ self._agwFlags = agwStyle
+
+ self._popupWin = None
+ self._naviIcon = None
+ self._imageList = None
+ self._last_drag_x = 0
+
+ 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.SetAGWFlags(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):
+ """
+ Loads a layout which was saved with L{SavePerspective}.
+
+ :param `layout`: a string which contains a saved L{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('|')]
+
+ if "=" not in tab_part:
+ # No pages in this perspective...
+ return False
+
+ # Get pane name
+ pane_name = tab_part[0:tab_part.index("=")]
+
+ # create a new tab frame
+ new_tabs = TabFrame(self)
+ 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.SetAGWFlags(self._agwFlags)
+ 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():
+ to_break1 = True
+ break
+
+ # 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):
+ """
+ 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`: ``True`` to 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 SetAGWWindowStyleFlag(self, agwStyle):
+ """
+ Sets the AGW-specific style of the window.
+
+ :param `agwStyle`: the new 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 L{AuiNotebook} tabs by mouse middle button click
+ ``AUI_NB_SUB_NOTEBOOK`` This style is used by L{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_ORDER_BY_ACCESS`` Tab navigation order by last access time for the tabs
+ ``AUI_NB_NO_TAB_FOCUS`` Don't draw tab focus rectangle
+ ==================================== ==================================
+
+ :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.
+
+ :todo: Implementation of flags ``AUI_NB_RIGHT`` and ``AUI_NB_LEFT``.
+ """
+
+ self._agwFlags = agwStyle
+
+ # 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.SetAGWFlags(self._agwFlags)
+ tabframe.DoSizing()
+ tabctrl.Refresh()
+ tabctrl.Update()
+
+
+ def GetAGWWindowStyleFlag(self):
+ """
+ Returns the AGW-specific style of the window.
+
+ :see: L{SetAGWWindowStyleFlag} for a list of possible AGW-specific window styles.
+ """
+
+ return self._agwFlags
+
+
+ def AddPage(self, page, caption, select=False, bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap, control=None):
+ """
+ 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;
+ :param `control`: a `wx.Window` instance inside a tab (or ``None``).
+ """
+
+ return self.InsertPage(self.GetPageCount(), page, caption, select, bitmap, disabled_bitmap, control)
+
+
+ def InsertPage(self, page_idx, page, caption, select=False, bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap,
+ control=None):
+ """
+ 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;
+ :param `control`: a `wx.Window` instance inside a tab (or ``None``).
+ """
+
+ if not page:
+ return False
+
+ page.Reparent(self)
+ info = AuiNotebookPage()
+ info.window = page
+ info.caption = caption
+ info.bitmap = bitmap
+ info.active = False
+ info.control = control
+
+ originalPaneMgr = framemanager.GetManager(page)
+ if originalPaneMgr:
+ originalPane = originalPaneMgr.GetPane(page)
+
+ if originalPane:
+ info.hasCloseButton = originalPane.HasCloseButton()
+
+ 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)
+
+ force = False
+ if control:
+ force = True
+ control.Reparent(active_tabctrl)
+ control.Show()
+
+ self.UpdateTabCtrlHeight(force=force)
+ 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: L{DeletePage} removes a tab from the multi-notebook, and destroys the window as well.
+
+ :see: L{RemovePage}
+ """
+
+ 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)
+
+ self.RemoveControlFromPage(page_idx)
+
+ 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: L{RemovePage} removes a tab from the multi-notebook, but does not destroy the window.
+
+ :see: L{DeletePage}
+ """
+
+ # 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
+
+ currentPage = ctrl.GetPage(ctrl_idx)
+ is_curpage = (self._curpage == page_idx)
+ is_active_in_split = currentPage.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():
+
+ ctrl_idx = self.FindNextActiveTab(ctrl_idx, ctrl)
+
+ # 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 0 <= 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 FindNextActiveTab(self, ctrl_idx, ctrl):
+ """
+ Finds the next active tab (used mainly when L{AuiNotebook} has inactive/disabled
+ tabs in it).
+
+ :param `ctrl_idx`: the index of the first (most obvious) tab to check for active status;
+ :param `ctrl`: an instance of L{AuiTabCtrl}.
+ """
+
+ if self.GetEnabled(ctrl_idx):
+ return ctrl_idx
+
+ for indx in xrange(ctrl_idx, ctrl.GetPageCount()):
+ if self.GetEnabled(indx):
+ return indx
+
+ for indx in xrange(ctrl_idx, -1, -1):
+ if self.GetEnabled(indx):
+ return indx
+
+ return 0
+
+
+ def HideAllTabs(self, hidden=True):
+ """
+ Hides all tabs on the L{AuiNotebook} control.
+
+ :param `hidden`: if ``True`` hides all tabs.
+ """
+
+ self._hide_tabs = hidden
+
+
+ def SetSashDClickUnsplit(self, unsplit=True):
+ """
+ Sets whether to unsplit a splitted L{AuiNotebook} when double-clicking on a sash.
+
+ :param `unsplit`: ``True`` to unsplit on sash double-clicking, ``False`` otherwise.
+ """
+
+ self._sash_dclick_unsplit = unsplit
+
+
+ def GetSashDClickUnsplit(self):
+ """
+ Returns whether a splitted L{AuiNotebook} can be unsplitted by double-clicking
+ on the splitter sash.
+ """
+
+ return self._sash_dclick_unsplit
+
+
+ def SetMinMaxTabWidth(self, minTabWidth, maxTabWidth):
+ """
+ Sets the minimum and/or the maximum tab widths for L{AuiNotebook} when the
+ ``AUI_NB_TAB_FIXED_WIDTH`` style is defined.
+
+ Pass -1 to either `minTabWidth` or `maxTabWidth` to reset to the default tab
+ width behaviour for L{AuiNotebook}.
+
+ :param `minTabWidth`: the minimum allowed tab width, in pixels;
+ :param `maxTabWidth`: the maximum allowed tab width, in pixels.
+
+ :note: Minimum and maximum tabs widths are used only when the ``AUI_NB_TAB_FIXED_WIDTH``
+ style is present.
+ """
+
+ if minTabWidth > maxTabWidth:
+ raise Exception("Minimum tab width must be less or equal than maximum tab width")
+
+ self._tabBounds = (minTabWidth, maxTabWidth)
+ self.SetAGWWindowStyleFlag(self._agwFlags)
+
+
+ def GetMinMaxTabWidth(self):
+ """
+ Returns the minimum and the maximum tab widths for L{AuiNotebook} when the
+ ``AUI_NB_TAB_FIXED_WIDTH`` style is defined.
+
+ :note: Minimum and maximum tabs widths are used only when the ``AUI_NB_TAB_FIXED_WIDTH``
+ style is present.
+
+ :see: L{SetMinMaxTabWidth} for more information.
+ """
+
+ return self._tabBounds
+
+
+ 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)
+ should_refresh = page_info.caption != text
+ 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)
+ should_refresh = should_refresh or info.caption != text
+ info.caption = text
+
+ if should_refresh:
+ ctrl.Refresh()
+ ctrl.Update()
+
+ self.UpdateTabCtrlHeight(force=True)
+
+ 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)
+ should_refresh = page_info.bitmap is not bitmap
+ 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)
+ should_refresh = should_refresh or info.bitmap is not bitmap
+ info.bitmap = bitmap
+ info.dis_bitmap = page_info.dis_bitmap
+ if should_refresh:
+ 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 L{AuiNotebook} control.
+
+ :param `imageList`: an instance of `wx.ImageList`.
+ """
+
+ self._imageList = imageList
+
+
+ def AssignImageList(self, imageList):
+ """
+ Sets the image list for the L{AuiNotebook} control.
+
+ :param `imageList`: an instance of `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)
+ should_refresh = page_info.text_colour != colour
+ 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)
+ should_refresh = should_refresh or info.text_colour != colour
+ info.text_colour = page_info.text_colour
+
+ if should_refresh:
+ 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 AddControlToPage(self, page_idx, control):
+ """
+ Adds a control inside a tab (not in the tab area).
+
+ :param `page_idx`: the page index;
+ :param `control`: an instance of `wx.Window`.
+ """
+
+ if page_idx >= self._tabs.GetPageCount():
+ return False
+
+ # update our own tab catalog
+ page_info = self._tabs.GetPage(page_idx)
+ page_info.control = control
+
+ # tab height might have changed
+ self.UpdateTabCtrlHeight(force=True)
+
+ # update what's on screen
+ ctrl, ctrl_idx = self.FindTab(page_info.window)
+ if not ctrl:
+ return False
+
+ control.Reparent(ctrl)
+
+ info = ctrl.GetPage(ctrl_idx)
+ info.control = control
+ ctrl.Refresh()
+ ctrl.Update()
+
+ return True
+
+
+ def RemoveControlFromPage(self, page_idx):
+ """
+ Removes a control from a tab (not from the tab area).
+
+ :param `page_idx`: the page index.
+ """
+
+ if page_idx >= self._tabs.GetPageCount():
+ return False
+
+ page_info = self._tabs.GetPage(page_idx)
+ if page_info.control is None:
+ return False
+
+ page_info.control.Destroy()
+ page_info.control = None
+
+ # tab height might have changed
+ self.UpdateTabCtrlHeight(force=True)
+
+ # update what's on screen
+ ctrl, ctrl_idx = self.FindTab(page_info.window)
+ if not ctrl:
+ return False
+
+ info = ctrl.GetPage(ctrl_idx)
+ info.control = None
+ ctrl.Refresh()
+ ctrl.Update()
+
+ return True
+
+
+ def SetCloseButton(self, page_idx, hasCloseButton):
+ """
+ Sets whether a tab should display a close button or not.
+
+ :param `page_idx`: the page index;
+ :param `hasCloseButton`: ``True`` if the page displays a close button.
+
+ :note: This can only be called if ``AUI_NB_CLOSE_ON_ALL_TABS`` is specified.
+ """
+
+ if page_idx >= self._tabs.GetPageCount():
+ return False
+
+ if self._agwFlags & AUI_NB_CLOSE_ON_ALL_TABS == 0:
+ raise Exception("SetCloseButton can only be used with AUI_NB_CLOSE_ON_ALL_TABS style.")
+
+ # update our own tab catalog
+ page_info = self._tabs.GetPage(page_idx)
+ page_info.hasCloseButton = hasCloseButton
+
+ # update what's on screen
+ ctrl, ctrl_idx = self.FindTab(page_info.window)
+ if not ctrl:
+ return False
+
+ info = ctrl.GetPage(ctrl_idx)
+ info.hasCloseButton = page_info.hasCloseButton
+ ctrl.Refresh()
+ ctrl.Update()
+
+ return True
+
+
+ def HasCloseButton(self, page_idx):
+ """
+ Returns whether a tab displays a close button or not.
+
+ :param `page_idx`: the page index.
+
+ :note: This can only be called if ``AUI_NB_CLOSE_ON_ALL_TABS`` is specified.
+ """
+
+ if page_idx >= self._tabs.GetPageCount():
+ return False
+
+ page_info = self._tabs.GetPage(page_idx)
+ return page_info.hasCloseButton
+
+
+ def GetSelection(self):
+ """ Returns the index of the currently active page, or -1 if none was selected. """
+
+ return self._curpage
+
+
+ def GetCurrentPage(self):
+ """ Returns the currently active page (not the index), or ``None`` if none was selected. """
+
+ 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)
+
+ #Update page access time
+ self._tabs.GetPages()[new_page].access_time = datetime.datetime.now()
+
+ 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 GetPageInfo(self, page_idx):
+ """
+ Returns the L{AuiNotebookPage} info structure 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.GetPage(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):
+ """
+ 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(self)
+ tabframe.SetTabCtrlHeight(self._tab_ctrl_height)
+ self._tab_id_counter += 1
+ tabframe._tabs = AuiTabCtrl(self, self._tab_id_counter)
+
+ tabframe._tabs.SetAGWFlags(self._agwFlags)
+ tabframe._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone())
+ self._mgr.AddPane(tabframe, framemanager.AuiPaneInfo().Center().CaptionVisible(False).
+ PaneBorder((self._agwFlags & AUI_NB_SUB_NOTEBOOK) == 0))
+
+ self._mgr.Update()
+
+ return tabframe._tabs
+
+
+ def FindTab(self, page):
+ """
+ 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):
+ """
+ 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(self)
+ 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.SetAGWFlags(self._agwFlags)
+ dest_tabs = new_tabs._tabs
+
+ page_info = src_tabs.GetPage(src_idx)
+ if page_info.control:
+ self.ReparentControl(page_info.control, dest_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.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 a tab split. """
+
+ 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 ReparentControl(self, control, dest_tabs):
+ """
+ Reparents a control added inside a tab.
+
+ :param `control`: an instance of `wx.Window`;
+ :param `dest_tabs`: the destination L{AuiTabCtrl}.
+ """
+
+ control.Hide()
+ control.Reparent(dest_tabs)
+
+
+ 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.
+
+ :warning: Due to a bug on MSW, for disabled pages `wx.FindWindowAtPoint`
+ returns the wrong window. See http://trac.wxwidgets.org/ticket/2942
+ """
+
+ if not self._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 isinstance(win1, wx.ScrollBar):
+ # Hopefully it will work
+ pos1 = wx.Point(*pos)
+ shift = wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X) + 2*(sash_size+1)
+ if part.orientation == wx.HORIZONTAL:
+ pos1.y -= shift
+ else:
+ pos1.x -= shift
+
+ pos1 = self.ClientToScreen(pos1)
+ win1 = wx.FindWindowAtPoint(pos1)
+
+ if isinstance(win2, wx.ScrollBar):
+ pos2 = wx.Point(*pos)
+ shift = wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X) + 2*(sash_size+1)
+ if part.orientation == wx.HORIZONTAL:
+ pos2.y += shift
+ else:
+ pos2.x += shift
+
+ pos2 = self.ClientToScreen(pos2)
+ win2 = 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 disabled 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()-1, -1, -1):
+ # 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)
+ if page_info.control:
+ self.ReparentControl(page_info.control, dest_tabs)
+
+ self.RemoveEmptyTabFrames()
+
+ dest_tabs.DoShowHide()
+ self.DoSizing()
+ dest_tabs.Refresh()
+ self._mgr.Update()
+ 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 `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{AuiNotebookEvent} event to be processed.
+ """
+
+ if self._textCtrl is not None:
+ self._textCtrl.StopEditing()
+
+ 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{AuiNotebookEvent} event to be processed.
+ """
+
+ if self._textCtrl is not None:
+ self._textCtrl.StopEditing()
+
+ # 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{AuiNotebookEvent} 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)
+
+ tabs = event.GetEventObject()
+ if not tabs.GetEnabled(event.GetSelection()):
+ return
+
+ if not self.IsRenamable(event.GetSelection()):
+ return
+
+ self.EditTab(event.GetSelection())
+
+
+ def OnTabBeginDrag(self, event):
+ """
+ Handles the ``EVT_AUINOTEBOOK_BEGIN_DRAG`` event for L{AuiNotebook}.
+
+ :param `event`: a L{AuiNotebookEvent} 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{AuiNotebookEvent} event to be processed.
+ """
+
+ tabs = event.GetEventObject()
+ if not tabs.GetEnabled(event.GetSelection()):
+ return
+
+ if self._textCtrl is not None:
+ self._textCtrl.StopEditing()
+
+ 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:
+
+ # always hide the hint for inner-tabctrl drag
+ self._mgr.HideHint()
+
+ # if tab moving is not allowed, leave
+ if not self._agwFlags & 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)
+ self._tabs.MovePage(self._tabs.GetPage(src_idx).window, 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._agwFlags & 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._agwFlags & 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._agwFlags & 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._agwFlags & AUI_NB_TAB_SPLIT:
+ return
+
+ 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{AuiNotebookEvent} 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?")
+
+ # 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._agwFlags & 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)
+
+ # Reparent the control in a tab (if any)
+ if page_info.control:
+ self.ReparentControl(page_info.control, tab_ctrl)
+
+ # find 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)
+
+ # notify the target notebook that the tab has been dragged
+ e3 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, nb.GetId())
+ e3.SetSelection(insert_idx)
+ e3.SetOldSelection(insert_idx)
+ e3.SetEventObject(nb)
+ nb.GetEventHandler().ProcessEvent(e3)
+
+ return
+
+ if self._agwFlags & 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._agwFlags & 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(self)
+ 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.SetAGWFlags(self._agwFlags)
+
+ 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())
+
+ if page_info.control:
+ self.ReparentControl(page_info.control, dest_tabs)
+
+ 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{AuiNotebookEvent} 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?")
+
+
+ def IsMouseWellOutsideWindow(self):
+ """ Returns whether the mouse is well outside the L{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.
+
+ :warning: When the notebook is more or less full screen, tabs cannot be dragged far
+ enough outside of the notebook to become floating pages.
+ """
+
+ 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)
+ info = self.GetPageInfo(page_index)
+
+ if root_manager and root_manager != self._mgr:
+ root_manager = framemanager.GetManager(self)
+
+ if hasattr(page_contents, "__floating_size__"):
+ floating_size = wx.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
+ page_contents.__control__ = info.control
+
+ if info.control:
+ info.control.Reparent(page_contents)
+ info.control.Hide()
+ info.control = None
+
+ self.RemovePage(page_index)
+ self.RemoveEmptyTabFrames()
+
+ pane_info = framemanager.AuiPaneInfo().Float().FloatingPosition(wx.GetMousePosition()). \
+ FloatingSize(floating_size).BestSize(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)
+
+ if info.control:
+ info.control.Reparent(frame)
+ info.control.Hide()
+
+ frame.bitmap = page_bitmap
+ frame.page_index = page_index
+ frame.text_colour = text_colour
+ frame.control = info.control
+ page_contents.Reparent(frame)
+ frame.Bind(wx.EVT_CLOSE, self.OnCloseFloatingPage)
+ frame.Move(wx.GetMousePosition())
+ frame.Show()
+ self.RemovePage(page_index)
+
+ self.RemoveEmptyTabFrames()
+
+ wx.CallAfter(self.RemoveEmptyTabFrames)
+
+
+ def OnCloseFloatingPage(self, event):
+ """
+ Handles the ``wx.EVT_CLOSE`` event for a floating page in L{AuiNotebook}.
+
+ :param `event`: a `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 = list(frame.GetChildren())[-1]
+ page_contents.Reparent(self)
+ self.InsertPage(frame.page_index, page_contents, page_title, select=True, bitmap=frame.bitmap, control=frame.control)
+
+ if frame.control:
+ src_tabs, idx = self.FindTab(page_contents)
+ frame.control.Reparent(src_tabs)
+ frame.control.Hide()
+ frame.control = None
+
+ 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__ = wx.Size(*pane.floating_size)
+ page_index = pane.window.__page_index__
+ text_colour = pane.window.__text_colour__
+ control = pane.window.__control__
+
+ root_manager.DetachPane(pane.window)
+ self.InsertPage(page_index, pane.window, pane.caption, True, pane.icon, control=control)
+
+ self.SetPageTextColour(page_index, text_colour)
+ self.GetActiveTabCtrl().DoShowHide()
+ self.DoSizing()
+ if control:
+ self.UpdateTabCtrlHeight(force=True)
+
+ 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 `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 `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.
+
+ event.Skip()
+
+ 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 `bmp`: 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 `wx.NavigationKeyEvent` event to be processed.
+ """
+
+ if event.IsWindowChange():
+ if self._agwFlags & AUI_NB_SMART_TABS:
+ if not self._popupWin:
+ self._popupWin = TabNavigatorWindow(self, self._naviIcon)
+ self._popupWin.SetReturnCode(wx.ID_OK)
+ self._popupWin.ShowModal()
+ idx = self._popupWin.GetSelectedPage()
+ self._popupWin.Destroy()
+ self._popupWin = None
+ # Need to do CallAfter so that the selection and its
+ # associated events get processed outside the context of
+ # this key event. Not doing so causes odd issues with the
+ # window focus under certain use cases on Windows.
+ wx.CallAfter(self.SetSelection, idx, True)
+ 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 L{AuiNotebookEvent} event to be processed.
+ """
+
+ tabs = event.GetEventObject()
+ button_id = event.GetInt()
+
+ if button_id == AUI_BUTTON_CLOSE:
+
+ 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 or not tabs.GetEnabled(selection):
+ return
+
+ if selection != -1:
+
+ close_wnd = tabs.GetWindowFromIdx(selection)
+
+ if close_wnd.GetName() == "__fake__page__":
+ # This is a notebook preview
+ previous_active, page_status = close_wnd.__previousStatus
+ for page, status in zip(tabs.GetPages(), page_status):
+ page.enabled = status
+
+ main_idx = self._tabs.GetIdxFromWindow(close_wnd)
+ self.DeletePage(main_idx)
+
+ if previous_active >= 0:
+ tabs.SetActivePage(previous_active)
+ page_count = tabs.GetPageCount()
+ selection = -1
+
+ for page in xrange(page_count):
+ # remove the page from the source tabs
+ page_info = tabs.GetPage(page)
+ if page_info.active:
+ selection = page
+ break
+
+ tabs.DoShowHide()
+ self.DoSizing()
+ tabs.Refresh()
+
+ if selection >= 0:
+ wx.CallAfter(tabs.MakeTabVisible, selection, self)
+
+ # Don't fire the event
+ return
+
+ # 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 L{AuiNotebookEvent} 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 L{AuiNotebookEvent} 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._agwFlags & 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 L{AuiNotebookEvent} 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 L{AuiNotebookEvent} 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):
+ """
+ Sets the tab font.
+
+ :param `font`: a `wx.Font` object.
+
+ :note: Overridden from `wx.PyPanel`.
+ """
+
+ wx.PyPanel.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)
+
+ # Recalculate tab container size based on new font
+ self.UpdateTabCtrlHeight(force=False)
+ self.DoSizing()
+
+ 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, wrap=True):
+ """
+ Cycles through the tabs.
+
+ :param `forward`: whether to advance forward or backward;
+ :param `wrap`: ``True`` to return to the first tab if we reach the last tab.
+
+ :note: The call to this function generates the page changing events.
+ """
+
+ tabCtrl = self.GetActiveTabCtrl()
+ newPage = -1
+
+ focusWin = tabCtrl.FindFocus()
+ activePage = tabCtrl.GetActivePage()
+ lenPages = len(tabCtrl.GetPages())
+
+ if lenPages == 1:
+ return False
+
+ if forward:
+ if lenPages > 1:
+
+ if activePage == -1 or activePage == lenPages - 1:
+ if not wrap:
+ return False
+
+ newPage = 0
+
+ elif activePage < lenPages - 1:
+ newPage = activePage + 1
+
+ else:
+
+ if lenPages > 1:
+ if activePage == -1 or activePage == 0:
+ if not wrap:
+ return False
+
+ newPage = lenPages - 1
+
+ elif activePage > 0:
+ newPage = activePage - 1
+
+
+ if newPage != -1:
+ if not self.GetEnabled(newPage):
+ return False
+
+ 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()
+
+ return True
+
+
+ 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{AddTabAreaButton} for a list of button identifiers.
+
+ :see: L{AddTabAreaButton}
+ """
+
+ active_tabctrl = self.GetActiveTabCtrl()
+ active_tabctrl.RemoveButton(id)
+
+
+ def HasMultiplePages(self):
+ """
+ This method should be overridden to return ``True`` if this window has multiple pages. All
+ standard class with multiple pages such as `wx.Notebook`, `wx.Listbook` and `wx.Treebook`
+ already override it to return ``True`` and user-defined classes with similar behaviour
+ should do it as well to allow the library to handle such windows appropriately.
+
+ :note: Overridden from `wx.PyPanel`.
+ """
+
+ return True
+
+
+ def GetDefaultBorder(self):
+ """ Returns the default border style for L{AuiNotebook}. """
+
+ return wx.BORDER_NONE
+
+
+ def NotebookPreview(self, thumbnail_size=200):
+ """
+ Generates a preview of all the pages in the notebook (MSW and GTK only).
+
+ :param `thumbnail_size`: the maximum size of every page thumbnail.
+
+ :note: this functionality is currently unavailable on wxMac.
+ """
+
+ if wx.Platform == "__WXMAC__":
+ return False
+
+ tabCtrl = self.GetActiveTabCtrl()
+ activePage = tabCtrl.GetActivePage()
+ pages = tabCtrl.GetPages()
+
+ pageStatus, pageText = [], []
+
+ for indx, page in enumerate(pages):
+
+ pageStatus.append(page.enabled)
+
+ if not page.enabled:
+ continue
+
+ self.SetSelectionToPage(page)
+ pageText.append(page.caption)
+
+ rect = page.window.GetScreenRect()
+ bmp = RescaleScreenShot(TakeScreenShot(rect), thumbnail_size)
+
+ page.enabled = False
+ if indx == 0:
+ il = wx.ImageList(bmp.GetWidth(), bmp.GetHeight(), True)
+
+ il.Add(bmp)
+
+ # create the list control
+ listCtrl = wx.ListCtrl(self, style=wx.LC_ICON|wx.LC_AUTOARRANGE|wx.LC_HRULES|wx.LC_VRULES,
+ name="__fake__page__")
+
+ # assign the image list to it
+ listCtrl.AssignImageList(il, wx.IMAGE_LIST_NORMAL)
+ listCtrl.__previousStatus = [activePage, pageStatus]
+
+ # create some items for the list
+ for indx, text in enumerate(pageText):
+ listCtrl.InsertImageStringItem(10000, text, indx)
+
+ self.AddPage(listCtrl, "AuiNotebook Preview", True, bitmap=auinotebook_preview.GetBitmap(), disabled_bitmap=wx.NullBitmap)
+ return True
+
+
+ def SetRenamable(self, page_idx, renamable):
+ """
+ Sets whether a tab can be renamed via a left double-click or not.
+
+ :param `page_idx`: the page index;
+ :param `renamable`: ``True`` if the page can be renamed.
+ """
+
+ if page_idx >= self._tabs.GetPageCount():
+ return False
+
+ # update our own tab catalog
+ page_info = self._tabs.GetPage(page_idx)
+ page_info.renamable = renamable
+
+ # update what's on screen
+ ctrl, ctrl_idx = self.FindTab(page_info.window)
+ if not ctrl:
+ return False
+
+ info = ctrl.GetPage(ctrl_idx)
+ info.renamable = page_info.renamable
+
+ return True
+
+
+ def IsRenamable(self, page_idx):
+ """
+ Returns whether a tab can be renamed or not.
+
+ :param `page_idx`: the page index.
+
+ :returns: ``True`` is a page can be renamed, ``False`` otherwise.
+ """
+
+ if page_idx >= self._tabs.GetPageCount():
+ return False
+
+ page_info = self._tabs.GetPage(page_idx)
+ return page_info.renamable
+
+
+ def OnRenameCancelled(self, page_index):
+ """
+ Called by L{TabTextCtrl}, to cancel the changes and to send the
+ `EVT_AUINOTEBOOK_END_LABEL_EDIT` event.
+
+ :param `page_index`: the page index in the notebook.
+ """
+
+ # let owner know that the edit was cancelled
+ evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_END_LABEL_EDIT, self.GetId())
+
+ evt.SetSelection(page_index)
+ evt.SetEventObject(self)
+ evt.SetLabel("")
+ evt.SetEditCanceled(True)
+ self.GetEventHandler().ProcessEvent(evt)
+
+
+ def OnRenameAccept(self, page_index, value):
+ """
+ Called by L{TabTextCtrl}, to accept the changes and to send the
+ `EVT_AUINOTEBOOK_END_LABEL_EDIT` event.
+
+ :param `page_index`: the page index in the notebook;
+ :param `value`: the new label for the tab.
+ """
+
+ evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_END_LABEL_EDIT, self.GetId())
+ evt.SetSelection(page_index)
+ evt.SetEventObject(self)
+ evt.SetLabel(value)
+ evt.SetEditCanceled(False)
+
+ return not self.GetEventHandler().ProcessEvent(evt) or evt.IsAllowed()
+
+
+ def ResetTextControl(self):
+ """ Called by L{TabTextCtrl} when it marks itself for deletion. """
+
+ if not self._textCtrl:
+ return
+
+ self._textCtrl.Destroy()
+ self._textCtrl = None
+
+ # tab height might have changed
+ self.UpdateTabCtrlHeight(force=True)
+
+
+ def EditTab(self, page_index):
+ """
+ Starts the editing of an item label, sending a `EVT_AUINOTEBOOK_BEGIN_LABEL_EDIT` event.
+
+ :param `page_index`: the page index we want to edit.
+ """
+
+ if page_index >= self._tabs.GetPageCount():
+ return False
+
+ if not self.IsRenamable(page_index):
+ return False
+
+ page_info = self._tabs.GetPage(page_index)
+ ctrl, ctrl_idx = self.FindTab(page_info.window)
+ if not ctrl:
+ return False
+
+ evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BEGIN_LABEL_EDIT, self.GetId())
+ evt.SetSelection(page_index)
+ evt.SetEventObject(self)
+ if self.GetEventHandler().ProcessEvent(evt) and not evt.IsAllowed():
+ # vetoed by user
+ return False
+
+ if self._textCtrl is not None and page_info != self._textCtrl.item():
+ self._textCtrl.StopEditing()
+
+ self._textCtrl = TabTextCtrl(ctrl, page_info, page_index)
+ self._textCtrl.SetFocus()
+
+ return True