mirror of
https://github.com/Sneed-Group/Poodletooth-iLand
synced 2024-12-28 14:12:52 -06:00
6108 lines
203 KiB
Python
6108 lines
203 KiB
Python
# -*- coding: utf-8 -*-
|
|
#----------------------------------------------------------------------------
|
|
# Name: auibook.py
|
|
# Purpose:
|
|
#
|
|
# Author: Andrea Gavana <andrea.gavana@gmail.com>
|
|
#
|
|
# Created:
|
|
# Version:
|
|
# Date: 31 March 2009
|
|
# Licence: wxWindows license
|
|
# Tags: phoenix-port, unittest, documented, py3-port
|
|
#----------------------------------------------------------------------------
|
|
"""
|
|
`auibook.py` contains a notebook control which implements many features common in
|
|
applications with dockable panes. Specifically, :class:`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 :class:`Notebook`.
|
|
|
|
The default theme that is used is :class:`~lib.agw.aui.tabart.AuiDefaultTabArt`, which provides a modern, glossy
|
|
look and feel. The theme can be changed by calling :meth:`AuiNotebook.SetArtProvider() <AuiNotebook.SetArtProvider>`.
|
|
"""
|
|
|
|
__author__ = "Andrea Gavana <andrea.gavana@gmail.com>"
|
|
__date__ = "31 March 2009"
|
|
|
|
|
|
import wx
|
|
import datetime
|
|
|
|
from wx.lib.expando import ExpandoTextCtrl
|
|
import wx.lib.six as six
|
|
|
|
from . 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_LEFT_UP = 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_LEFT_UP = 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_LEFT_UP = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_LEFT_UP, 1)
|
|
""" The user clicked with the left mouse button on a tab. """
|
|
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_LEFT_UP = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_LEFT_UP, 1)
|
|
""" The user left-clicked in the tab area but not over a tab or a button. """
|
|
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 :class:`AuiNotebook` owning the tab;
|
|
:param `tab`: the actual :class:`AuiTabCtrl` tab;
|
|
:param integer `page_index`: the :class:`AuiTabContainer` 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.GetFullMultiLineTextExtent(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 :class:`TabTextCtrl`.
|
|
|
|
:param `event`: a :class:`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 :class:`TabTextCtrl`.
|
|
|
|
:param `event`: a :class:`KeyEvent` event to be processed.
|
|
"""
|
|
|
|
if not self._finished:
|
|
|
|
# auto-grow the textctrl:
|
|
mySize = self.GetSize()
|
|
|
|
dc = wx.ClientDC(self)
|
|
sx, sy, dummy = dc.GetFullMultiLineTextExtent(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 :class:`TabTextCtrl`.
|
|
|
|
:param `event`: a :class:`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.tooltip = "" # tab's tooltip
|
|
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.name = ""
|
|
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 :class:`AuiNotebook` . """
|
|
|
|
def __init__(self, command_type=None, win_id=0):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:param `command_type`: the event kind or an instance of :class:`PyCommandEvent`.
|
|
:param integer `win_id`: the window identification number.
|
|
"""
|
|
|
|
if type(command_type) in six.integer_types:
|
|
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
|
|
self.page = None
|
|
|
|
|
|
def SetSelection(self, s):
|
|
"""
|
|
Sets the selection member variable.
|
|
|
|
:param integer `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 integer `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 :class:`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 :class:`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 bool `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 string `label`: the new label.
|
|
"""
|
|
|
|
self.label = label
|
|
|
|
|
|
Page = property(lambda self: self.page,
|
|
lambda self, page: setattr(self, 'page', page))
|
|
Selection = property(lambda self: self.GetSelection(), lambda self, sel: self.SetSelection(sel))
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
class AuiNotebookEvent(CommandNotebookEvent):
|
|
""" A specialized command event class for events sent by :class:`AuiNotebook`. """
|
|
|
|
def __init__(self, command_type=None, win_id=0):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:param `command_type`: the event kind or an instance of :class:`PyCommandEvent`.
|
|
:param integer `win_id`: the window identification number.
|
|
"""
|
|
|
|
CommandNotebookEvent.__init__(self, command_type, win_id)
|
|
|
|
if type(command_type) in six.integer_types:
|
|
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 :class:`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 :meth:`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 TabNavigatorProps
|
|
# ---------------------------------------------------------------------------- #
|
|
|
|
class TabNavigatorProps(object):
|
|
"""
|
|
Data storage class for managing and providing access to :class:`TabNavigatorWindow` properties.
|
|
"""
|
|
|
|
def __init__(self):
|
|
""" Default class constructor. """
|
|
|
|
super(TabNavigatorProps, self).__init__()
|
|
|
|
# Attributes
|
|
self._icon = wx.NullBitmap
|
|
self._font = wx.NullFont
|
|
self._minsize = wx.DefaultSize
|
|
|
|
# Accessors
|
|
Icon = property(lambda self: self._icon,
|
|
lambda self, icon: setattr(self, '_icon', icon),
|
|
doc='Sets/Gets the icon for the L{TabNavigatorWindow}, an instance of :class:`Bitmap`.')
|
|
Font = property(lambda self: self._font,
|
|
lambda self, font: setattr(self, '_font', font),
|
|
doc='Sets/Gets the font for the L{TabNavigatorWindow}, an instance of :class:`Font`.')
|
|
MinSize = property(lambda self: self._minsize,
|
|
lambda self, size: setattr(self, '_minsize', size),
|
|
doc='Sets/Gets the minimum size for the L{TabNavigatorWindow}, an instance of :class:`Size`.')
|
|
|
|
# ---------------------------------------------------------------------------- #
|
|
# 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, props):
|
|
"""
|
|
Default class constructor. Used internally.
|
|
|
|
:param `parent`: the :class:`TabNavigatorWindow` parent;
|
|
:param `props`: the :class:`TabNavigatorProps` object.
|
|
"""
|
|
|
|
wx.Dialog.__init__(self, parent, wx.ID_ANY, "", size=props.MinSize, style=0)
|
|
|
|
self._selectedItem = -1
|
|
self._indexMap = []
|
|
self._props = props
|
|
|
|
if not self._props.Icon.IsOk():
|
|
self._props.Icon = Mondrian.GetBitmap()
|
|
|
|
if props.Icon.GetSize() != (16, 16):
|
|
img = self._props.Icon.ConvertToImage()
|
|
img.Rescale(16, 16, wx.IMAGE_QUALITY_HIGH)
|
|
self._props.Icon = wx.Bitmap(img)
|
|
|
|
if self._props.Font.IsOk():
|
|
self.Font = self._props.Font
|
|
|
|
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.Bitmap(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(-1, panelHeight))
|
|
|
|
sz.Add(self._panel, 0, wx.EXPAND)
|
|
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.SetInitialSize(props.MinSize)
|
|
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 :class:`TabNavigatorWindow`.
|
|
|
|
:param `event`: a :class:`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 :class:`TabNavigatorWindow`.
|
|
|
|
:param `event`: a :class:`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 :class:`TabNavigatorWindow` listbox with a list of tabs.
|
|
|
|
:param `book`: the actual :class:`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 :class:`ListBox` inside :class:`TabNavigatorWindow`.
|
|
|
|
:param `event`: a :class:`ListEvent` event to be processed.
|
|
"""
|
|
|
|
self.CloseDialog()
|
|
|
|
|
|
def CloseDialog(self):
|
|
""" Closes the :class:`TabNavigatorWindow` dialog, setting selection in :class:`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 :class:`TabNavigatorWindow` top panel.
|
|
|
|
:param `event`: a :class:`PaintEvent` event to be processed.
|
|
"""
|
|
|
|
dc = wx.PaintDC(self._panel)
|
|
rect = self._panel.GetClientRect()
|
|
|
|
bmp = wx.Bitmap(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._props.Icon.GetHeight())/2
|
|
bmpPt.x = 3
|
|
mem_dc.DrawBitmap(self._props.Icon, 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._props.Icon.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 :class:`TabNavigatorWindow` top panel.
|
|
|
|
:param `event`: a :class:`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 :class:`AuiNotebook`, where it is disadvantageous to have separate
|
|
windows for each tab control in the case of "docked tabs".
|
|
|
|
A derived class, :class:`AuiTabCtrl`, is an actual :class:`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 :class:`AuiNotebook` window.
|
|
"""
|
|
|
|
self._tab_offset = 0
|
|
self._agwFlags = 0
|
|
self._art = TA.AuiDefaultTabArt()
|
|
|
|
self._buttons = []
|
|
self._pages = []
|
|
self._tab_close_buttons = []
|
|
self._click_tab = None
|
|
|
|
self._rect = wx.Rect()
|
|
self._auiNotebook = auiNotebook
|
|
|
|
self.AddButton(AUI_BUTTON_LEFT, wx.LEFT, name="Scroll Left")
|
|
self.AddButton(AUI_BUTTON_RIGHT, wx.RIGHT, name="Scroll Right")
|
|
self.AddButton(AUI_BUTTON_WINDOWLIST, wx.RIGHT, name="Window List")
|
|
self.AddButton(AUI_BUTTON_CLOSE, wx.RIGHT, name="Close")
|
|
|
|
|
|
def SetArtProvider(self, art):
|
|
"""
|
|
Instructs :class:`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 :class:`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 integer `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 :class:`AuiNotebook` tabs by mouse middle button click
|
|
``AUI_NB_SUB_NOTEBOOK`` This style is used by :class:`~lib.agw.aui.framemanager.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, name="Scroll Left")
|
|
self.AddButton(AUI_BUTTON_RIGHT, wx.RIGHT, name="Scroll Right")
|
|
|
|
if agwFlags & AUI_NB_WINDOWLIST_BUTTON:
|
|
self.AddButton(AUI_BUTTON_WINDOWLIST, wx.RIGHT, name="Window List")
|
|
|
|
if agwFlags & AUI_NB_CLOSE_BUTTON:
|
|
self.AddButton(AUI_BUTTON_CLOSE, wx.RIGHT, name="Close")
|
|
|
|
if self._art:
|
|
self._art.SetAGWFlags(self._agwFlags)
|
|
|
|
|
|
def GetAGWFlags(self):
|
|
"""
|
|
Returns the tab art flags.
|
|
|
|
.. seealso:: :meth:`SetAGWFlags` for a list of possible return values.
|
|
"""
|
|
|
|
return self._agwFlags
|
|
|
|
|
|
def SetNormalFont(self, font):
|
|
"""
|
|
Sets the normal font for drawing tab labels.
|
|
|
|
:param Font `font`: the new font to use to draw tab labels in their normal, un-selected state.
|
|
"""
|
|
|
|
self._art.SetNormalFont(font)
|
|
|
|
|
|
def SetSelectedFont(self, font):
|
|
"""
|
|
Sets the selected tab font for drawing tab labels.
|
|
|
|
:param Font `font`: the new font to use to draw tab labels in their selected state.
|
|
"""
|
|
|
|
self._art.SetSelectedFont(font)
|
|
|
|
|
|
def SetMeasuringFont(self, font):
|
|
"""
|
|
Sets the font for calculating text measurements.
|
|
|
|
:param Font `font`: the new font to use to measure tab label text extents.
|
|
"""
|
|
|
|
self._art.SetMeasuringFont(font)
|
|
|
|
|
|
def SetTabRect(self, rect):
|
|
"""
|
|
Sets the tab area rectangle.
|
|
|
|
:param Rect `rect`: the available area for :class:`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 Window `page`: the window associated with this tab;
|
|
:param `info`: an instance of :class:`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 Window `page`: the window associated with this tab;
|
|
:param `info`: an instance of :class:`AuiNotebookPage`;
|
|
:param integer `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 Window `page`: the window associated with this tab;
|
|
:param integer `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 :class:`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 :class:`AuiNotebook` active page.
|
|
|
|
:param `wndOrInt`: an instance of :class:`Window` or an integer specifying a tab index.
|
|
"""
|
|
|
|
if type(wndOrInt) in six.integer_types:
|
|
|
|
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 integer `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 :class:`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 integer `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 :class:`AuiTabContainer`. """
|
|
|
|
return self._pages
|
|
|
|
|
|
def GetPageCount(self):
|
|
""" Returns the number of pages in the :class:`AuiTabContainer`. """
|
|
|
|
return len(self._pages)
|
|
|
|
|
|
def GetEnabled(self, idx):
|
|
"""
|
|
Returns whether a tab is enabled or not.
|
|
|
|
:param integer `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 :class:`AuiTabContainer`.
|
|
|
|
:param integer `idx`: the tab index;
|
|
:param bool `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, name=""):
|
|
"""
|
|
Adds a button in the tab area.
|
|
|
|
:param integer `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 integer `location`: the button location. Can be ``wx.LEFT`` or ``wx.RIGHT``;
|
|
:param Bitmap `normal_bitmap`: the bitmap for an enabled tab;
|
|
:param Bitmap `disabled_bitmap`: the bitmap for a disabled tab;
|
|
:param string `name`: the button name.
|
|
"""
|
|
|
|
button = AuiTabContainerButton()
|
|
button.id = id
|
|
button.name = name
|
|
button.bitmap = normal_bitmap
|
|
button.dis_bitmap = disabled_bitmap
|
|
button.location = location
|
|
button.cur_state = AUI_BUTTON_STATE_NORMAL
|
|
|
|
self._buttons.append(button)
|
|
|
|
|
|
def CloneButtons(self):
|
|
"""
|
|
Clones the tab area buttons when the :class:`AuiNotebook` is being split.
|
|
|
|
:see: :meth:`AddButton`
|
|
|
|
:note: Standard buttons for :class:`AuiNotebook` are not cloned, only custom ones.
|
|
"""
|
|
|
|
singleton_list = [AUI_BUTTON_CLOSE, AUI_BUTTON_WINDOWLIST, AUI_BUTTON_LEFT, AUI_BUTTON_RIGHT]
|
|
clones = []
|
|
|
|
for button in self._buttons:
|
|
if button.id not in singleton_list:
|
|
new_button = AuiTabContainerButton()
|
|
new_button.id = button.id
|
|
new_button.bitmap = button.bitmap
|
|
new_button.dis_bitmap = button.dis_bitmap
|
|
new_button.location = button.location
|
|
clones.append(new_button)
|
|
|
|
return clones
|
|
|
|
|
|
def RemoveButton(self, id):
|
|
"""
|
|
Removes a button from the tab area.
|
|
|
|
:param integer `id`: the button identifier. See :meth:`AddButton` for a list of button identifiers.
|
|
|
|
:see: :meth:`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 integer `offset`: the tab offset.
|
|
"""
|
|
|
|
self._tab_offset = offset
|
|
|
|
|
|
def Render(self, raw_dc, wnd):
|
|
"""
|
|
Renders the tab catalog to the specified :class:`DC`.
|
|
|
|
It is a virtual function and can be overridden to provide custom drawing
|
|
capabilities.
|
|
|
|
:param `raw_dc`: a :class:`DC` device context;
|
|
:param `wnd`: an instance of :class:`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.Bitmap(self._rect.GetWidth(), self._rect.GetHeight())
|
|
dc.SelectObject(bmp)
|
|
|
|
if not dc.IsOk():
|
|
return
|
|
|
|
# prepare the tab-close-button array
|
|
# make sure tab button entries which aren't used are marked as hidden
|
|
for i in range(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)
|
|
|
|
# find out if size of tabs is larger than can be
|
|
# afforded on screen
|
|
total_width = visible_width = 0
|
|
tab_width = [0] * page_count
|
|
|
|
for i in range(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 RuntimeError:
|
|
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
|
|
tab_width[i] = x_extent
|
|
else:
|
|
total_width += size[0]
|
|
tab_width[i] = size[0]
|
|
|
|
if i >= self._tab_offset:
|
|
if i+1 < page_count:
|
|
visible_width += x_extent
|
|
else:
|
|
visible_width += size[0]
|
|
|
|
# Calculate the width of visible buttons
|
|
buttons_width = 0
|
|
|
|
for button in self._buttons:
|
|
if not (button.cur_state & AUI_BUTTON_STATE_HIDDEN):
|
|
buttons_width += button.rect.GetWidth()
|
|
|
|
total_width += buttons_width
|
|
|
|
if (total_width > self._rect.GetWidth() and page_count > 1) 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
|
|
|
|
# Re-calculate the width of visible buttons (may have been hidden/shown)
|
|
buttons_width = 0
|
|
for button in self._buttons:
|
|
if not (button.cur_state & AUI_BUTTON_STATE_HIDDEN):
|
|
buttons_width += button.rect.GetWidth()
|
|
|
|
# Shift the tab offset down to make use of available space
|
|
available_width = self._rect.GetWidth() - buttons_width
|
|
while self._tab_offset > 0 and visible_width + tab_width[self._tab_offset - 1] < available_width:
|
|
self._tab_offset -= 1
|
|
visible_width += tab_width[self._tab_offset]
|
|
|
|
# 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() - buttons_width:
|
|
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 range(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 range(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()
|
|
|
|
# buttons before the tab offset must be set to hidden
|
|
for i in range(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()
|
|
|
|
# draw tab before tab offset
|
|
if self._tab_offset > 0:
|
|
page = self._pages[self._tab_offset - 1]
|
|
tab_button = self._tab_close_buttons[self._tab_offset - 1]
|
|
size, x_extent = self._art.GetTabSize(dc, wnd, page.caption, page.bitmap, page.active, tab_button.cur_state, page.control)
|
|
|
|
rect = wx.Rect(offset - x_extent, 0, self._rect.width - right_buttons_width - offset - x_extent - 2, self._rect.height)
|
|
clip_rect = wx.Rect(*self._rect)
|
|
clip_rect.x = offset
|
|
|
|
dc.SetClippingRegion(clip_rect)
|
|
self._art.DrawTab(dc, wnd, page, rect, tab_button.cur_state)
|
|
dc.DestroyClippingRegion()
|
|
|
|
# draw the tabs
|
|
active = 999
|
|
active_offset = 0
|
|
|
|
rect = wx.Rect(*self._rect)
|
|
rect.y = 0
|
|
rect.height = self._rect.height
|
|
|
|
for i in range(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 range(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 integer `tabPage`: the tab index;
|
|
:param integer `tabOffset`: the tab offset;
|
|
:param `dc`: a :class:`DC` device context;
|
|
:param `wnd`: an instance of :class:`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 range(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
|
|
|
|
# calculate size of the buttons on the right side
|
|
offset = self._rect.x + self._rect.width
|
|
|
|
for i in range(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 range(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 range(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 integer `tabPage`: the tab index;
|
|
:param `win`: an instance of :class:`Window` derived window.
|
|
"""
|
|
|
|
dc = wx.ClientDC(win)
|
|
|
|
if not self.IsTabVisible(tabPage, self.GetTabOffset(), dc, win):
|
|
for i in range(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 integer `x`: the mouse `x` position;
|
|
:param integer `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 range(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, state_flags=AUI_BUTTON_STATE_HIDDEN|AUI_BUTTON_STATE_DISABLED):
|
|
"""
|
|
Tests if a button was hit.
|
|
|
|
:param integer `x`: the mouse `x` position;
|
|
:param integer `y`: the mouse `y` position;
|
|
:param integer `state_flags`: the current button state (hidden, disabled, etc...).
|
|
|
|
:returns: and instance of :class:`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 & state_flags) == 0:
|
|
return button
|
|
|
|
for button in self._tab_close_buttons:
|
|
if button.rect.Contains((x,y)) and \
|
|
(button.cur_state & state_flags) == 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.Control, AuiTabContainer):
|
|
"""
|
|
This is an actual :class:`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 :class:`AuiNotebook` parent;
|
|
:param integer `id`: an identifier for the control: a value of -1 is taken to mean a default;
|
|
:param Point `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 `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 integer `style`: the window style.
|
|
"""
|
|
|
|
wx.Control.__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._tooltip_timer = None
|
|
self._tooltip_wnd = None
|
|
|
|
self.Bind(wx.EVT_PAINT, self.OnPaint)
|
|
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
|
|
self.Bind(wx.EVT_SIZE, self.OnSize)
|
|
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
|
|
self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
|
|
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
|
|
self.Bind(wx.EVT_MIDDLE_DOWN, self.OnMiddleDown)
|
|
self.Bind(wx.EVT_MIDDLE_UP, self.OnMiddleUp)
|
|
self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
|
|
self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp)
|
|
self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
|
|
self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
|
|
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
|
|
self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.OnCaptureLost)
|
|
self.Bind(wx.EVT_MOTION, self.OnMotion)
|
|
self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnterWindow)
|
|
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 :class:`AuiTabCtrl`. """
|
|
|
|
return wx.BORDER_NONE
|
|
|
|
|
|
def OnPaint(self, event):
|
|
"""
|
|
Handles the ``wx.EVT_PAINT`` event for :class:`AuiTabCtrl`.
|
|
|
|
:param `event`: a :class:`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 :class:`AuiTabCtrl`.
|
|
|
|
:param `event`: a :class:`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 :class:`Control`.
|
|
"""
|
|
|
|
return wx.Size(self._rect.width, self._rect.height)
|
|
|
|
|
|
def OnSize(self, event):
|
|
"""
|
|
Handles the ``wx.EVT_SIZE`` event for :class:`AuiTabCtrl`.
|
|
|
|
:param `event`: a :class:`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 :class:`AuiTabCtrl`.
|
|
|
|
:param `event`: a :class:`MouseEvent` event to be processed.
|
|
"""
|
|
|
|
self.StopTooltipTimer()
|
|
|
|
if not self.HasCapture():
|
|
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)) and \
|
|
not self._hover_button:
|
|
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
|
|
|
|
wnd.SetFocus()
|
|
else:
|
|
page_index = self.GetActivePage()
|
|
if page_index != wx.NOT_FOUND:
|
|
self.GetWindowFromIdx(page_index).SetFocus()
|
|
|
|
self._hover_button = self.ButtonHitTest(event.GetX(), event.GetY())
|
|
|
|
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 :class:`AuiTabCtrl`.
|
|
|
|
:param `event`: a :class:`MouseCaptureLostEvent` event to be processed.
|
|
"""
|
|
|
|
if self._click_tab:
|
|
e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_LEFT_UP, self.GetId())
|
|
e.SetEventObject(self)
|
|
e.SetSelection(self.GetIdxFromWindow(self._click_tab))
|
|
self.GetEventHandler().ProcessEvent(e)
|
|
|
|
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)
|
|
|
|
if self._hover_button:
|
|
self._hover_button.cur_state = AUI_BUTTON_STATE_NORMAL
|
|
self._hover_button = None
|
|
|
|
self.Refresh()
|
|
self.Update()
|
|
|
|
|
|
def OnLeftUp(self, event):
|
|
"""
|
|
Handles the ``wx.EVT_LEFT_UP`` event for :class:`AuiTabCtrl`.
|
|
|
|
:param `event`: a :class:`MouseEvent` event to be processed.
|
|
"""
|
|
|
|
if self._click_tab:
|
|
e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_LEFT_UP, self.GetId())
|
|
e.SetEventObject(self)
|
|
e.SetSelection(self.GetIdxFromWindow(self._click_tab))
|
|
self.GetEventHandler().ProcessEvent(e)
|
|
elif not self.ButtonHitTest(event.GetX(), event.GetY()):
|
|
e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_LEFT_UP, self.GetId())
|
|
e.SetEventObject(self)
|
|
self.GetEventHandler().ProcessEvent(e)
|
|
|
|
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
|
|
self._hover_button.cur_state = AUI_BUTTON_STATE_NORMAL
|
|
|
|
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 :class:`AuiTabCtrl`.
|
|
|
|
:param `event`: a :class:`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.Page = wnd
|
|
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 :class:`AuiTabCtrl`.
|
|
|
|
:param `event`: a :class:`MouseEvent` event to be processed.
|
|
"""
|
|
|
|
self.StopTooltipTimer()
|
|
|
|
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.Page = wnd
|
|
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 :class:`AuiTabCtrl`.
|
|
|
|
:param `event`: a :class:`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.Selection = self.GetIdxFromWindow(wnd)
|
|
e.Page = 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 :class:`AuiTabCtrl`.
|
|
|
|
:param `event`: a :class:`MouseEvent` event to be processed.
|
|
"""
|
|
|
|
self.StopTooltipTimer()
|
|
|
|
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))
|
|
e.Page = 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 :class:`AuiTabCtrl`.
|
|
|
|
:param `event`: a :class:`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))
|
|
e.Page = wnd
|
|
self.GetEventHandler().ProcessEvent(e)
|
|
elif not self.ButtonHitTest(x, y, state_flags=AUI_BUTTON_STATE_HIDDEN):
|
|
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 :class:`AuiTabCtrl`.
|
|
|
|
:param `event`: a :class:`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):
|
|
|
|
# if the mouse has moved from one tab to another then restart the
|
|
# tooltip timer.
|
|
if wnd != self._tooltip_wnd or (wnd is None and self._hover_button is not None):
|
|
self.RestartTooltipTimer(wnd)
|
|
|
|
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.HasCapture():
|
|
self.ReleaseMouse()
|
|
|
|
if self._agwFlags & AUI_NB_TAB_FLOAT:
|
|
self._drag_image.BeginDrag(wx.Point(0,0), self, fullScreen=True)
|
|
else:
|
|
self._drag_image.BeginDrag(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 GetPointedToTab(self):
|
|
"""
|
|
Returns the page at which the mouse is pointing (if any).
|
|
|
|
:rtype: :class:`Window`.
|
|
"""
|
|
|
|
screen_pt = wx.GetMousePosition()
|
|
client_pt = self.ScreenToClient(screen_pt)
|
|
return self.TabHitTest(client_pt.x, client_pt.y)
|
|
|
|
|
|
def RestartTooltipTimer(self, wnd):
|
|
"""
|
|
Starts a timer: when it fires, a tooltip will be shown on the notebook tab
|
|
the mouse is pointing at.
|
|
|
|
:param Window `wnd`: the window pointed by the mouse.
|
|
"""
|
|
|
|
self._tooltip_wnd = wnd
|
|
if (wnd is None and self._hover_button is None) or not wx.GetApp().IsActive():
|
|
self.StopTooltipTimer()
|
|
elif self._tooltip_timer:
|
|
self._tooltip_timer.Start()
|
|
else:
|
|
self._tooltip_timer = wx.CallLater(1000, self.ShowTooltip)
|
|
|
|
|
|
def StopTooltipTimer(self):
|
|
""" Stops the timer keeping track of tooltips and mouse movements on the tab area. """
|
|
|
|
if self._tooltip_timer:
|
|
self._tooltip_timer.Stop()
|
|
self._tooltip_timer = None
|
|
|
|
|
|
def OnEnterWindow(self, event):
|
|
"""
|
|
Handles the ``wx.EVT_ENTER_WINDOW`` event fof :class:`AuiTabCtrl`.
|
|
|
|
:param `event`: a :class:`MouseEvent` event to be processed.
|
|
"""
|
|
|
|
self.RestartTooltipTimer(self.GetPointedToTab())
|
|
|
|
|
|
def ShowTooltip(self):
|
|
""" Shows the tooltip on the tab. """
|
|
|
|
wnd = self.GetPointedToTab()
|
|
if wnd != self._tooltip_wnd:
|
|
self.RestartTooltipTimer(wnd)
|
|
else:
|
|
idx = self.GetIdxFromWindow(wnd)
|
|
if idx >= 0 and idx < len(self._pages):
|
|
page = self._pages[idx]
|
|
if page.tooltip:
|
|
pos = self.ClientToScreen(page.rect.GetPosition())
|
|
rect = wx.Rect(pos, page.rect.GetSize())
|
|
tooltip = wx.TipWindow(self, page.tooltip)
|
|
tooltip.SetBoundingRect(rect)
|
|
else:
|
|
pos = self.ScreenToClient(wx.GetMousePosition())
|
|
button = self.ButtonHitTest(pos.x, pos.y)
|
|
if button:
|
|
pos = self.ClientToScreen(button.rect.GetPosition())
|
|
rect = wx.Rect(pos, button.rect.GetSize())
|
|
tooltip = wx.TipWindow(self, button.name)
|
|
tooltip.SetBoundingRect(rect)
|
|
|
|
|
|
def OnLeaveWindow(self, event):
|
|
"""
|
|
Handles the ``wx.EVT_LEAVE_WINDOW`` event for :class:`AuiTabCtrl`.
|
|
|
|
:param `event`: a :class:`MouseEvent` event to be processed.
|
|
"""
|
|
|
|
self.StopTooltipTimer()
|
|
|
|
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 :class:`AuiTabCtrl`.
|
|
|
|
:param `event`: a :class:`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 :class:`AuiTabCtrl`.
|
|
|
|
:param `event`: a :class:`FocusEvent` event to be processed.
|
|
"""
|
|
|
|
self.Refresh()
|
|
|
|
|
|
def OnKillFocus(self, event):
|
|
"""
|
|
Handles the ``wx.EVT_KILL_FOCUS`` event for :class:`AuiTabCtrl`.
|
|
|
|
:param `event`: a :class:`FocusEvent` event to be processed.
|
|
"""
|
|
|
|
self.Refresh()
|
|
|
|
|
|
def OnKeyDown(self, event):
|
|
"""
|
|
Handles the ``wx.EVT_KEY_DOWN`` event for :class:`AuiTabCtrl`.
|
|
|
|
:param `event`: a :class:`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):
|
|
"""
|
|
Handles the ``wx.EVT_KEY_DOWN`` event for :class:`AuiTabCtrl`.
|
|
|
|
:param `event`: a :class:`KeyEvent` event to be processed.
|
|
|
|
.. deprecated:: 0.6
|
|
This implementation is now deprecated. Refer to :meth:`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.Window):
|
|
"""
|
|
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.Window.__init__(self)
|
|
|
|
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.Create(parent, size=(0, 0))
|
|
|
|
|
|
def SetTabCtrlHeight(self, h):
|
|
"""
|
|
Sets the tab control height.
|
|
|
|
:param integer `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 integer `x`: the window `x` position;
|
|
:param integer `y`: the window `y` position;
|
|
:param integer `width`: the window width;
|
|
:param integer `height`: the window height;
|
|
:param integer `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 :class:`Control`.
|
|
"""
|
|
|
|
self._rect = wx.Rect(x, y, max(1, width), max(1, height))
|
|
self.DoSizing()
|
|
|
|
|
|
def DoGetSize(self):
|
|
"""
|
|
Returns the window size.
|
|
|
|
:note: Overridden from :class:`Control`.
|
|
"""
|
|
|
|
return self._rect.width, self._rect.height
|
|
|
|
|
|
def DoGetClientSize(self):
|
|
"""
|
|
Returns the window client size.
|
|
|
|
:note: Overridden from :class:`Control`.
|
|
"""
|
|
|
|
return self._rect.width, self._rect.height
|
|
|
|
|
|
def Show(self, show=True):
|
|
"""
|
|
Shows/hides the window.
|
|
|
|
:param bool `show`: ``True`` to show the window, ``False`` otherwise.
|
|
|
|
:note:
|
|
|
|
Overridden from :class:`Control`, this method always returns ``False`` as
|
|
:class:`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.SetSize(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.SetSize(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.SetSize(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.SetSize(self._rect.x, self._rect.y, self._rect.width, height)
|
|
|
|
else:
|
|
page.window.SetSize(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 :class:`Control`.
|
|
"""
|
|
|
|
# does nothing
|
|
pass
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
# -- AuiNotebook class implementation --
|
|
|
|
class AuiNotebook(wx.Panel):
|
|
"""
|
|
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 :class:`Notebook`.
|
|
|
|
The default theme that is used is :class:`~lib.agw.aui.tabart.AuiDefaultTabArt`, which provides a modern, glossy
|
|
look and feel. The theme can be changed by calling :meth:`AuiNotebook.SetArtProvider`.
|
|
"""
|
|
|
|
def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
|
|
style=0, agwStyle=AUI_NB_DEFAULT_STYLE, name="AuiNotebook"):
|
|
"""
|
|
Default class constructor.
|
|
|
|
:param Window `parent`: the :class:`AuiNotebook` parent;
|
|
:param integer `id`: an identifier for the control: a value of -1 is taken to mean a default;
|
|
:param Point `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 `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 integer `style`: the underlying :class:`Panel` window style;
|
|
:param integer `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 :class:`AuiNotebook` tabs by mouse middle button click
|
|
``AUI_NB_SUB_NOTEBOOK`` This style is used by :class:`~lib.agw.aui.framemanager.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``
|
|
|
|
:param string `name`: the window name.
|
|
"""
|
|
|
|
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)
|
|
|
|
wx.Panel.__init__(self, parent, id, pos, size, style|wx.BORDER_NONE|wx.TAB_TRAVERSAL, name=name)
|
|
from . import framemanager
|
|
self._mgr = framemanager.AuiManager()
|
|
self._tabs = AuiTabContainer(self)
|
|
|
|
self.InitNotebook(agwStyle)
|
|
|
|
NavigatorProps = property(lambda self: self._navProps)
|
|
|
|
|
|
def Destroy(self):
|
|
"""
|
|
Destroys the window safely.
|
|
|
|
Use this function instead of the ``del`` operator, since different window
|
|
classes can be destroyed differently. Frames and dialogs are not destroyed
|
|
immediately when this function is called -- they are added to a list of
|
|
windows to be deleted on idle time, when all the window's events have been
|
|
processed. This prevents problems with events being sent to non-existent windows.
|
|
|
|
:return: ``True`` if the window has either been successfully deleted, or
|
|
it has been added to the list of windows pending real deletion.
|
|
|
|
.. note::
|
|
|
|
This method has been added to safely un-initialize the underlying
|
|
:class:`~lib.agw.aui.framemanager.AuiManager` which manages the :class:`AuiNotebook`
|
|
layout (i.e., tab split, re-ordering, tab floating etc...).
|
|
|
|
"""
|
|
|
|
self._mgr.UnInit()
|
|
return wx.Panel.Destroy(self)
|
|
|
|
|
|
def __getitem__(self, index):
|
|
"""
|
|
More Pythonic way to get a specific page, also useful for iterating
|
|
over all pages.
|
|
|
|
:param integer `index`: the page index.
|
|
|
|
.. note::
|
|
|
|
This method makes easier to iterate over all the pages in the notebook, i.e. you can
|
|
safely do::
|
|
|
|
for page in notebook:
|
|
DoSomething(page)
|
|
|
|
|
|
"""
|
|
|
|
if index < self.GetPageCount():
|
|
return self.GetPage(index)
|
|
else:
|
|
raise IndexError("Invalid page index")
|
|
|
|
|
|
def GetTabContainer(self):
|
|
""" Returns the instance of :class:`AuiTabContainer`. """
|
|
|
|
return self._tabs
|
|
|
|
|
|
def InitNotebook(self, agwStyle):
|
|
"""
|
|
Contains common initialization code called by all constructors.
|
|
|
|
:param integer `agwStyle`: the notebook style.
|
|
|
|
:see: :meth:`~AuiNotebook.__init__` for a list of available `agwStyle` bits.
|
|
"""
|
|
|
|
from . import framemanager
|
|
|
|
self._agwFlags = agwStyle
|
|
|
|
self._popupWin = None
|
|
self._imageList = None
|
|
self._navProps = TabNavigatorProps()
|
|
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 :class:`Config <ConfigBase>`). When a perspective
|
|
is restored using :meth:`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 range(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 :meth:`SavePerspective`.
|
|
|
|
:param string `layout`: a string which contains a saved :class:`AuiNotebook` layout.
|
|
"""
|
|
from . import framemanager
|
|
|
|
# Remove all tab ctrls (but still keep them in main index)
|
|
tab_count = self._tabs.GetPageCount()
|
|
for i in range(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 :meth:`SetUniformBitmapSize`. Specifying -1 as the height will
|
|
return the control to its default auto-sizing behaviour.
|
|
|
|
:param integer `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 method 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 `size`: 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):
|
|
"""
|
|
:meth:`UpdateTabCtrlHeight` does the actual tab resizing. It's meant
|
|
to be used interally.
|
|
|
|
:param bool `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 :class:`~lib.agw.aui.framemanager.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 integer `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 :class:`AuiNotebook` tabs by mouse middle button click
|
|
``AUI_NB_SUB_NOTEBOOK`` This style is used by :class:`~lib.agw.aui.framemanager.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: :meth:`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, tooltip=""):
|
|
"""
|
|
Adds a page. If the `select` parameter is ``True``, calling this will generate a
|
|
page change event.
|
|
|
|
:param Window `page`: the page to be added;
|
|
:param string `caption`: specifies the text for the new page;
|
|
:param bool `select`: specifies whether the page should be selected;
|
|
:param Bitmap `bitmap`: the bitmap to display in the enabled tab;
|
|
:param Bitmap `disabled_bitmap`: the bitmap to display in the disabled tab;
|
|
:param Window `control`: almost any :class:`Window` -derived instance to be located
|
|
inside a tab;
|
|
:param string `tooltip`: the tooltip to display when the mouse hovers over the tab.
|
|
"""
|
|
|
|
return self.InsertPage(self.GetPageCount(), page, caption, select, bitmap, disabled_bitmap, control, tooltip)
|
|
|
|
|
|
def InsertPage(self, page_idx, page, caption, select=False, bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap,
|
|
control=None, tooltip=""):
|
|
"""
|
|
This is similar to :meth:`AddPage`, but allows the ability to specify the insert location.
|
|
|
|
:param integer `page_idx`: specifies the position for the new page;
|
|
:param Window `page`: the page to be added;
|
|
:param string `caption`: specifies the text for the new page;
|
|
:param bool `select`: specifies whether the page should be selected;
|
|
:param Bitmap `bitmap`: the bitmap to display in the enabled tab;
|
|
:param Bitmap `disabled_bitmap`: the bitmap to display in the disabled tab;
|
|
:param Window `control`: almost any :class:`Window` -derived instance to be located
|
|
inside a ;
|
|
:param string `tooltip`: the tooltip to display when the mouse hovers over the tab.
|
|
"""
|
|
from . import framemanager
|
|
|
|
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
|
|
info.tooltip = tooltip
|
|
|
|
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 integer `page_idx`: the page index to be deleted.
|
|
|
|
:note:
|
|
|
|
:meth:`DeletePage` removes a tab from the multi-notebook, and destroys the window as well.
|
|
|
|
:see: :meth:`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 integer `page_idx`: the page index to be removed.
|
|
|
|
:note:
|
|
|
|
:meth:`RemovePage` removes a tab from the multi-notebook, but does not destroy the window.
|
|
|
|
:see: :meth:`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 :class:`AuiNotebook` has inactive/disabled
|
|
tabs in it).
|
|
|
|
:param integer `ctrl_idx`: the index of the first (most obvious) tab to check for active status;
|
|
:param `ctrl`: an instance of :class:`AuiTabCtrl`.
|
|
"""
|
|
|
|
if self.GetEnabled(ctrl_idx):
|
|
return ctrl_idx
|
|
|
|
for indx in range(ctrl_idx, ctrl.GetPageCount()):
|
|
if self.GetEnabled(indx):
|
|
return indx
|
|
|
|
for indx in range(ctrl_idx, -1, -1):
|
|
if self.GetEnabled(indx):
|
|
return indx
|
|
|
|
return 0
|
|
|
|
|
|
def HideAllTabs(self, hidden=True):
|
|
"""
|
|
Hides all tabs on the :class:`AuiNotebook` control.
|
|
|
|
:param bool `hidden`: if ``True`` hides all tabs.
|
|
"""
|
|
|
|
self._hide_tabs = hidden
|
|
|
|
|
|
def SetSashDClickUnsplit(self, unsplit=True):
|
|
"""
|
|
Sets whether to unsplit a splitted :class:`AuiNotebook` when double-clicking on a sash.
|
|
|
|
:param bool `unsplit`: ``True`` to unsplit on sash double-clicking, ``False`` otherwise.
|
|
"""
|
|
|
|
self._sash_dclick_unsplit = unsplit
|
|
|
|
|
|
def GetSashDClickUnsplit(self):
|
|
"""
|
|
Returns whether a splitted :class:`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 :class:`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 :class:`AuiNotebook`.
|
|
|
|
:param integer `minTabWidth`: the minimum allowed tab width, in pixels;
|
|
:param integer `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 :class:`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: :meth:`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.
|
|
|
|
:param Window `page_wnd`: the window we are looking for.
|
|
"""
|
|
|
|
return self._tabs.GetIdxFromWindow(page_wnd)
|
|
|
|
|
|
def SetPageText(self, page_idx, text):
|
|
"""
|
|
Sets the tab label for the page.
|
|
|
|
:param integer `page_idx`: the page index;
|
|
:param string `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 integer `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 integer `page_idx`: the page index;
|
|
:param Bitmap `bitmap`: the bitmap to display on the page tab.
|
|
"""
|
|
|
|
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 integer `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 SetPageTooltip(self, page_idx, tooltip):
|
|
"""
|
|
Sets the tab tooltip for the page.
|
|
|
|
:param integer `page_idx`: the page index;
|
|
:param string `tooltip`: the new tooltip.
|
|
|
|
:returns: ``True`` if the page tooltip has been set, ``False`` otherwise
|
|
(for example when the input `page_idx` is greater than the number of
|
|
pages in the notebook.
|
|
"""
|
|
|
|
if page_idx >= self._tabs.GetPageCount():
|
|
return False
|
|
|
|
# update our own tab catalog
|
|
page_info = self._tabs.GetPage(page_idx)
|
|
page_info.tooltip = tooltip
|
|
return True
|
|
|
|
|
|
def GetPageTooltip(self, page_idx):
|
|
"""
|
|
Returns the tab tooltip for the page.
|
|
|
|
:param integer `page_idx`: the page index.
|
|
"""
|
|
|
|
if page_idx >= self._tabs.GetPageCount():
|
|
return ""
|
|
|
|
page_info = self._tabs.GetPage(page_idx)
|
|
return page_info.tooltip
|
|
|
|
|
|
def SetImageList(self, imageList):
|
|
"""
|
|
Sets the image list for the :class:`AuiNotebook` control.
|
|
|
|
:param ImageList `imageList`: the bitmap image list to associate to :class:`AuiNotebook`.
|
|
"""
|
|
|
|
self._imageList = imageList
|
|
|
|
|
|
def AssignImageList(self, imageList):
|
|
"""
|
|
Sets the image list for the :class:`AuiNotebook` control.
|
|
|
|
:param `imageList`: an instance of :class:`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 integer `page`: the page index;
|
|
:param integer `image`: an index into the image list which was set with :meth:`SetImageList`.
|
|
"""
|
|
|
|
if page >= self._tabs.GetPageCount():
|
|
return False
|
|
|
|
if not isinstance(image, six.integer_types):
|
|
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 integer `page`: the given page for which to retrieve the image index.
|
|
"""
|
|
|
|
if page >= self._tabs.GetPageCount():
|
|
return wx.NOT_FOUND
|
|
|
|
bitmap = self.GetPageBitmap(page)
|
|
bmpData1 = bitmap.ConvertToImage().GetData()
|
|
|
|
for indx in range(self._imageList.GetImageCount()):
|
|
imgListBmp = self._imageList.GetBitmap(indx)
|
|
bmpData2 = imgListBmp.ConvertToImage().GetData()
|
|
if bmpData1 == bmpData2:
|
|
return indx
|
|
|
|
return wx.NOT_FOUND
|
|
|
|
|
|
def SetPageTextColour(self, page_idx, colour):
|
|
"""
|
|
Sets the tab text colour for the page.
|
|
|
|
:param integer `page_idx`: the page index;
|
|
:param Colour `colour`: the new tab label text 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 integer `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 integer `page_idx`: the page index;
|
|
:param Window `control`: almost any :class:`Window` -derived instance to be located
|
|
inside a tab.
|
|
"""
|
|
|
|
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 integer `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 integer `page_idx`: the page index;
|
|
:param bool `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 integer `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 integer `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 integer `new_page`: the index of the new selection;
|
|
:param bool `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 :class:`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 :class:`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 integer `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 :class:`AuiNotebookPage` info structure specified by the given index.
|
|
|
|
:param integer `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 integer `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 integer `page_idx`: the page index;
|
|
:param bool `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 :class:`~lib.agw.aui.framemanager.AuiManager`. """
|
|
|
|
return self._mgr
|
|
|
|
|
|
def GetActiveTabCtrl(self):
|
|
"""
|
|
Returns the active tab control. It is called to determine which control
|
|
gets new windows being added.
|
|
"""
|
|
from . import framemanager
|
|
|
|
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 :class:`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 integer `page`: indicates the page that will be split off. This page will also become
|
|
the active page after the split.
|
|
:param integer `direction`: specifies where the pane should go, it should be one of the
|
|
following: ``wx.TOP``, ``wx.BOTTOM``, ``wx.LEFT``, or ``wx.RIGHT``.
|
|
"""
|
|
from . import framemanager
|
|
|
|
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
|
|
|
|
selection = self.GetSelection()
|
|
|
|
# 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.Rect(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)
|
|
|
|
cloned_buttons = self.CloneTabAreaButtons()
|
|
for clone in cloned_buttons:
|
|
dest_tabs.AddButton(clone.id, clone.location, clone.bitmap, clone.dis_bitmap)
|
|
# 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:
|
|
if selection < 0 or selection == src_idx:
|
|
active_page = 0
|
|
else:
|
|
if selection > src_idx:
|
|
selection -= 1
|
|
|
|
active_page = selection
|
|
|
|
src_tabs.SetActivePage(active_page)
|
|
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 range(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 Window `control`: almost any :class:`Window` -derived instance to be located
|
|
inside a tab;
|
|
:param `dest_tabs`: the destination :class:`AuiTabCtrl`.
|
|
"""
|
|
|
|
control.Hide()
|
|
control.Reparent(dest_tabs)
|
|
|
|
|
|
def UnsplitDClick(self, part, sash_size, pos):
|
|
"""
|
|
Unsplit the :class:`AuiNotebook` on sash double-click.
|
|
|
|
:param `part`: an UI part representing the sash;
|
|
:param integer `sash_size`: the sash size;
|
|
:param Point `pos`: the double-click mouse position.
|
|
|
|
.. warning::
|
|
|
|
Due to a bug on MSW, for disabled pages :func:`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 range(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 :class:`AuiNotebook`.
|
|
|
|
:param `event`: a :class:`SizeEvent` event to be processed.
|
|
"""
|
|
|
|
self.UpdateHintWindowSize()
|
|
event.Skip()
|
|
|
|
|
|
def OnTabClicked(self, event):
|
|
"""
|
|
Handles the ``EVT_AUINOTEBOOK_PAGE_CHANGING`` event for :class:`AuiNotebook`.
|
|
|
|
:param `event`: a :class:`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 :class:`AuiNotebook`.
|
|
|
|
:param `event`: a :class:`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 :class:`AuiNotebook`.
|
|
|
|
:param `event`: a :class:`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 :class:`AuiNotebook`.
|
|
|
|
:param `event`: a :class:`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 :class:`AuiNotebook`.
|
|
|
|
:param `event`: a :class:`AuiNotebookEvent` event to be processed.
|
|
"""
|
|
|
|
self._curpage = event.GetSelection()
|
|
|
|
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.Rect(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.ClientToScreen((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.Rect(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.ClientToScreen((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.ClientToScreen((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 :class:`AuiNotebook`.
|
|
|
|
:param `event`: a :class:`AuiNotebookEvent` event to be processed.
|
|
"""
|
|
from . import framemanager
|
|
|
|
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.Rect(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
|
|
|
|
cloned_buttons = self.CloneTabAreaButtons()
|
|
for clone in cloned_buttons:
|
|
dest_tabs.AddButton(clone.id, clone.location, clone.bitmap, clone.dis_bitmap)
|
|
# 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 :class:`AuiNotebook`.
|
|
|
|
:param `event`: a :class:`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 :class:`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 integer `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.
|
|
|
|
"""
|
|
from . import framemanager
|
|
|
|
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 :class:`AuiNotebook`.
|
|
|
|
:param `event`: a :class:`CloseEvent` event to be processed.
|
|
"""
|
|
from . import framemanager
|
|
|
|
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 :class:`AuiNotebook` tab in the original position, when possible.
|
|
|
|
:param `pane`: an instance of :class:`~lib.agw.aui.framemanager.AuiPaneInfo`.
|
|
"""
|
|
from . import framemanager
|
|
|
|
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 Point `pt`: the mouse location.
|
|
"""
|
|
|
|
# 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 :class:`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 Window `wnd`: the window for which we want to locate the :class:`TabFrame`.
|
|
"""
|
|
|
|
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 range(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 :class:`AuiNotebook`.
|
|
|
|
:param `event`: a :class:`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 :class:`TabNavigatorWindow`.
|
|
|
|
:param Bitmap `bmp`: the new bitmap for the :class:`TabNavigatorWindow`.
|
|
"""
|
|
|
|
if isinstance(bmp, wx.Bitmap) and bmp.IsOk():
|
|
self.NavigatorProps.Icon = bmp
|
|
else:
|
|
raise TypeError("SetNavigatorIcon requires a valid bitmap")
|
|
|
|
|
|
def OnNavigationKeyNotebook(self, event):
|
|
"""
|
|
Handles the ``wx.EVT_NAVIGATION_KEY`` event for :class:`AuiNotebook`.
|
|
|
|
:param `event`: a :class:`NavigationKeyEvent` event to be processed.
|
|
"""
|
|
|
|
if event.IsWindowChange():
|
|
if self._agwFlags & AUI_NB_SMART_TABS:
|
|
if not self._popupWin:
|
|
self._popupWin = TabNavigatorWindow(self, self.NavigatorProps)
|
|
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 :class:`AuiNotebook`.
|
|
|
|
:param `event`: a :class:`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 range(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 :class:`AuiNotebook`.
|
|
|
|
:param `event`: a :class:`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 :class:`AuiNotebook`.
|
|
|
|
:param `event`: a :class:`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 :class:`AuiNotebook`.
|
|
|
|
:param `event`: a :class:`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 :class:`AuiNotebook`.
|
|
|
|
:param `event`: a :class:`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 `font`: the new font to use to draw tab labels in their normal, un-selected state.
|
|
"""
|
|
|
|
self._normal_font = font
|
|
self.GetArtProvider().SetNormalFont(font)
|
|
|
|
|
|
def SetSelectedFont(self, font):
|
|
"""
|
|
Sets the selected tab font for drawing tab labels.
|
|
|
|
:param Font `font`: the new font to use to draw tab labels in their selected state.
|
|
"""
|
|
|
|
self._selected_font = font
|
|
self.GetArtProvider().SetSelectedFont(font)
|
|
|
|
|
|
def SetMeasuringFont(self, font):
|
|
"""
|
|
Sets the font for calculating text measurements.
|
|
|
|
:param Font `font`: the new font to use to measure tab label text extents.
|
|
"""
|
|
|
|
self.GetArtProvider().SetMeasuringFont(font)
|
|
|
|
|
|
def SetFont(self, font):
|
|
"""
|
|
Sets the tab font.
|
|
|
|
:param Font `font`: the new font to use to draw tab labels in their normal, un-selected state.
|
|
|
|
:note: Overridden from :class:`Panel`.
|
|
"""
|
|
|
|
wx.Panel.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 integer `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 bool `forward`: whether to advance forward or backward;
|
|
:param bool `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, name=""):
|
|
"""
|
|
Adds a button in the tab area.
|
|
|
|
:param integer `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 integer `location`: the button location. Can be ``wx.LEFT`` or ``wx.RIGHT``;
|
|
:param Bitmap `normal_bitmap`: the bitmap for an enabled tab;
|
|
:param Bitmap `disabled_bitmap`: the bitmap for a disabled tab;
|
|
:param string `name`: the button name.
|
|
"""
|
|
|
|
active_tabctrl = self.GetActiveTabCtrl()
|
|
active_tabctrl.AddButton(id, location, normal_bitmap, disabled_bitmap, name=name)
|
|
|
|
|
|
def RemoveTabAreaButton(self, id):
|
|
"""
|
|
Removes a button from the tab area.
|
|
|
|
:param integer `id`: the button identifier.
|
|
|
|
:see: :meth:`AddTabAreaButton` for a list of button identifiers.
|
|
"""
|
|
|
|
active_tabctrl = self.GetActiveTabCtrl()
|
|
active_tabctrl.RemoveButton(id)
|
|
|
|
|
|
def CloneTabAreaButtons(self):
|
|
"""
|
|
Clones the tab area buttons when the :class:`AuiNotebook` is being split.
|
|
|
|
:see: :meth:`AddTabAreaButton`
|
|
|
|
:note: Standard buttons for :class:`AuiNotebook` are not cloned, only custom ones.
|
|
"""
|
|
|
|
active_tabctrl = self.GetActiveTabCtrl()
|
|
clones = active_tabctrl.CloneButtons()
|
|
|
|
return clones
|
|
|
|
|
|
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 :class:`Notebook`, :class:`Listbook` and :class:`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 :class:`Panel`.
|
|
"""
|
|
|
|
return True
|
|
|
|
|
|
def GetDefaultBorder(self):
|
|
""" Returns the default border style for :class:`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 integer `thumbnail_size`: the maximum size of every page thumbnail
|
|
(default=200 pixels).
|
|
|
|
: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 = [], []
|
|
count = 0
|
|
|
|
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 count == 0:
|
|
il = wx.ImageList(bmp.GetWidth(), bmp.GetHeight(), True)
|
|
|
|
il.Add(bmp)
|
|
count += 1
|
|
|
|
# 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 integer `page_idx`: the page index;
|
|
:param bool `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 integer `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 :class:`TabTextCtrl`, to cancel the changes and to send the
|
|
``EVT_AUINOTEBOOK_END_LABEL_EDIT`` event.
|
|
|
|
:param integer `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 :class:`TabTextCtrl`, to accept the changes and to send the
|
|
``EVT_AUINOTEBOOK_END_LABEL_EDIT`` event.
|
|
|
|
:param integer `page_index`: the page index in the notebook;
|
|
:param string `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 :class:`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 integer `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
|