X-Git-Url: http://iramuteq.org/git?a=blobdiff_plain;f=aui%2Fauibook.py;fp=aui%2Fauibook.py;h=62ae00c1e7d30eddf7a3067ec8d7548b5885254a;hb=7fb5b2b86f6c9a0617208ee85211177c23d12f47;hp=0000000000000000000000000000000000000000;hpb=22f93a602f3584ddc6ba68114556212c90307a50;p=iramuteq diff --git a/aui/auibook.py b/aui/auibook.py new file mode 100644 index 0000000..62ae00c --- /dev/null +++ b/aui/auibook.py @@ -0,0 +1,5737 @@ +""" +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 " +__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