...
[iramuteq] / aui / auibook.py
1 """
2 auibook contains a notebook control which implements many features common in
3 applications with dockable panes. Specifically, L{AuiNotebook} implements functionality
4 which allows the user to rearrange tab order via drag-and-drop, split the tab window
5 into many different splitter configurations, and toggle through different themes to
6 customize the control's look and feel.
7
8 An effort has been made to try to maintain an API as similar to that of `wx.Notebook`.
9
10 The default theme that is used is L{AuiDefaultTabArt}, which provides a modern, glossy
11 look and feel. The theme can be changed by calling L{AuiNotebook.SetArtProvider}.
12 """
13
14 __author__ = "Andrea Gavana <andrea.gavana@gmail.com>"
15 __date__ = "31 March 2009"
16
17
18 import wx
19 import types
20 import datetime
21
22 from wx.lib.expando import ExpandoTextCtrl
23
24 import framemanager
25 import tabart as TA
26
27 from aui_utilities import LightColour, MakeDisabledBitmap, TabDragImage
28 from aui_utilities import TakeScreenShot, RescaleScreenShot
29
30 from aui_constants import *
31
32 # AuiNotebook events
33 wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE = wx.NewEventType()
34 wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSED = wx.NewEventType()
35 wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGED = wx.NewEventType()
36 wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING = wx.NewEventType()
37 wxEVT_COMMAND_AUINOTEBOOK_BUTTON = wx.NewEventType()
38 wxEVT_COMMAND_AUINOTEBOOK_BEGIN_DRAG = wx.NewEventType()
39 wxEVT_COMMAND_AUINOTEBOOK_END_DRAG = wx.NewEventType()
40 wxEVT_COMMAND_AUINOTEBOOK_DRAG_MOTION = wx.NewEventType()
41 wxEVT_COMMAND_AUINOTEBOOK_ALLOW_DND = wx.NewEventType()
42 wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE = wx.NewEventType()
43 wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_DOWN = wx.NewEventType()
44 wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_UP = wx.NewEventType()
45 wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_DOWN = wx.NewEventType()
46 wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP = wx.NewEventType()
47 wxEVT_COMMAND_AUINOTEBOOK_TAB_DCLICK = wx.NewEventType()
48 wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_DOWN = wx.NewEventType()
49 wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_UP = wx.NewEventType()
50 wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_DOWN = wx.NewEventType()
51 wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_UP = wx.NewEventType()
52 wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK = wx.NewEventType()
53
54 # Define a new event for a drag cancelled
55 wxEVT_COMMAND_AUINOTEBOOK_CANCEL_DRAG = wx.NewEventType()
56
57 # Define events for editing a tab label
58 wxEVT_COMMAND_AUINOTEBOOK_BEGIN_LABEL_EDIT = wx.NewEventType()
59 wxEVT_COMMAND_AUINOTEBOOK_END_LABEL_EDIT = wx.NewEventType()
60
61 # Create event binders
62 EVT_AUINOTEBOOK_PAGE_CLOSE = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE, 1)
63 """ A tab in `AuiNotebook` is being closed. Can be vetoed by calling `Veto()`. """
64 EVT_AUINOTEBOOK_PAGE_CLOSED = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSED, 1)
65 """ A tab in `AuiNotebook` has been closed. """
66 EVT_AUINOTEBOOK_PAGE_CHANGED = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGED, 1)
67 """ The page selection was changed. """
68 EVT_AUINOTEBOOK_PAGE_CHANGING = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, 1)
69 """ The page selection is being changed. """
70 EVT_AUINOTEBOOK_BUTTON = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BUTTON, 1)
71 """ The user clicked on a button in the `AuiNotebook` tab area. """
72 EVT_AUINOTEBOOK_BEGIN_DRAG = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BEGIN_DRAG, 1)
73 """ A drag-and-drop operation on a notebook tab has started. """
74 EVT_AUINOTEBOOK_END_DRAG = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_END_DRAG, 1)
75 """ A drag-and-drop operation on a notebook tab has finished. """
76 EVT_AUINOTEBOOK_DRAG_MOTION = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_DRAG_MOTION, 1)
77 """ A drag-and-drop operation on a notebook tab is ongoing. """
78 EVT_AUINOTEBOOK_ALLOW_DND = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_ALLOW_DND, 1)
79 """ Fires an event asking if it is OK to drag and drop a tab. """
80 EVT_AUINOTEBOOK_DRAG_DONE = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, 1)
81 """ A drag-and-drop operation on a notebook tab has finished. """
82 EVT_AUINOTEBOOK_TAB_MIDDLE_DOWN = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_DOWN, 1)
83 """ The user clicked with the middle mouse button on a tab. """
84 EVT_AUINOTEBOOK_TAB_MIDDLE_UP = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_UP, 1)
85 """ The user clicked with the middle mouse button on a tab. """
86 EVT_AUINOTEBOOK_TAB_RIGHT_DOWN = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_DOWN, 1)
87 """ The user clicked with the right mouse button on a tab. """
88 EVT_AUINOTEBOOK_TAB_RIGHT_UP = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP, 1)
89 """ The user clicked with the right mouse button on a tab. """
90 EVT_AUINOTEBOOK_BG_MIDDLE_DOWN = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_DOWN, 1)
91 """ The user middle-clicked in the tab area but not over a tab or a button. """
92 EVT_AUINOTEBOOK_BG_MIDDLE_UP = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_UP, 1)
93 """ The user middle-clicked in the tab area but not over a tab or a button. """
94 EVT_AUINOTEBOOK_BG_RIGHT_DOWN = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_DOWN, 1)
95 """ The user right-clicked in the tab area but not over a tab or a button. """
96 EVT_AUINOTEBOOK_BG_RIGHT_UP = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_UP, 1)
97 """ The user right-clicked in the tab area but not over a tab or a button. """
98 EVT_AUINOTEBOOK_BG_DCLICK = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK, 1)
99 """ The user left-clicked on the tab area not occupied by `AuiNotebook` tabs. """
100 EVT_AUINOTEBOOK_CANCEL_DRAG = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_CANCEL_DRAG, 1)
101 """ A drag and drop operation has been cancelled. """
102 EVT_AUINOTEBOOK_TAB_DCLICK = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_DCLICK, 1)
103 """ The user double-clicked with the left mouse button on a tab. """
104 EVT_AUINOTEBOOK_BEGIN_LABEL_EDIT = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BEGIN_LABEL_EDIT, 1)
105 """ The user double-clicked with the left mouse button on a tab which text is editable. """
106 EVT_AUINOTEBOOK_END_LABEL_EDIT = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_END_LABEL_EDIT, 1)
107 """ The user finished editing a tab label. """
108
109
110 # -----------------------------------------------------------------------------
111 # Auxiliary class: TabTextCtrl
112 # This is the temporary ExpandoTextCtrl created when you edit the text of a tab
113 # -----------------------------------------------------------------------------
114
115 class TabTextCtrl(ExpandoTextCtrl):
116     """ Control used for in-place edit. """
117
118     def __init__(self, owner, tab, page_index):
119         """
120         Default class constructor.
121         For internal use: do not call it in your code!
122
123         :param `owner`: the L{AuiTabCtrl} owning the tab;
124         :param `tab`: the actual L{AuiNotebookPage} tab;
125         :param `page_index`: the L{AuiNotebook} page index for the tab.
126         """
127
128         self._owner = owner
129         self._tabEdited = tab
130         self._pageIndex = page_index
131         self._startValue = tab.caption
132         self._finished = False
133         self._aboutToFinish = False
134         self._currentValue = self._startValue
135
136         x, y, w, h = self._tabEdited.rect
137
138         wnd = self._tabEdited.control
139         if wnd:
140             x += wnd.GetSize()[0] + 2
141             h = 0
142
143         image_h = 0
144         image_w = 0
145
146         image = tab.bitmap
147
148         if image.IsOk():
149             image_w, image_h = image.GetWidth(), image.GetHeight()
150             image_w += 6
151
152         dc = wx.ClientDC(self._owner)
153         h = max(image_h, dc.GetMultiLineTextExtent(tab.caption)[1])
154         h = h + 2
155
156         # FIXME: what are all these hardcoded 4, 8 and 11s really?
157         x += image_w
158         w -= image_w + 4
159
160         y = (self._tabEdited.rect.height - h)/2 + 1
161
162         expandoStyle = wx.WANTS_CHARS
163         if wx.Platform in ["__WXGTK__", "__WXMAC__"]:
164             expandoStyle |= wx.SIMPLE_BORDER
165             xSize, ySize = w + 2, h
166         else:
167             expandoStyle |= wx.SUNKEN_BORDER
168             xSize, ySize = w + 2, h+2
169
170         ExpandoTextCtrl.__init__(self, self._owner, wx.ID_ANY, self._startValue,
171                                  wx.Point(x, y), wx.Size(xSize, ySize),
172                                  expandoStyle)
173
174         if wx.Platform == "__WXMAC__":
175             self.SetFont(owner.GetFont())
176             bs = self.GetBestSize()
177             self.SetSize((-1, bs.height))
178
179         self.Bind(wx.EVT_CHAR, self.OnChar)
180         self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
181         self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
182
183
184     def AcceptChanges(self):
185         """ Accepts/refuses the changes made by the user. """
186
187         value = self.GetValue()
188         notebook = self._owner.GetParent()
189
190         if value == self._startValue:
191             # nothing changed, always accept
192             # when an item remains unchanged, the owner
193             # needs to be notified that the user decided
194             # not to change the tree item label, and that
195             # the edit has been cancelled
196             notebook.OnRenameCancelled(self._pageIndex)
197             return True
198
199         if not notebook.OnRenameAccept(self._pageIndex, value):
200             # vetoed by the user
201             return False
202
203         # accepted, do rename the item
204         notebook.SetPageText(self._pageIndex, value)
205
206         return True
207
208
209     def Finish(self):
210         """ Finish editing. """
211
212         if not self._finished:
213
214             notebook = self._owner.GetParent()
215
216             self._finished = True
217             self._owner.SetFocus()
218             notebook.ResetTextControl()
219
220
221     def OnChar(self, event):
222         """
223         Handles the ``wx.EVT_CHAR`` event for L{TabTextCtrl}.
224
225         :param `event`: a `wx.KeyEvent` event to be processed.
226         """
227
228         keycode = event.GetKeyCode()
229         shiftDown = event.ShiftDown()
230
231         if keycode == wx.WXK_RETURN:
232             if shiftDown and self._tabEdited.IsMultiline():
233                 event.Skip()
234             else:
235                 self._aboutToFinish = True
236                 self.SetValue(self._currentValue)
237                 # Notify the owner about the changes
238                 self.AcceptChanges()
239                 # Even if vetoed, close the control (consistent with MSW)
240                 wx.CallAfter(self.Finish)
241
242         elif keycode == wx.WXK_ESCAPE:
243             self.StopEditing()
244
245         else:
246             event.Skip()
247
248
249     def OnKeyUp(self, event):
250         """
251         Handles the ``wx.EVT_KEY_UP`` event for L{TabTextCtrl}.
252
253         :param `event`: a `wx.KeyEvent` event to be processed.
254         """
255
256         if not self._finished:
257
258             # auto-grow the textctrl:
259             mySize = self.GetSize()
260
261             dc = wx.ClientDC(self)
262             sx, sy, dummy = dc.GetMultiLineTextExtent(self.GetValue() + "M")
263
264             self.SetSize((sx, -1))
265             self._currentValue = self.GetValue()
266
267         event.Skip()
268
269
270     def OnKillFocus(self, event):
271         """
272         Handles the ``wx.EVT_KILL_FOCUS`` event for L{TabTextCtrl}.
273
274         :param `event`: a `wx.FocusEvent` event to be processed.
275         """
276
277         if not self._finished and not self._aboutToFinish:
278
279             # We must finish regardless of success, otherwise we'll get
280             # focus problems:
281             if not self.AcceptChanges():
282                 self._owner.GetParent().OnRenameCancelled(self._pageIndex)
283
284         # We must let the native text control handle focus, too, otherwise
285         # it could have problems with the cursor (e.g., in wxGTK).
286         event.Skip()
287         wx.CallAfter(self._owner.GetParent().ResetTextControl)
288
289
290     def StopEditing(self):
291         """ Suddenly stops the editing. """
292
293         self._owner.GetParent().OnRenameCancelled(self._pageIndex)
294         self.Finish()
295
296
297     def item(self):
298         """ Returns the item currently edited. """
299
300         return self._tabEdited
301
302
303 # ----------------------------------------------------------------------
304
305 class AuiNotebookPage(object):
306     """
307     A simple class which holds information about tab captions, bitmaps and
308     colours.
309     """
310
311     def __init__(self):
312         """
313         Default class constructor.
314         Used internally, do not call it in your code!
315         """
316
317         self.window = None              # page's associated window
318         self.caption = ""               # caption displayed on the tab
319         self.bitmap = wx.NullBitmap     # tab's bitmap
320         self.dis_bitmap = wx.NullBitmap # tab's disabled bitmap
321         self.rect = wx.Rect()           # tab's hit rectangle
322         self.active = False             # True if the page is currently active
323         self.enabled = True             # True if the page is currently enabled
324         self.hasCloseButton = True      # True if the page has a close button using the style
325                                         # AUI_NB_CLOSE_ON_ALL_TABS
326         self.control = None             # A control can now be inside a tab
327         self.renamable = False          # If True, a tab can be renamed by a left double-click
328
329         self.text_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNTEXT)
330
331         self.access_time = datetime.datetime.now() # Last time this page was selected
332
333
334     def IsMultiline(self):
335         """ Returns whether the tab contains multiline text. """
336
337         return "\n" in self.caption
338
339
340 # ----------------------------------------------------------------------
341
342 class AuiTabContainerButton(object):
343     """
344     A simple class which holds information about tab buttons and their state.
345     """
346
347     def __init__(self):
348         """
349         Default class constructor.
350         Used internally, do not call it in your code!
351         """
352
353         self.id = -1                                      # button's id
354         self.cur_state = AUI_BUTTON_STATE_NORMAL          # current state (normal, hover, pressed, etc.)
355         self.location = wx.LEFT                           # buttons location (wxLEFT, wxRIGHT, or wxCENTER)
356         self.bitmap = wx.NullBitmap                       # button's hover bitmap
357         self.dis_bitmap = wx.NullBitmap                   # button's disabled bitmap
358         self.rect = wx.Rect()                             # button's hit rectangle
359
360
361 # ----------------------------------------------------------------------
362
363 class CommandNotebookEvent(wx.PyCommandEvent):
364     """ A specialized command event class for events sent by L{AuiNotebook} . """
365
366     def __init__(self, command_type=None, win_id=0):
367         """
368         Default class constructor.
369
370         :param `command_type`: the event kind or an instance of `wx.PyCommandEvent`.
371         :param `win_id`: the window identification number.
372         """
373
374         if type(command_type) == types.IntType:
375             wx.PyCommandEvent.__init__(self, command_type, win_id)
376         else:
377             wx.PyCommandEvent.__init__(self, command_type.GetEventType(), command_type.GetId())
378
379         self.old_selection = -1
380         self.selection = -1
381         self.drag_source = None
382         self.dispatched = 0
383         self.label = ""
384         self.editCancelled = False
385
386
387     def SetSelection(self, s):
388         """
389         Sets the selection member variable.
390
391         :param `s`: the new selection.
392         """
393
394         self.selection = s
395         self._commandInt = s
396
397
398     def GetSelection(self):
399         """ Returns the currently selected page, or -1 if none was selected. """
400
401         return self.selection
402
403
404     def SetOldSelection(self, s):
405         """
406         Sets the id of the page selected before the change.
407
408         :param `s`: the old selection.
409         """
410
411         self.old_selection = s
412
413
414     def GetOldSelection(self):
415         """
416         Returns the page that was selected before the change, or -1 if none was
417         selected.
418         """
419
420         return self.old_selection
421
422
423     def SetDragSource(self, s):
424         """
425         Sets the drag and drop source.
426
427         :param `s`: the drag source.
428         """
429
430         self.drag_source = s
431
432
433     def GetDragSource(self):
434         """ Returns the drag and drop source. """
435
436         return self.drag_source
437
438
439     def SetDispatched(self, b):
440         """
441         Sets the event as dispatched (used for automatic L{AuiNotebook} ).
442
443         :param `b`: whether the event was dispatched or not.
444         """
445
446         self.dispatched = b
447
448
449     def GetDispatched(self):
450         """ Returns whether the event was dispatched (used for automatic L{AuiNotebook} ). """
451
452         return self.dispatched
453
454
455     def IsEditCancelled(self):
456         """ Returns the edit cancel flag (for ``EVT_AUINOTEBOOK_BEGIN`` | ``END_LABEL_EDIT`` only)."""
457
458         return self.editCancelled
459
460
461     def SetEditCanceled(self, editCancelled):
462         """
463         Sets the edit cancel flag (for ``EVT_AUINOTEBOOK_BEGIN`` | ``END_LABEL_EDIT`` only).
464
465         :param `editCancelled`: whether the editing action has been cancelled or not.
466         """
467
468         self.editCancelled = editCancelled
469
470
471     def GetLabel(self):
472         """Returns the label-itemtext (for ``EVT_AUINOTEBOOK_BEGIN`` | ``END_LABEL_EDIT`` only)."""
473
474         return self.label
475
476
477     def SetLabel(self, label):
478         """
479         Sets the label. Useful only for ``EVT_AUINOTEBOOK_END_LABEL_EDIT``.
480
481         :param `label`: the new label.
482         """
483
484         self.label = label
485
486
487 # ----------------------------------------------------------------------
488
489 class AuiNotebookEvent(CommandNotebookEvent):
490     """ A specialized command event class for events sent by L{AuiNotebook}. """
491
492     def __init__(self, command_type=None, win_id=0):
493         """
494         Default class constructor.
495
496         :param `command_type`: the event kind or an instance of `wx.PyCommandEvent`.
497         :param `win_id`: the window identification number.
498         """
499
500         CommandNotebookEvent.__init__(self, command_type, win_id)
501
502         if type(command_type) == types.IntType:
503             self.notify = wx.NotifyEvent(command_type, win_id)
504         else:
505             self.notify = wx.NotifyEvent(command_type.GetEventType(), command_type.GetId())
506
507
508     def GetNotifyEvent(self):
509         """ Returns the actual `wx.NotifyEvent`. """
510
511         return self.notify
512
513
514     def IsAllowed(self):
515         """ Returns whether the event is allowed or not. """
516
517         return self.notify.IsAllowed()
518
519
520     def Veto(self):
521         """
522         Prevents the change announced by this event from happening.
523
524         It is in general a good idea to notify the user about the reasons for
525         vetoing the change because otherwise the applications behaviour (which
526         just refuses to do what the user wants) might be quite surprising.
527         """
528
529         self.notify.Veto()
530
531
532     def Allow(self):
533         """
534         This is the opposite of L{Veto}: it explicitly allows the event to be
535         processed. For most events it is not necessary to call this method as the
536         events are allowed anyhow but some are forbidden by default (this will
537         be mentioned in the corresponding event description).
538         """
539
540         self.notify.Allow()
541
542
543 # ---------------------------------------------------------------------------- #
544 # Class TabNavigatorWindow
545 # ---------------------------------------------------------------------------- #
546
547 class TabNavigatorWindow(wx.Dialog):
548     """
549     This class is used to create a modal dialog that enables "Smart Tabbing",
550     similar to what you would get by hitting ``Alt`` + ``Tab`` on Windows.
551     """
552
553     def __init__(self, parent=None, icon=None):
554         """
555         Default class constructor. Used internally.
556
557         :param `parent`: the L{TabNavigatorWindow} parent;
558         :param `icon`: the L{TabNavigatorWindow} icon.
559         """
560
561         wx.Dialog.__init__(self, parent, wx.ID_ANY, "", style=0)
562
563         self._selectedItem = -1
564         self._indexMap = []
565
566         if icon is None:
567             self._bmp = Mondrian.GetBitmap()
568         else:
569             self._bmp = icon
570
571         if self._bmp.GetSize() != (16, 16):
572             img = self._bmp.ConvertToImage()
573             img.Rescale(16, 16, wx.IMAGE_QUALITY_HIGH)
574             self._bmp = wx.BitmapFromImage(img)
575
576         sz = wx.BoxSizer(wx.VERTICAL)
577
578         self._listBox = wx.ListBox(self, wx.ID_ANY, wx.DefaultPosition, wx.Size(200, 150), [], wx.LB_SINGLE | wx.NO_BORDER)
579
580         mem_dc = wx.MemoryDC()
581         mem_dc.SelectObject(wx.EmptyBitmap(1,1))
582         font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
583         font.SetWeight(wx.BOLD)
584         mem_dc.SetFont(font)
585
586         panelHeight = mem_dc.GetCharHeight()
587         panelHeight += 4 # Place a spacer of 2 pixels
588
589         # Out signpost bitmap is 24 pixels
590         if panelHeight < 24:
591             panelHeight = 24
592
593         self._panel = wx.Panel(self, wx.ID_ANY, wx.DefaultPosition, wx.Size(200, panelHeight))
594
595         sz.Add(self._panel)
596         sz.Add(self._listBox, 1, wx.EXPAND)
597
598         self.SetSizer(sz)
599
600         # Connect events to the list box
601         self._listBox.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
602         self._listBox.Bind(wx.EVT_NAVIGATION_KEY, self.OnNavigationKey)
603         self._listBox.Bind(wx.EVT_LISTBOX_DCLICK, self.OnItemSelected)
604
605         # Connect paint event to the panel
606         self._panel.Bind(wx.EVT_PAINT, self.OnPanelPaint)
607         self._panel.Bind(wx.EVT_ERASE_BACKGROUND, self.OnPanelEraseBg)
608
609         self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE))
610         self._listBox.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE))
611         self.PopulateListControl(parent)
612
613         self.GetSizer().Fit(self)
614         self.GetSizer().SetSizeHints(self)
615         self.GetSizer().Layout()
616         self.Centre()
617
618         # Set focus on the list box to avoid having to click on it to change
619         # the tab selection under GTK.
620         self._listBox.SetFocus()
621
622
623     def OnKeyUp(self, event):
624         """
625         Handles the ``wx.EVT_KEY_UP`` for the L{TabNavigatorWindow}.
626
627         :param `event`: a `wx.KeyEvent` event to be processed.
628         """
629
630         if event.GetKeyCode() == wx.WXK_CONTROL:
631             self.CloseDialog()
632
633
634     def OnNavigationKey(self, event):
635         """
636         Handles the ``wx.EVT_NAVIGATION_KEY`` for the L{TabNavigatorWindow}.
637
638         :param `event`: a `wx.NavigationKeyEvent` event to be processed.
639         """
640
641         selected = self._listBox.GetSelection()
642         bk = self.GetParent()
643         maxItems = bk.GetPageCount()
644
645         if event.GetDirection():
646
647             # Select next page
648             if selected == maxItems - 1:
649                 itemToSelect = 0
650             else:
651                 itemToSelect = selected + 1
652
653         else:
654
655             # Previous page
656             if selected == 0:
657                 itemToSelect = maxItems - 1
658             else:
659                 itemToSelect = selected - 1
660
661         self._listBox.SetSelection(itemToSelect)
662
663
664     def PopulateListControl(self, book):
665         """
666         Populates the L{TabNavigatorWindow} listbox with a list of tabs.
667
668         :param `book`: the actual L{AuiNotebook}.
669         """
670         # Index of currently selected page
671         selection = book.GetSelection()
672         # Total number of pages
673         count = book.GetPageCount()
674         # List of (index, AuiNotebookPage)
675         pages = list(enumerate(book.GetTabContainer().GetPages()))
676         if book.GetAGWWindowStyleFlag() & AUI_NB_ORDER_BY_ACCESS:
677             # Sort pages using last access time. Most recently used is the
678             # first in line
679             pages.sort(
680                 key = lambda element: element[1].access_time,
681                 reverse = True
682             )
683         else:
684             # Manually add the current selection as first item
685             # Remaining ones are added in the next loop
686             del pages[selection]
687             self._listBox.Append(book.GetPageText(selection))
688             self._indexMap.append(selection)
689
690         for (index, page) in pages:
691             self._listBox.Append(book.GetPageText(index))
692             self._indexMap.append(index)
693
694         # Select the next entry after the current selection
695         self._listBox.SetSelection(0)
696         dummy = wx.NavigationKeyEvent()
697         dummy.SetDirection(True)
698         self.OnNavigationKey(dummy)
699
700
701     def OnItemSelected(self, event):
702         """
703         Handles the ``wx.EVT_LISTBOX_DCLICK`` event for the wx.ListBox inside L{TabNavigatorWindow}.
704
705         :param `event`: a `wx.ListEvent` event to be processed.
706         """
707
708         self.CloseDialog()
709
710
711     def CloseDialog(self):
712         """ Closes the L{TabNavigatorWindow} dialog, setting selection in L{AuiNotebook}. """
713
714         bk = self.GetParent()
715         self._selectedItem = self._listBox.GetSelection()
716         self.EndModal(wx.ID_OK)
717
718
719     def GetSelectedPage(self):
720         """ Gets the page index that was selected when the dialog was closed. """
721
722         return self._indexMap[self._selectedItem]
723
724
725     def OnPanelPaint(self, event):
726         """
727         Handles the ``wx.EVT_PAINT`` event for L{TabNavigatorWindow} top panel.
728
729         :param `event`: a `wx.PaintEvent` event to be processed.
730         """
731
732         dc = wx.PaintDC(self._panel)
733         rect = self._panel.GetClientRect()
734
735         bmp = wx.EmptyBitmap(rect.width, rect.height)
736
737         mem_dc = wx.MemoryDC()
738         mem_dc.SelectObject(bmp)
739
740         endColour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)
741         startColour = LightColour(endColour, 50)
742         mem_dc.GradientFillLinear(rect, startColour, endColour, wx.SOUTH)
743
744         # Draw the caption title and place the bitmap
745         # get the bitmap optimal position, and draw it
746         bmpPt, txtPt = wx.Point(), wx.Point()
747         bmpPt.y = (rect.height - self._bmp.GetHeight())/2
748         bmpPt.x = 3
749         mem_dc.DrawBitmap(self._bmp, bmpPt.x, bmpPt.y, True)
750
751         # get the text position, and draw it
752         font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
753         font.SetWeight(wx.BOLD)
754         mem_dc.SetFont(font)
755         fontHeight = mem_dc.GetCharHeight()
756
757         txtPt.x = bmpPt.x + self._bmp.GetWidth() + 4
758         txtPt.y = (rect.height - fontHeight)/2
759         mem_dc.SetTextForeground(wx.WHITE)
760         mem_dc.DrawText("Opened tabs:", txtPt.x, txtPt.y)
761         mem_dc.SelectObject(wx.NullBitmap)
762
763         dc.DrawBitmap(bmp, 0, 0)
764
765
766     def OnPanelEraseBg(self, event):
767         """
768         Handles the ``wx.EVT_ERASE_BACKGROUND`` event for L{TabNavigatorWindow} top panel.
769
770         :param `event`: a `wx.EraseEvent` event to be processed.
771
772         :note: This is intentionally empty, to reduce flicker.
773         """
774
775         pass
776
777
778 # ----------------------------------------------------------------------
779 # -- AuiTabContainer class implementation --
780
781 class AuiTabContainer(object):
782     """
783     AuiTabContainer is a class which contains information about each
784     tab. It also can render an entire tab control to a specified DC.
785     It's not a window class itself, because this code will be used by
786     the L{AuiManager}, where it is disadvantageous to have separate
787     windows for each tab control in the case of "docked tabs".
788
789     A derived class, L{AuiTabCtrl}, is an actual `wx.Window`-derived window
790     which can be used as a tab control in the normal sense.
791     """
792
793     def __init__(self, auiNotebook):
794         """
795         Default class constructor.
796         Used internally, do not call it in your code!
797
798         :param `auiNotebook`: the parent L{AuiNotebook} window.        
799         """
800
801         self._tab_offset = 0
802         self._agwFlags = 0
803         self._art = TA.AuiDefaultTabArt()
804
805         self._buttons = []
806         self._pages = []
807         self._tab_close_buttons = []
808
809         self._rect = wx.Rect()
810         self._auiNotebook = auiNotebook
811
812         self.AddButton(AUI_BUTTON_LEFT, wx.LEFT)
813         self.AddButton(AUI_BUTTON_RIGHT, wx.RIGHT)
814         self.AddButton(AUI_BUTTON_WINDOWLIST, wx.RIGHT)
815         self.AddButton(AUI_BUTTON_CLOSE, wx.RIGHT)
816
817
818     def SetArtProvider(self, art):
819         """
820         Instructs L{AuiTabContainer} to use art provider specified by parameter `art`
821         for all drawing calls. This allows plugable look-and-feel features.
822
823         :param `art`: an art provider.
824
825         :note: The previous art provider object, if any, will be deleted by L{AuiTabContainer}.
826         """
827
828         del self._art
829         self._art = art
830
831         if self._art:
832             self._art.SetAGWFlags(self._agwFlags)
833
834
835     def GetArtProvider(self):
836         """ Returns the current art provider being used. """
837
838         return self._art
839
840
841     def SetAGWFlags(self, agwFlags):
842         """
843         Sets the tab art flags.
844
845         :param `agwFlags`: a combination of the following values:
846
847          ==================================== ==================================
848          Flag name                            Description
849          ==================================== ==================================
850          ``AUI_NB_TOP``                       With this style, tabs are drawn along the top of the notebook
851          ``AUI_NB_LEFT``                      With this style, tabs are drawn along the left of the notebook. Not implemented yet
852          ``AUI_NB_RIGHT``                     With this style, tabs are drawn along the right of the notebook. Not implemented yet
853          ``AUI_NB_BOTTOM``                    With this style, tabs are drawn along the bottom of the notebook
854          ``AUI_NB_TAB_SPLIT``                 Allows the tab control to be split by dragging a tab
855          ``AUI_NB_TAB_MOVE``                  Allows a tab to be moved horizontally by dragging
856          ``AUI_NB_TAB_EXTERNAL_MOVE``         Allows a tab to be moved to another tab control
857          ``AUI_NB_TAB_FIXED_WIDTH``           With this style, all tabs have the same width
858          ``AUI_NB_SCROLL_BUTTONS``            With this style, left and right scroll buttons are displayed
859          ``AUI_NB_WINDOWLIST_BUTTON``         With this style, a drop-down list of windows is available
860          ``AUI_NB_CLOSE_BUTTON``              With this style, a close button is available on the tab bar
861          ``AUI_NB_CLOSE_ON_ACTIVE_TAB``       With this style, a close button is available on the active tab
862          ``AUI_NB_CLOSE_ON_ALL_TABS``         With this style, a close button is available on all tabs
863          ``AUI_NB_MIDDLE_CLICK_CLOSE``        Allows to close L{AuiNotebook} tabs by mouse middle button click
864          ``AUI_NB_SUB_NOTEBOOK``              This style is used by L{AuiManager} to create automatic AuiNotebooks
865          ``AUI_NB_HIDE_ON_SINGLE_TAB``        Hides the tab window if only one tab is present
866          ``AUI_NB_SMART_TABS``                Use Smart Tabbing, like ``Alt`` + ``Tab`` on Windows
867          ``AUI_NB_USE_IMAGES_DROPDOWN``       Uses images on dropdown window list menu instead of check items
868          ``AUI_NB_CLOSE_ON_TAB_LEFT``         Draws the tab close button on the left instead of on the right (a la Camino browser)
869          ``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
870          ``AUI_NB_DRAW_DND_TAB``              Draws an image representation of a tab while dragging (on by default)
871          ``AUI_NB_ORDER_BY_ACCESS``           Tab navigation order by last access time for the tabs
872          ``AUI_NB_NO_TAB_FOCUS``              Don't draw tab focus rectangle
873          ==================================== ==================================
874
875         :todo: Implementation of flags ``AUI_NB_RIGHT`` and ``AUI_NB_LEFT``.
876
877         """
878
879         self._agwFlags = agwFlags
880
881         # check for new close button settings
882         self.RemoveButton(AUI_BUTTON_LEFT)
883         self.RemoveButton(AUI_BUTTON_RIGHT)
884         self.RemoveButton(AUI_BUTTON_WINDOWLIST)
885         self.RemoveButton(AUI_BUTTON_CLOSE)
886
887         if agwFlags & AUI_NB_SCROLL_BUTTONS:
888             self.AddButton(AUI_BUTTON_LEFT, wx.LEFT)
889             self.AddButton(AUI_BUTTON_RIGHT, wx.RIGHT)
890
891         if agwFlags & AUI_NB_WINDOWLIST_BUTTON:
892             self.AddButton(AUI_BUTTON_WINDOWLIST, wx.RIGHT)
893
894         if agwFlags & AUI_NB_CLOSE_BUTTON:
895             self.AddButton(AUI_BUTTON_CLOSE, wx.RIGHT)
896
897         if self._art:
898             self._art.SetAGWFlags(self._agwFlags)
899
900
901     def GetAGWFlags(self):
902         """
903         Returns the tab art flags.
904
905         See L{SetAGWFlags} for a list of possible return values.
906
907         :see: L{SetAGWFlags}
908         """
909
910         return self._agwFlags
911
912
913     def SetNormalFont(self, font):
914         """
915         Sets the normal font for drawing tab labels.
916
917         :param `font`: a `wx.Font` object.
918         """
919
920         self._art.SetNormalFont(font)
921
922
923     def SetSelectedFont(self, font):
924         """
925         Sets the selected tab font for drawing tab labels.
926
927         :param `font`: a `wx.Font` object.
928         """
929
930         self._art.SetSelectedFont(font)
931
932
933     def SetMeasuringFont(self, font):
934         """
935         Sets the font for calculating text measurements.
936
937         :param `font`: a `wx.Font` object.
938         """
939
940         self._art.SetMeasuringFont(font)
941
942
943     def SetTabRect(self, rect):
944         """
945         Sets the tab area rectangle.
946
947         :param `rect`: an instance of `wx.Rect`, specifying the available area for L{AuiTabContainer}.
948         """
949
950         self._rect = rect
951
952         if self._art:
953             minMaxTabWidth = self._auiNotebook.GetMinMaxTabWidth()
954             self._art.SetSizingInfo(rect.GetSize(), len(self._pages), minMaxTabWidth)
955
956
957     def AddPage(self, page, info):
958         """
959         Adds a page to the tab control.
960
961         :param `page`: the window associated with this tab;
962         :param `info`: an instance of L{AuiNotebookPage}.
963         """
964
965         page_info = info
966         page_info.window = page
967
968         self._pages.append(page_info)
969
970         # let the art provider know how many pages we have
971         if self._art:
972             minMaxTabWidth = self._auiNotebook.GetMinMaxTabWidth()
973             self._art.SetSizingInfo(self._rect.GetSize(), len(self._pages), minMaxTabWidth)
974
975         return True
976
977
978     def InsertPage(self, page, info, idx):
979         """
980         Inserts a page in the tab control in the position specified by `idx`.
981
982         :param `page`: the window associated with this tab;
983         :param `info`: an instance of L{AuiNotebookPage};
984         :param `idx`: the page insertion index.
985         """
986
987         page_info = info
988         page_info.window = page
989
990         if idx >= len(self._pages):
991             self._pages.append(page_info)
992         else:
993             self._pages.insert(idx, page_info)
994
995         # let the art provider know how many pages we have
996         if self._art:
997             minMaxTabWidth = self._auiNotebook.GetMinMaxTabWidth()
998             self._art.SetSizingInfo(self._rect.GetSize(), len(self._pages), minMaxTabWidth)
999
1000         return True
1001
1002
1003     def MovePage(self, page, new_idx):
1004         """
1005         Moves a page in a new position specified by `new_idx`.
1006
1007         :param `page`: the window associated with this tab;
1008         :param `new_idx`: the new page position.
1009         """
1010
1011         idx = self.GetIdxFromWindow(page)
1012         if idx == -1:
1013             return False
1014
1015         # get page entry, make a copy of it
1016         p = self.GetPage(idx)
1017
1018         # remove old page entry
1019         self.RemovePage(page)
1020
1021         # insert page where it should be
1022         self.InsertPage(page, p, new_idx)
1023
1024         return True
1025
1026
1027     def RemovePage(self, wnd):
1028         """
1029         Removes a page from the tab control.
1030
1031         :param `wnd`: an instance of `wx.Window`, a window associated with this tab.
1032         """
1033
1034         minMaxTabWidth = self._auiNotebook.GetMinMaxTabWidth()
1035
1036         for page in self._pages:
1037             if page.window == wnd:
1038                 self._pages.remove(page)
1039                 self._tab_offset = min(self._tab_offset, len(self._pages) - 1)
1040
1041                 # let the art provider know how many pages we have
1042                 if self._art:
1043                     self._art.SetSizingInfo(self._rect.GetSize(), len(self._pages), minMaxTabWidth)
1044
1045                 return True
1046
1047         return False
1048
1049
1050     def SetActivePage(self, wndOrInt):
1051         """
1052         Sets the L{AuiTabContainer} active page.
1053
1054         :param `wndOrInt`: an instance of `wx.Window` or an integer specifying a tab index.
1055         """
1056
1057         if type(wndOrInt) == types.IntType:
1058
1059             if wndOrInt >= len(self._pages):
1060                 return False
1061
1062             wnd = self._pages[wndOrInt].window
1063
1064         else:
1065             wnd = wndOrInt
1066
1067         found = False
1068
1069         for indx, page in enumerate(self._pages):
1070             if page.window == wnd:
1071                 page.active = True
1072                 found = True
1073             else:
1074                 page.active = False
1075
1076         return found
1077
1078
1079     def SetNoneActive(self):
1080         """ Sets all the tabs as inactive (non-selected). """
1081
1082         for page in self._pages:
1083             page.active = False
1084
1085
1086     def GetActivePage(self):
1087         """ Returns the current selected tab or ``wx.NOT_FOUND`` if none is selected. """
1088
1089         for indx, page in enumerate(self._pages):
1090             if page.active:
1091                 return indx
1092
1093         return wx.NOT_FOUND
1094
1095
1096     def GetWindowFromIdx(self, idx):
1097         """
1098         Returns the window associated with the tab with index `idx`.
1099
1100         :param `idx`: the tab index.
1101         """
1102
1103         if idx >= len(self._pages):
1104             return None
1105
1106         return self._pages[idx].window
1107
1108
1109     def GetIdxFromWindow(self, wnd):
1110         """
1111         Returns the tab index based on the window `wnd` associated with it.
1112
1113         :param `wnd`: an instance of `wx.Window`.
1114         """
1115
1116         for indx, page in enumerate(self._pages):
1117             if page.window == wnd:
1118                 return indx
1119
1120         return wx.NOT_FOUND
1121
1122
1123     def GetPage(self, idx):
1124         """
1125         Returns the page specified by the given index.
1126
1127         :param `idx`: the tab index.
1128         """
1129
1130         if idx < 0 or idx >= len(self._pages):
1131             raise Exception("Invalid Page index")
1132
1133         return self._pages[idx]
1134
1135
1136     def GetPages(self):
1137         """ Returns a list of all the pages in this L{AuiTabContainer}. """
1138
1139         return self._pages
1140
1141
1142     def GetPageCount(self):
1143         """ Returns the number of pages in the L{AuiTabContainer}. """
1144
1145         return len(self._pages)
1146
1147
1148     def GetEnabled(self, idx):
1149         """
1150         Returns whether a tab is enabled or not.
1151
1152         :param `idx`: the tab index.
1153         """
1154
1155         if idx < 0 or idx >= len(self._pages):
1156             return False
1157
1158         return self._pages[idx].enabled
1159
1160
1161     def EnableTab(self, idx, enable=True):
1162         """
1163         Enables/disables a tab in the L{AuiTabContainer}.
1164
1165         :param `idx`: the tab index;
1166         :param `enable`: ``True`` to enable a tab, ``False`` to disable it.
1167         """
1168
1169         if idx < 0 or idx >= len(self._pages):
1170             raise Exception("Invalid Page index")
1171
1172         self._pages[idx].enabled = enable
1173         wnd = self.GetWindowFromIdx(idx)
1174         wnd.Enable(enable)
1175
1176
1177     def AddButton(self, id, location, normal_bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap):
1178         """
1179         Adds a button in the tab area.
1180
1181         :param `id`: the button identifier. This can be one of the following:
1182
1183          ==============================  =================================
1184          Button Identifier               Description
1185          ==============================  =================================
1186          ``AUI_BUTTON_CLOSE``            Shows a close button on the tab area
1187          ``AUI_BUTTON_WINDOWLIST``       Shows a window list button on the tab area
1188          ``AUI_BUTTON_LEFT``             Shows a left button on the tab area
1189          ``AUI_BUTTON_RIGHT``            Shows a right button on the tab area
1190          ==============================  =================================
1191
1192         :param `location`: the button location. Can be ``wx.LEFT`` or ``wx.RIGHT``;
1193         :param `normal_bitmap`: the bitmap for an enabled tab;
1194         :param `disabled_bitmap`: the bitmap for a disabled tab.
1195         """
1196
1197         button = AuiTabContainerButton()
1198         button.id = id
1199         button.bitmap = normal_bitmap
1200         button.dis_bitmap = disabled_bitmap
1201         button.location = location
1202         button.cur_state = AUI_BUTTON_STATE_NORMAL
1203
1204         self._buttons.append(button)
1205
1206
1207     def RemoveButton(self, id):
1208         """
1209         Removes a button from the tab area.
1210
1211         :param `id`: the button identifier. See L{AddButton} for a list of button identifiers.
1212
1213         :see: L{AddButton}
1214         """
1215
1216         for button in self._buttons:
1217             if button.id == id:
1218                 self._buttons.remove(button)
1219                 return
1220
1221
1222     def GetTabOffset(self):
1223         """ Returns the tab offset. """
1224
1225         return self._tab_offset
1226
1227
1228     def SetTabOffset(self, offset):
1229         """
1230         Sets the tab offset.
1231
1232         :param `offset`: the tab offset.
1233         """
1234
1235         self._tab_offset = offset
1236
1237
1238     def MinimizeTabOffset(self, dc, wnd, max_width):
1239         """
1240         Minimize `self._tab_offset` to fit as many tabs as possible in the available space.
1241
1242         :param `dc`: a `wx.DC` device context;
1243         :param `wnd`: an instance of `wx.Window`;
1244         :param `max_width`: the maximum available width for the tabs.
1245         """
1246
1247         total_width = 0
1248
1249         for i, page in reversed(list(enumerate(self._pages))):
1250
1251             tab_button = self._tab_close_buttons[i]
1252             (tab_width, tab_height), x_extent = self._art.GetTabSize(dc, wnd, page.caption, page.bitmap, page.active, tab_button.cur_state, page.control)
1253             total_width += tab_width
1254
1255             if total_width > max_width:
1256
1257                 tab_offset = i + 1
1258
1259                 if tab_offset < self._tab_offset and tab_offset < len(self._pages):
1260                     self._tab_offset = tab_offset
1261
1262                 break
1263
1264         if i == 0:
1265             self._tab_offset = 0
1266
1267
1268     def Render(self, raw_dc, wnd):
1269         """
1270         Renders the tab catalog to the specified `wx.DC`.
1271
1272         It is a virtual function and can be overridden to provide custom drawing
1273         capabilities.
1274
1275         :param `raw_dc`: a `wx.DC` device context;
1276         :param `wnd`: an instance of `wx.Window`.
1277         """
1278
1279         if not raw_dc or not raw_dc.IsOk():
1280             return
1281
1282         dc = wx.MemoryDC()
1283
1284         # use the same layout direction as the window DC uses to ensure that the
1285         # text is rendered correctly
1286         dc.SetLayoutDirection(raw_dc.GetLayoutDirection())
1287
1288         page_count = len(self._pages)
1289         button_count = len(self._buttons)
1290
1291         # create off-screen bitmap
1292         bmp = wx.EmptyBitmap(self._rect.GetWidth(), self._rect.GetHeight())
1293         dc.SelectObject(bmp)
1294
1295         if not dc.IsOk():
1296             return
1297
1298         # find out if size of tabs is larger than can be
1299         # afforded on screen
1300         total_width = visible_width = 0
1301
1302         for i in xrange(page_count):
1303             page = self._pages[i]
1304
1305             # determine if a close button is on this tab
1306             close_button = False
1307             if (self._agwFlags & AUI_NB_CLOSE_ON_ALL_TABS and page.hasCloseButton) or \
1308                (self._agwFlags & AUI_NB_CLOSE_ON_ACTIVE_TAB and page.active and page.hasCloseButton):
1309
1310                 close_button = True
1311
1312             control = page.control
1313             if control:
1314                 try:
1315                     control.GetSize()
1316                 except wx.PyDeadObjectError:
1317                     page.control = None
1318
1319             size, x_extent = self._art.GetTabSize(dc, wnd, page.caption, page.bitmap, page.active,
1320                                                   (close_button and [AUI_BUTTON_STATE_NORMAL] or \
1321                                                    [AUI_BUTTON_STATE_HIDDEN])[0], page.control)
1322
1323             if i+1 < page_count:
1324                 total_width += x_extent
1325             else:
1326                 total_width += size[0]
1327
1328             if i >= self._tab_offset:
1329                 if i+1 < page_count:
1330                     visible_width += x_extent
1331                 else:
1332                     visible_width += size[0]
1333
1334         if total_width > self._rect.GetWidth() or self._tab_offset != 0:
1335
1336             # show left/right buttons
1337             for button in self._buttons:
1338                 if button.id == AUI_BUTTON_LEFT or \
1339                    button.id == AUI_BUTTON_RIGHT:
1340
1341                     button.cur_state &= ~AUI_BUTTON_STATE_HIDDEN
1342
1343         else:
1344
1345             # hide left/right buttons
1346             for button in self._buttons:
1347                 if button.id == AUI_BUTTON_LEFT or \
1348                    button.id == AUI_BUTTON_RIGHT:
1349
1350                     button.cur_state |= AUI_BUTTON_STATE_HIDDEN
1351
1352         # determine whether left button should be enabled
1353         for button in self._buttons:
1354             if button.id == AUI_BUTTON_LEFT:
1355                 if self._tab_offset == 0:
1356                     button.cur_state |= AUI_BUTTON_STATE_DISABLED
1357                 else:
1358                     button.cur_state &= ~AUI_BUTTON_STATE_DISABLED
1359
1360             if button.id == AUI_BUTTON_RIGHT:
1361                 if visible_width < self._rect.GetWidth() - 16*button_count:
1362                     button.cur_state |= AUI_BUTTON_STATE_DISABLED
1363                 else:
1364                     button.cur_state &= ~AUI_BUTTON_STATE_DISABLED
1365
1366         # draw background
1367         self._art.DrawBackground(dc, wnd, self._rect)
1368
1369         # draw buttons
1370         left_buttons_width = 0
1371         right_buttons_width = 0
1372
1373         # draw the buttons on the right side
1374         offset = self._rect.x + self._rect.width
1375
1376         for i in xrange(button_count):
1377             button = self._buttons[button_count - i - 1]
1378
1379             if button.location != wx.RIGHT:
1380                 continue
1381             if button.cur_state & AUI_BUTTON_STATE_HIDDEN:
1382                 continue
1383
1384             button_rect = wx.Rect(*self._rect)
1385             button_rect.SetY(1)
1386             button_rect.SetWidth(offset)
1387
1388             button.rect = self._art.DrawButton(dc, wnd, button_rect, button, wx.RIGHT)
1389
1390             offset -= button.rect.GetWidth()
1391             right_buttons_width += button.rect.GetWidth()
1392
1393         offset = 0
1394
1395         # draw the buttons on the left side
1396         for i in xrange(button_count):
1397             button = self._buttons[button_count - i - 1]
1398
1399             if button.location != wx.LEFT:
1400                 continue
1401             if button.cur_state & AUI_BUTTON_STATE_HIDDEN:
1402                 continue
1403
1404             button_rect = wx.Rect(offset, 1, 1000, self._rect.height)
1405
1406             button.rect = self._art.DrawButton(dc, wnd, button_rect, button, wx.LEFT)
1407
1408             offset += button.rect.GetWidth()
1409             left_buttons_width += button.rect.GetWidth()
1410
1411         offset = left_buttons_width
1412
1413         if offset == 0:
1414             offset += self._art.GetIndentSize()
1415
1416         # prepare the tab-close-button array
1417         # make sure tab button entries which aren't used are marked as hidden
1418         for i in xrange(page_count, len(self._tab_close_buttons)):
1419             self._tab_close_buttons[i].cur_state = AUI_BUTTON_STATE_HIDDEN
1420
1421         # make sure there are enough tab button entries to accommodate all tabs
1422         while len(self._tab_close_buttons) < page_count:
1423             tempbtn = AuiTabContainerButton()
1424             tempbtn.id = AUI_BUTTON_CLOSE
1425             tempbtn.location = wx.CENTER
1426             tempbtn.cur_state = AUI_BUTTON_STATE_HIDDEN
1427             self._tab_close_buttons.append(tempbtn)
1428
1429         # buttons before the tab offset must be set to hidden
1430         for i in xrange(self._tab_offset):
1431             self._tab_close_buttons[i].cur_state = AUI_BUTTON_STATE_HIDDEN
1432             if self._pages[i].control:
1433                 if self._pages[i].control.IsShown():
1434                     self._pages[i].control.Hide()
1435
1436         self.MinimizeTabOffset(dc, wnd, self._rect.GetWidth() - right_buttons_width - offset - 2)
1437
1438         # draw the tabs
1439         active = 999
1440         active_offset = 0
1441
1442         rect = wx.Rect(*self._rect)
1443         rect.y = 0
1444         rect.height = self._rect.height
1445
1446         for i in xrange(self._tab_offset, page_count):
1447
1448             page = self._pages[i]
1449             tab_button = self._tab_close_buttons[i]
1450
1451             # determine if a close button is on this tab
1452             if (self._agwFlags & AUI_NB_CLOSE_ON_ALL_TABS and page.hasCloseButton) or \
1453                (self._agwFlags & AUI_NB_CLOSE_ON_ACTIVE_TAB and page.active and page.hasCloseButton):
1454
1455                 if tab_button.cur_state == AUI_BUTTON_STATE_HIDDEN:
1456
1457                     tab_button.id = AUI_BUTTON_CLOSE
1458                     tab_button.cur_state = AUI_BUTTON_STATE_NORMAL
1459                     tab_button.location = wx.CENTER
1460
1461             else:
1462
1463                 tab_button.cur_state = AUI_BUTTON_STATE_HIDDEN
1464
1465             rect.x = offset
1466             rect.width = self._rect.width - right_buttons_width - offset - 2
1467
1468             if rect.width <= 0:
1469                 break
1470
1471             page.rect, tab_button.rect, x_extent = self._art.DrawTab(dc, wnd, page, rect, tab_button.cur_state)
1472
1473             if page.active:
1474                 active = i
1475                 active_offset = offset
1476                 active_rect = wx.Rect(*rect)
1477
1478             offset += x_extent
1479
1480         lenPages = len(self._pages)
1481         # make sure to deactivate buttons which are off the screen to the right
1482         for j in xrange(i+1, len(self._tab_close_buttons)):
1483             self._tab_close_buttons[j].cur_state = AUI_BUTTON_STATE_HIDDEN
1484             if j > 0 and j <= lenPages:
1485                 if self._pages[j-1].control:
1486                     if self._pages[j-1].control.IsShown():
1487                         self._pages[j-1].control.Hide()
1488
1489         # draw the active tab again so it stands in the foreground
1490         if active >= self._tab_offset and active < len(self._pages):
1491
1492             page = self._pages[active]
1493             tab_button = self._tab_close_buttons[active]
1494
1495             rect.x = active_offset
1496             dummy = self._art.DrawTab(dc, wnd, page, active_rect, tab_button.cur_state)
1497
1498         raw_dc.Blit(self._rect.x, self._rect.y, self._rect.GetWidth(), self._rect.GetHeight(), dc, 0, 0)
1499
1500
1501     def IsTabVisible(self, tabPage, tabOffset, dc, wnd):
1502         """
1503         Returns whether a tab is visible or not.
1504
1505         :param `tabPage`: the tab index;
1506         :param `tabOffset`: the tab offset;
1507         :param `dc`: a `wx.DC` device context;
1508         :param `wnd`: an instance of `wx.Window` derived window.
1509         """
1510
1511         if not dc or not dc.IsOk():
1512             return False
1513
1514         page_count = len(self._pages)
1515         button_count = len(self._buttons)
1516         self.Render(dc, wnd)
1517
1518         # Hasn't been rendered yet assume it's visible
1519         if len(self._tab_close_buttons) < page_count:
1520             return True
1521
1522         if self._agwFlags & AUI_NB_SCROLL_BUTTONS:
1523             # First check if both buttons are disabled - if so, there's no need to
1524             # check further for visibility.
1525             arrowButtonVisibleCount = 0
1526             for i in xrange(button_count):
1527
1528                 button = self._buttons[i]
1529                 if button.id == AUI_BUTTON_LEFT or \
1530                    button.id == AUI_BUTTON_RIGHT:
1531
1532                     if button.cur_state & AUI_BUTTON_STATE_HIDDEN == 0:
1533                         arrowButtonVisibleCount += 1
1534
1535             # Tab must be visible
1536             if arrowButtonVisibleCount == 0:
1537                 return True
1538
1539         # If tab is less than the given offset, it must be invisible by definition
1540         if tabPage < tabOffset:
1541             return False
1542
1543         # draw buttons
1544         left_buttons_width = 0
1545         right_buttons_width = 0
1546
1547         offset = 0
1548
1549         # calculate size of the buttons on the right side
1550         offset = self._rect.x + self._rect.width
1551
1552         for i in xrange(button_count):
1553             button = self._buttons[button_count - i - 1]
1554
1555             if button.location != wx.RIGHT:
1556                 continue
1557             if button.cur_state & AUI_BUTTON_STATE_HIDDEN:
1558                 continue
1559
1560             offset -= button.rect.GetWidth()
1561             right_buttons_width += button.rect.GetWidth()
1562
1563         offset = 0
1564
1565         # calculate size of the buttons on the left side
1566         for i in xrange(button_count):
1567             button = self._buttons[button_count - i - 1]
1568
1569             if button.location != wx.LEFT:
1570                 continue
1571             if button.cur_state & AUI_BUTTON_STATE_HIDDEN:
1572                 continue
1573
1574             offset += button.rect.GetWidth()
1575             left_buttons_width += button.rect.GetWidth()
1576
1577         offset = left_buttons_width
1578
1579         if offset == 0:
1580             offset += self._art.GetIndentSize()
1581
1582         rect = wx.Rect(*self._rect)
1583         rect.y = 0
1584         rect.height = self._rect.height
1585
1586         # See if the given page is visible at the given tab offset (effectively scroll position)
1587         for i in xrange(tabOffset, page_count):
1588
1589             page = self._pages[i]
1590             tab_button = self._tab_close_buttons[i]
1591
1592             rect.x = offset
1593             rect.width = self._rect.width - right_buttons_width - offset - 2
1594
1595             if rect.width <= 0:
1596                 return False # haven't found the tab, and we've run out of space, so return False
1597
1598             size, x_extent = self._art.GetTabSize(dc, wnd, page.caption, page.bitmap, page.active, tab_button.cur_state, page.control)
1599             offset += x_extent
1600
1601             if i == tabPage:
1602
1603                 # If not all of the tab is visible, and supposing there's space to display it all,
1604                 # we could do better so we return False.
1605                 if (self._rect.width - right_buttons_width - offset - 2) <= 0 and (self._rect.width - right_buttons_width - left_buttons_width) > x_extent:
1606                     return False
1607                 else:
1608                     return True
1609
1610         # Shouldn't really get here, but if it does, assume the tab is visible to prevent
1611         # further looping in calling code.
1612         return True
1613
1614
1615     def MakeTabVisible(self, tabPage, win):
1616         """
1617         Make the tab visible if it wasn't already.
1618
1619         :param `tabPage`: the tab index;
1620         :param `win`: an instance of `wx.Window` derived window.
1621         """
1622
1623         dc = wx.ClientDC(win)
1624
1625         if not self.IsTabVisible(tabPage, self.GetTabOffset(), dc, win):
1626             for i in xrange(len(self._pages)):
1627                 if self.IsTabVisible(tabPage, i, dc, win):
1628                     self.SetTabOffset(i)
1629                     win.Refresh()
1630                     return
1631
1632
1633     def TabHitTest(self, x, y):
1634         """
1635         TabHitTest() tests if a tab was hit, passing the window pointer
1636         back if that condition was fulfilled.
1637
1638         :param `x`: the mouse `x` position;
1639         :param `y`: the mouse `y` position.
1640         """
1641
1642         if not self._rect.Contains((x,y)):
1643             return None
1644
1645         btn = self.ButtonHitTest(x, y)
1646         if btn:
1647             if btn in self._buttons:
1648                 return None
1649
1650         for i in xrange(self._tab_offset, len(self._pages)):
1651             page = self._pages[i]
1652             if page.rect.Contains((x,y)):
1653                 return page.window
1654
1655         return None
1656
1657
1658     def ButtonHitTest(self, x, y):
1659         """
1660         Tests if a button was hit.
1661
1662         :param `x`: the mouse `x` position;
1663         :param `y`: the mouse `y` position.
1664
1665         :returns: and instance of L{AuiTabContainerButton} if a button was hit, ``None`` otherwise.
1666         """
1667
1668         if not self._rect.Contains((x,y)):
1669             return None
1670
1671         for button in self._buttons:
1672             if button.rect.Contains((x,y)) and \
1673                (button.cur_state & (AUI_BUTTON_STATE_HIDDEN|AUI_BUTTON_STATE_DISABLED)) == 0:
1674                 return button
1675
1676         for button in self._tab_close_buttons:
1677             if button.rect.Contains((x,y)) and \
1678                (button.cur_state & (AUI_BUTTON_STATE_HIDDEN|AUI_BUTTON_STATE_DISABLED)) == 0:
1679                 return button
1680
1681         return None
1682
1683
1684     def DoShowHide(self):
1685         """
1686         This function shows the active window, then hides all of the other windows
1687         (in that order).
1688         """
1689
1690         pages = self.GetPages()
1691
1692         # show new active page first
1693         for page in pages:
1694             if page.active:
1695                 page.window.Show(True)
1696                 break
1697
1698         # hide all other pages
1699         for page in pages:
1700             if not page.active:
1701                 page.window.Show(False)
1702
1703
1704 # ----------------------------------------------------------------------
1705 # -- AuiTabCtrl class implementation --
1706
1707 class AuiTabCtrl(wx.PyControl, AuiTabContainer):
1708     """
1709     This is an actual `wx.Window`-derived window which can be used as a tab
1710     control in the normal sense.
1711     """
1712
1713     def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
1714                  style=wx.NO_BORDER|wx.WANTS_CHARS|wx.TAB_TRAVERSAL):
1715         """
1716         Default class constructor.
1717         Used internally, do not call it in your code!
1718
1719         :param `parent`: the L{AuiTabCtrl} parent;
1720         :param `id`: an identifier for the control: a value of -1 is taken to mean a default;
1721         :param `pos`: the control position. A value of (-1, -1) indicates a default position,
1722          chosen by either the windowing system or wxPython, depending on platform;
1723         :param `size`: the control size. A value of (-1, -1) indicates a default size,
1724          chosen by either the windowing system or wxPython, depending on platform;
1725         :param `style`: the window style.
1726         """
1727
1728         wx.PyControl.__init__(self, parent, id, pos, size, style, name="AuiTabCtrl")
1729         AuiTabContainer.__init__(self, parent)
1730
1731         self._click_pt = wx.Point(-1, -1)
1732         self._is_dragging = False
1733         self._hover_button = None
1734         self._pressed_button = None
1735         self._drag_image = None
1736         self._drag_img_offset = (0, 0)
1737         self._on_button = False
1738
1739         self.Bind(wx.EVT_PAINT, self.OnPaint)
1740         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
1741         self.Bind(wx.EVT_SIZE, self.OnSize)
1742         self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
1743         self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
1744         self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
1745         self.Bind(wx.EVT_MIDDLE_DOWN, self.OnMiddleDown)
1746         self.Bind(wx.EVT_MIDDLE_UP, self.OnMiddleUp)
1747         self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
1748         self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp)
1749         self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
1750         self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
1751         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
1752         self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.OnCaptureLost)
1753         self.Bind(wx.EVT_MOTION, self.OnMotion)
1754         self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)
1755         self.Bind(EVT_AUINOTEBOOK_BUTTON, self.OnButton)
1756
1757
1758     def IsDragging(self):
1759         """ Returns whether the user is dragging a tab with the mouse or not. """
1760
1761         return self._is_dragging
1762
1763
1764     def GetDefaultBorder(self):
1765         """ Returns the default border style for L{AuiTabCtrl}. """
1766
1767         return wx.BORDER_NONE
1768
1769
1770     def OnPaint(self, event):
1771         """
1772         Handles the ``wx.EVT_PAINT`` event for L{AuiTabCtrl}.
1773
1774         :param `event`: a `wx.PaintEvent` event to be processed.
1775         """
1776
1777         dc = wx.PaintDC(self)
1778         dc.SetFont(self.GetFont())
1779
1780         if self.GetPageCount() > 0:
1781             self.Render(dc, self)
1782
1783
1784     def OnEraseBackground(self, event):
1785         """
1786         Handles the ``wx.EVT_ERASE_BACKGROUND`` event for L{AuiTabCtrl}.
1787
1788         :param `event`: a `wx.EraseEvent` event to be processed.
1789
1790         :note: This is intentionally empty, to reduce flicker.
1791         """
1792
1793         pass
1794
1795
1796     def DoGetBestSize(self):
1797         """
1798         Gets the size which best suits the window: for a control, it would be the
1799         minimal size which doesn't truncate the control, for a panel - the same
1800         size as it would have after a call to `Fit()`.
1801
1802         :note: Overridden from `wx.PyControl`.
1803         """
1804
1805         return wx.Size(self._rect.width, self._rect.height)
1806
1807
1808     def OnSize(self, event):
1809         """
1810         Handles the ``wx.EVT_SIZE`` event for L{AuiTabCtrl}.
1811
1812         :param `event`: a `wx.SizeEvent` event to be processed.
1813         """
1814
1815         s = event.GetSize()
1816         self.SetTabRect(wx.Rect(0, 0, s.GetWidth(), s.GetHeight()))
1817
1818
1819     def OnLeftDown(self, event):
1820         """
1821         Handles the ``wx.EVT_LEFT_DOWN`` event for L{AuiTabCtrl}.
1822
1823         :param `event`: a `wx.MouseEvent` event to be processed.
1824         """
1825
1826         self.CaptureMouse()
1827         self._click_pt = wx.Point(-1, -1)
1828         self._is_dragging = False
1829         self._click_tab = None
1830         self._pressed_button = None
1831
1832         wnd = self.TabHitTest(event.GetX(), event.GetY())
1833
1834         if wnd is not None:
1835             new_selection = self.GetIdxFromWindow(wnd)
1836
1837             # AuiNotebooks always want to receive this event
1838             # even if the tab is already active, because they may
1839             # have multiple tab controls
1840             if new_selection != self.GetActivePage() or isinstance(self.GetParent(), AuiNotebook):
1841
1842                 e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, self.GetId())
1843                 e.SetSelection(new_selection)
1844                 e.SetOldSelection(self.GetActivePage())
1845                 e.SetEventObject(self)
1846                 self.GetEventHandler().ProcessEvent(e)
1847
1848             self._click_pt.x = event.GetX()
1849             self._click_pt.y = event.GetY()
1850             self._click_tab = wnd
1851         else:
1852             page_index = self.GetActivePage()
1853             if page_index != wx.NOT_FOUND:
1854                 self.GetWindowFromIdx(page_index).SetFocus()
1855
1856         if self._hover_button:
1857             self._pressed_button = self._hover_button
1858             self._pressed_button.cur_state = AUI_BUTTON_STATE_PRESSED
1859             self._on_button = True
1860             self.Refresh()
1861             self.Update()
1862
1863
1864     def OnCaptureLost(self, event):
1865         """
1866         Handles the ``wx.EVT_MOUSE_CAPTURE_LOST`` event for L{AuiTabCtrl}.
1867
1868         :param `event`: a `wx.MouseCaptureLostEvent` event to be processed.
1869         """
1870
1871         if self._is_dragging:
1872             self._is_dragging = False
1873             self._on_button = False
1874
1875             if self._drag_image:
1876                 self._drag_image.EndDrag()
1877                 del self._drag_image
1878                 self._drag_image = None
1879
1880             event = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_CANCEL_DRAG, self.GetId())
1881             event.SetSelection(self.GetIdxFromWindow(self._click_tab))
1882             event.SetOldSelection(event.GetSelection())
1883             event.SetEventObject(self)
1884             self.GetEventHandler().ProcessEvent(event)
1885
1886
1887     def OnLeftUp(self, event):
1888         """
1889         Handles the ``wx.EVT_LEFT_UP`` event for L{AuiTabCtrl}.
1890
1891         :param `event`: a `wx.MouseEvent` event to be processed.
1892         """
1893
1894         self._on_button = False
1895
1896         if self._is_dragging:
1897
1898             if self.HasCapture():
1899                 self.ReleaseMouse()
1900
1901             self._is_dragging = False
1902             if self._drag_image:
1903                 self._drag_image.EndDrag()
1904                 del self._drag_image
1905                 self._drag_image = None
1906                 self.GetParent().Refresh()
1907
1908             evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_END_DRAG, self.GetId())
1909             evt.SetSelection(self.GetIdxFromWindow(self._click_tab))
1910             evt.SetOldSelection(evt.GetSelection())
1911             evt.SetEventObject(self)
1912             self.GetEventHandler().ProcessEvent(evt)
1913
1914             return
1915
1916         self.GetParent()._mgr.HideHint()
1917
1918         if self.HasCapture():
1919             self.ReleaseMouse()
1920
1921         if self._hover_button:
1922             self._pressed_button = self._hover_button
1923
1924         if self._pressed_button:
1925
1926             # make sure we're still clicking the button
1927             button = self.ButtonHitTest(event.GetX(), event.GetY())
1928
1929             if button is None:
1930                 return
1931
1932             if button != self._pressed_button:
1933                 self._pressed_button = None
1934                 return
1935
1936             self.Refresh()
1937             self.Update()
1938
1939             if self._pressed_button.cur_state & AUI_BUTTON_STATE_DISABLED == 0:
1940
1941                 evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BUTTON, self.GetId())
1942                 evt.SetSelection(self.GetIdxFromWindow(self._click_tab))
1943                 evt.SetInt(self._pressed_button.id)
1944                 evt.SetEventObject(self)
1945                 eventHandler = self.GetEventHandler()
1946
1947                 if eventHandler is not None:
1948                     eventHandler.ProcessEvent(evt)
1949
1950             self._pressed_button = None
1951
1952         self._click_pt = wx.Point(-1, -1)
1953         self._is_dragging = False
1954         self._click_tab = None
1955
1956
1957     def OnMiddleUp(self, event):
1958         """
1959         Handles the ``wx.EVT_MIDDLE_UP`` event for L{AuiTabCtrl}.
1960
1961         :param `event`: a `wx.MouseEvent` event to be processed.
1962         """
1963
1964         eventHandler = self.GetEventHandler()
1965         if not isinstance(eventHandler, AuiTabCtrl):
1966             event.Skip()
1967             return
1968
1969         x, y = event.GetX(), event.GetY()
1970         wnd = self.TabHitTest(x, y)
1971
1972         if wnd:
1973             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_UP, self.GetId())
1974             e.SetEventObject(self)
1975             e.SetSelection(self.GetIdxFromWindow(wnd))
1976             self.GetEventHandler().ProcessEvent(e)
1977         elif not self.ButtonHitTest(x, y):
1978             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_UP, self.GetId())
1979             e.SetEventObject(self)
1980             self.GetEventHandler().ProcessEvent(e)
1981
1982
1983     def OnMiddleDown(self, event):
1984         """
1985         Handles the ``wx.EVT_MIDDLE_DOWN`` event for L{AuiTabCtrl}.
1986
1987         :param `event`: a `wx.MouseEvent` event to be processed.
1988         """
1989
1990         eventHandler = self.GetEventHandler()
1991         if not isinstance(eventHandler, AuiTabCtrl):
1992             event.Skip()
1993             return
1994         
1995         x, y = event.GetX(), event.GetY()
1996         wnd = self.TabHitTest(x, y)
1997
1998         if wnd:
1999             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_DOWN, self.GetId())
2000             e.SetEventObject(self)
2001             e.SetSelection(self.GetIdxFromWindow(wnd))
2002             self.GetEventHandler().ProcessEvent(e)
2003         elif not self.ButtonHitTest(x, y):
2004             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_DOWN, self.GetId())
2005             e.SetEventObject(self)
2006             self.GetEventHandler().ProcessEvent(e)
2007
2008
2009     def OnRightUp(self, event):
2010         """
2011         Handles the ``wx.EVT_RIGHT_UP`` event for L{AuiTabCtrl}.
2012
2013         :param `event`: a `wx.MouseEvent` event to be processed.
2014         """
2015
2016         x, y = event.GetX(), event.GetY()
2017         wnd = self.TabHitTest(x, y)
2018
2019         if wnd:
2020             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP, self.GetId())
2021             e.SetEventObject(self)
2022             e.SetSelection(self.GetIdxFromWindow(wnd))
2023             self.GetEventHandler().ProcessEvent(e)
2024         elif not self.ButtonHitTest(x, y):
2025             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_UP, self.GetId())
2026             e.SetEventObject(self)
2027             self.GetEventHandler().ProcessEvent(e)
2028
2029
2030     def OnRightDown(self, event):
2031         """
2032         Handles the ``wx.EVT_RIGHT_DOWN`` event for L{AuiTabCtrl}.
2033
2034         :param `event`: a `wx.MouseEvent` event to be processed.
2035         """
2036
2037         x, y = event.GetX(), event.GetY()
2038         wnd = self.TabHitTest(x, y)
2039
2040         if wnd:
2041             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_DOWN, self.GetId())
2042             e.SetEventObject(self)
2043             e.SetSelection(self.GetIdxFromWindow(wnd))
2044             self.GetEventHandler().ProcessEvent(e)
2045         elif not self.ButtonHitTest(x, y):
2046             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_DOWN, self.GetId())
2047             e.SetEventObject(self)
2048             self.GetEventHandler().ProcessEvent(e)
2049
2050
2051     def OnLeftDClick(self, event):
2052         """
2053         Handles the ``wx.EVT_LEFT_DCLICK`` event for L{AuiTabCtrl}.
2054
2055         :param `event`: a `wx.MouseEvent` event to be processed.
2056         """
2057
2058         x, y = event.GetX(), event.GetY()
2059         wnd = self.TabHitTest(x, y)
2060
2061         if wnd:
2062             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_DCLICK, self.GetId())
2063             e.SetEventObject(self)
2064             e.SetSelection(self.GetIdxFromWindow(wnd))
2065             self.GetEventHandler().ProcessEvent(e)
2066         elif not self.ButtonHitTest(x, y):
2067             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK, self.GetId())
2068             e.SetEventObject(self)
2069             self.GetEventHandler().ProcessEvent(e)
2070
2071
2072     def OnMotion(self, event):
2073         """
2074         Handles the ``wx.EVT_MOTION`` event for L{AuiTabCtrl}.
2075
2076         :param `event`: a `wx.MouseEvent` event to be processed.
2077         """
2078
2079         pos = event.GetPosition()
2080
2081         # check if the mouse is hovering above a button
2082
2083         button = self.ButtonHitTest(pos.x, pos.y)
2084         wnd = self.TabHitTest(pos.x, pos.y)
2085
2086         if wnd is not None:
2087             mouse_tab = self.GetIdxFromWindow(wnd)
2088             if not self._pages[mouse_tab].enabled:
2089                 self._hover_button = None
2090                 return
2091
2092         if self._on_button:
2093             return
2094
2095         if button:
2096
2097             if self._hover_button and button != self._hover_button:
2098                 self._hover_button.cur_state = AUI_BUTTON_STATE_NORMAL
2099                 self._hover_button = None
2100                 self.Refresh()
2101                 self.Update()
2102
2103             if button.cur_state != AUI_BUTTON_STATE_HOVER:
2104                 button.cur_state = AUI_BUTTON_STATE_HOVER
2105                 self.Refresh()
2106                 self.Update()
2107                 self._hover_button = button
2108                 return
2109
2110         else:
2111
2112             if self._hover_button:
2113                 self._hover_button.cur_state = AUI_BUTTON_STATE_NORMAL
2114                 self._hover_button = None
2115                 self.Refresh()
2116                 self.Update()
2117
2118         if not event.LeftIsDown() or self._click_pt == wx.Point(-1, -1):
2119             return
2120
2121         if not self.HasCapture():
2122             return
2123
2124         wnd = self.TabHitTest(pos.x, pos.y)
2125
2126         if not self._is_dragging:
2127
2128             drag_x_threshold = wx.SystemSettings.GetMetric(wx.SYS_DRAG_X)
2129             drag_y_threshold = wx.SystemSettings.GetMetric(wx.SYS_DRAG_Y)
2130
2131             if abs(pos.x - self._click_pt.x) > drag_x_threshold or \
2132                abs(pos.y - self._click_pt.y) > drag_y_threshold:
2133
2134                 self._is_dragging = True
2135
2136                 if self._drag_image:
2137                     self._drag_image.EndDrag()
2138                     del self._drag_image
2139                     self._drag_image = None
2140
2141                 if self._agwFlags & AUI_NB_DRAW_DND_TAB:
2142                     # Create the custom draw image from the icons and the text of the item
2143                     mouse_tab = self.GetIdxFromWindow(wnd)
2144                     page = self._pages[mouse_tab]
2145                     tab_button = self._tab_close_buttons[mouse_tab]
2146                     self._drag_image = TabDragImage(self, page, tab_button.cur_state, self._art)
2147
2148                     if self._agwFlags & AUI_NB_TAB_FLOAT:
2149                         self._drag_image.BeginDrag(wx.Point(0,0), self, fullScreen=True)
2150                     else:
2151                         self._drag_image.BeginDragBounded(wx.Point(0,0), self, self.GetParent())
2152
2153                     # Capture the mouse cursor position offset relative to
2154                     # The tab image location
2155                     self._drag_img_offset = (pos[0] - page.rect.x,
2156                                              pos[1] - page.rect.y)
2157
2158                     self._drag_image.Show()
2159
2160         if not wnd:
2161             evt2 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BEGIN_DRAG, self.GetId())
2162             evt2.SetSelection(self.GetIdxFromWindow(self._click_tab))
2163             evt2.SetOldSelection(evt2.GetSelection())
2164             evt2.SetEventObject(self)
2165             self.GetEventHandler().ProcessEvent(evt2)
2166             if evt2.GetDispatched():
2167                 return
2168
2169         evt3 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_DRAG_MOTION, self.GetId())
2170         evt3.SetSelection(self.GetIdxFromWindow(self._click_tab))
2171         evt3.SetOldSelection(evt3.GetSelection())
2172         evt3.SetEventObject(self)
2173         self.GetEventHandler().ProcessEvent(evt3)
2174
2175         if self._drag_image:
2176             # Apply the drag images offset
2177             pos -= self._drag_img_offset
2178             self._drag_image.Move(pos)
2179
2180
2181     def OnLeaveWindow(self, event):
2182         """
2183         Handles the ``wx.EVT_LEAVE_WINDOW`` event for L{AuiTabCtrl}.
2184
2185         :param `event`: a `wx.MouseEvent` event to be processed.
2186         """
2187
2188         if self._hover_button:
2189             self._hover_button.cur_state = AUI_BUTTON_STATE_NORMAL
2190             self._hover_button = None
2191             self.Refresh()
2192             self.Update()
2193
2194
2195     def OnButton(self, event):
2196         """
2197         Handles the ``EVT_AUINOTEBOOK_BUTTON`` event for L{AuiTabCtrl}.
2198
2199         :param `event`: a L{AuiNotebookEvent} event to be processed.
2200         """
2201
2202         button = event.GetInt()
2203
2204         if button == AUI_BUTTON_LEFT or button == AUI_BUTTON_RIGHT:
2205             if button == AUI_BUTTON_LEFT:
2206                 if self.GetTabOffset() > 0:
2207
2208                     self.SetTabOffset(self.GetTabOffset()-1)
2209                     self.Refresh()
2210                     self.Update()
2211             else:
2212                 self.SetTabOffset(self.GetTabOffset()+1)
2213                 self.Refresh()
2214                 self.Update()
2215
2216         elif button == AUI_BUTTON_WINDOWLIST:
2217             idx = self.GetArtProvider().ShowDropDown(self, self._pages, self.GetActivePage())
2218
2219             if idx != -1:
2220
2221                 e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, self.GetId())
2222                 e.SetSelection(idx)
2223                 e.SetOldSelection(self.GetActivePage())
2224                 e.SetEventObject(self)
2225                 self.GetEventHandler().ProcessEvent(e)
2226
2227         else:
2228             event.Skip()
2229
2230
2231     def OnSetFocus(self, event):
2232         """
2233         Handles the ``wx.EVT_SET_FOCUS`` event for L{AuiTabCtrl}.
2234
2235         :param `event`: a `wx.FocusEvent` event to be processed.
2236         """
2237
2238         self.Refresh()
2239
2240
2241     def OnKillFocus(self, event):
2242         """
2243         Handles the ``wx.EVT_KILL_FOCUS`` event for L{AuiTabCtrl}.
2244
2245         :param `event`: a `wx.FocusEvent` event to be processed.
2246         """
2247
2248         self.Refresh()
2249
2250
2251     def OnKeyDown(self, event):
2252         """
2253         Handles the ``wx.EVT_KEY_DOWN`` event for L{AuiTabCtrl}.
2254
2255         :param `event`: a `wx.KeyEvent` event to be processed.
2256         """
2257
2258         key = event.GetKeyCode()
2259         nb = self.GetParent()
2260
2261         if key == wx.WXK_LEFT:
2262             nb.AdvanceSelection(False)
2263             self.SetFocus()
2264
2265         elif key == wx.WXK_RIGHT:
2266             nb.AdvanceSelection(True)
2267             self.SetFocus()
2268
2269         elif key == wx.WXK_HOME:
2270             newPage = 0
2271             nb.SetSelection(newPage)
2272             self.SetFocus()
2273
2274         elif key == wx.WXK_END:
2275             newPage = nb.GetPageCount() - 1
2276             nb.SetSelection(newPage)
2277             self.SetFocus()
2278
2279         elif key == wx.WXK_TAB:
2280             if not event.ControlDown():
2281                 flags = 0
2282                 if not event.ShiftDown(): flags |= wx.NavigationKeyEvent.IsForward
2283                 if event.CmdDown():       flags |= wx.NavigationKeyEvent.WinChange
2284                 self.Navigate(flags)
2285             else:
2286
2287                 if not nb or not isinstance(nb, AuiNotebook):
2288                     event.Skip()
2289                     return
2290
2291                 bForward = bWindowChange = 0
2292                 if not event.ShiftDown(): bForward |= wx.NavigationKeyEvent.IsForward
2293                 if event.CmdDown():       bWindowChange |= wx.NavigationKeyEvent.WinChange
2294
2295                 keyEvent = wx.NavigationKeyEvent()
2296                 keyEvent.SetDirection(bForward)
2297                 keyEvent.SetWindowChange(bWindowChange)
2298                 keyEvent.SetFromTab(True)
2299                 keyEvent.SetEventObject(nb)
2300
2301                 if not nb.GetEventHandler().ProcessEvent(keyEvent):
2302
2303                     # Not processed? Do an explicit tab into the page.
2304                     win = self.GetWindowFromIdx(self.GetActivePage())
2305                     if win:
2306                         win.SetFocus()
2307
2308                 self.SetFocus()
2309
2310                 return
2311
2312         else:
2313             event.Skip()
2314
2315
2316     def OnKeyDown2(self, event):
2317         """
2318         Deprecated.
2319
2320         Handles the ``wx.EVT_KEY_DOWN`` event for L{AuiTabCtrl}.
2321
2322         :param `event`: a `wx.KeyEvent` event to be processed.
2323
2324         :warning: This method implementation is now deprecated. Refer to L{OnKeyDown}
2325          for the correct one.
2326         """
2327
2328         if self.GetActivePage() == -1:
2329             event.Skip()
2330             return
2331
2332         # We can't leave tab processing to the system on Windows, tabs and keys
2333         # get eaten by the system and not processed properly if we specify both
2334         # wxTAB_TRAVERSAL and wxWANTS_CHARS. And if we specify just wxTAB_TRAVERSAL,
2335         # we don't key arrow key events.
2336
2337         key = event.GetKeyCode()
2338
2339         if key == wx.WXK_NUMPAD_PAGEUP:
2340             key = wx.WXK_PAGEUP
2341         if key == wx.WXK_NUMPAD_PAGEDOWN:
2342             key = wx.WXK_PAGEDOWN
2343         if key == wx.WXK_NUMPAD_HOME:
2344             key = wx.WXK_HOME
2345         if key == wx.WXK_NUMPAD_END:
2346             key = wx.WXK_END
2347         if key == wx.WXK_NUMPAD_LEFT:
2348             key = wx.WXK_LEFT
2349         if key == wx.WXK_NUMPAD_RIGHT:
2350             key = wx.WXK_RIGHT
2351
2352         if key == wx.WXK_TAB or key == wx.WXK_PAGEUP or key == wx.WXK_PAGEDOWN:
2353
2354             bCtrlDown = event.ControlDown()
2355             bShiftDown = event.ShiftDown()
2356
2357             bForward = (key == wx.WXK_TAB and not bShiftDown) or (key == wx.WXK_PAGEDOWN)
2358             bWindowChange = (key == wx.WXK_PAGEUP) or (key == wx.WXK_PAGEDOWN) or bCtrlDown
2359             bFromTab = (key == wx.WXK_TAB)
2360
2361             nb = self.GetParent()
2362             if not nb or not isinstance(nb, AuiNotebook):
2363                 event.Skip()
2364                 return
2365
2366             keyEvent = wx.NavigationKeyEvent()
2367             keyEvent.SetDirection(bForward)
2368             keyEvent.SetWindowChange(bWindowChange)
2369             keyEvent.SetFromTab(bFromTab)
2370             keyEvent.SetEventObject(nb)
2371
2372             if not nb.GetEventHandler().ProcessEvent(keyEvent):
2373
2374                 # Not processed? Do an explicit tab into the page.
2375                 win = self.GetWindowFromIdx(self.GetActivePage())
2376                 if win:
2377                     win.SetFocus()
2378
2379             return
2380
2381         if len(self._pages) < 2:
2382             event.Skip()
2383             return
2384
2385         newPage = -1
2386
2387         if self.GetLayoutDirection() == wx.Layout_RightToLeft:
2388             forwardKey = wx.WXK_LEFT
2389             backwardKey = wx.WXK_RIGHT
2390         else:
2391             forwardKey = wx.WXK_RIGHT
2392             backwardKey = wx.WXK_LEFT
2393
2394         if key == forwardKey:
2395             if self.GetActivePage() == -1:
2396                 newPage = 0
2397             elif self.GetActivePage() < len(self._pages) - 1:
2398                 newPage = self.GetActivePage() + 1
2399
2400         elif key == backwardKey:
2401             if self.GetActivePage() == -1:
2402                 newPage = len(self._pages) - 1
2403             elif self.GetActivePage() > 0:
2404                 newPage = self.GetActivePage() - 1
2405
2406         elif key == wx.WXK_HOME:
2407             newPage = 0
2408
2409         elif key == wx.WXK_END:
2410             newPage = len(self._pages) - 1
2411
2412         else:
2413             event.Skip()
2414
2415         if newPage != -1:
2416             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, self.GetId())
2417             e.SetSelection(newPage)
2418             e.SetOldSelection(newPage)
2419             e.SetEventObject(self)
2420             self.GetEventHandler().ProcessEvent(e)
2421
2422         else:
2423             event.Skip()
2424
2425
2426 # ----------------------------------------------------------------------
2427
2428 class TabFrame(wx.PyWindow):
2429     """
2430     TabFrame is an interesting case. It's important that all child pages
2431     of the multi-notebook control are all actually children of that control
2432     (and not grandchildren). TabFrame facilitates this. There is one
2433     instance of TabFrame for each tab control inside the multi-notebook.
2434
2435     It's important to know that TabFrame is not a real window, but it merely
2436     used to capture the dimensions/positioning of the internal tab control and
2437     it's managed page windows.
2438     """
2439
2440     def __init__(self, parent):
2441         """
2442         Default class constructor.
2443         Used internally, do not call it in your code!
2444         """
2445
2446         pre = wx.PrePyWindow()
2447
2448         self._tabs = None
2449         self._rect = wx.Rect(0, 0, 200, 200)
2450         self._tab_ctrl_height = 20
2451         self._tab_rect = wx.Rect()
2452         self._parent = parent
2453
2454         self.PostCreate(pre)
2455
2456
2457     def SetTabCtrlHeight(self, h):
2458         """
2459         Sets the tab control height.
2460
2461         :param `h`: the tab area height.
2462         """
2463
2464         self._tab_ctrl_height = h
2465
2466
2467     def DoSetSize(self, x, y, width, height, flags=wx.SIZE_AUTO):
2468         """
2469         Sets the position and size of the window in pixels. The `flags`
2470         parameter indicates the interpretation of the other params if they are
2471         equal to -1.
2472
2473         :param `x`: the window `x` position;
2474         :param `y`: the window `y` position;
2475         :param `width`: the window width;
2476         :param `height`: the window height;
2477         :param `flags`: may have one of this bit set:
2478
2479          ===================================  ======================================
2480          Size Flags                           Description
2481          ===================================  ======================================
2482          ``wx.SIZE_AUTO``                     A -1 indicates that a class-specific default should be used.
2483          ``wx.SIZE_AUTO_WIDTH``               A -1 indicates that a class-specific default should be used for the width.
2484          ``wx.SIZE_AUTO_HEIGHT``              A -1 indicates that a class-specific default should be used for the height.
2485          ``wx.SIZE_USE_EXISTING``             Existing dimensions should be used if -1 values are supplied.
2486          ``wx.SIZE_ALLOW_MINUS_ONE``          Allow dimensions of -1 and less to be interpreted as real dimensions, not default values.
2487          ``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)
2488          ===================================  ======================================
2489
2490         :note: Overridden from `wx.PyControl`.
2491         """
2492
2493         self._rect = wx.Rect(x, y, max(1, width), max(1, height))
2494         self.DoSizing()
2495
2496
2497     def DoGetSize(self):
2498         """
2499         Returns the window size.
2500
2501         :note: Overridden from `wx.PyControl`.
2502         """
2503
2504         return self._rect.width, self._rect.height
2505
2506
2507     def DoGetClientSize(self):
2508         """
2509         Returns the window client size.
2510
2511         :note: Overridden from `wx.PyControl`.
2512         """
2513
2514         return self._rect.width, self._rect.height
2515
2516
2517     def Show(self, show=True):
2518         """
2519         Shows/hides the window.
2520
2521         :param `show`: ``True`` to show the window, ``False`` otherwise.
2522
2523         :note: Overridden from `wx.PyControl`, this method always returns ``False`` as
2524          L{TabFrame} should never be phisically shown on screen.
2525         """
2526
2527         return False
2528
2529
2530     def DoSizing(self):
2531         """ Does the actual sizing of the tab control. """
2532
2533         if not self._tabs:
2534             return
2535
2536         hideOnSingle = ((self._tabs.GetAGWFlags() & AUI_NB_HIDE_ON_SINGLE_TAB) and \
2537                         self._tabs.GetPageCount() <= 1)
2538
2539         if not hideOnSingle and not self._parent._hide_tabs:
2540             tab_height = self._tab_ctrl_height
2541
2542             self._tab_rect = wx.Rect(self._rect.x, self._rect.y, self._rect.width, self._tab_ctrl_height)
2543
2544             if self._tabs.GetAGWFlags() & AUI_NB_BOTTOM:
2545                 self._tab_rect = wx.Rect(self._rect.x, self._rect.y + self._rect.height - tab_height,
2546                                          self._rect.width, tab_height)
2547                 self._tabs.SetDimensions(self._rect.x, self._rect.y + self._rect.height - tab_height,
2548                                          self._rect.width, tab_height)
2549                 self._tabs.SetTabRect(wx.Rect(0, 0, self._rect.width, tab_height))
2550
2551             else:
2552
2553                 self._tab_rect = wx.Rect(self._rect.x, self._rect.y, self._rect.width, tab_height)
2554                 self._tabs.SetDimensions(self._rect.x, self._rect.y, self._rect.width, tab_height)
2555                 self._tabs.SetTabRect(wx.Rect(0, 0, self._rect.width, tab_height))
2556
2557             # TODO: elif (GetAGWFlags() & AUI_NB_LEFT)
2558             # TODO: elif (GetAGWFlags() & AUI_NB_RIGHT)
2559
2560             self._tabs.Refresh()
2561             self._tabs.Update()
2562
2563         else:
2564
2565             tab_height = 0
2566             self._tabs.SetDimensions(self._rect.x, self._rect.y, self._rect.width, tab_height)
2567             self._tabs.SetTabRect(wx.Rect(0, 0, self._rect.width, tab_height))
2568
2569         pages = self._tabs.GetPages()
2570
2571         for page in pages:
2572
2573             height = self._rect.height - tab_height
2574
2575             if height < 0:
2576                 # avoid passing negative height to wx.Window.SetSize(), this
2577                 # results in assert failures/GTK+ warnings
2578                 height = 0
2579
2580             if self._tabs.GetAGWFlags() & AUI_NB_BOTTOM:
2581                 page.window.SetDimensions(self._rect.x, self._rect.y, self._rect.width, height)
2582
2583             else:
2584                 page.window.SetDimensions(self._rect.x, self._rect.y + tab_height,
2585                                           self._rect.width, height)
2586
2587             # TODO: elif (GetAGWFlags() & AUI_NB_LEFT)
2588             # TODO: elif (GetAGWFlags() & AUI_NB_RIGHT)
2589
2590             if repr(page.window.__class__).find("AuiMDIChildFrame") >= 0:
2591                 page.window.ApplyMDIChildFrameRect()
2592
2593
2594     def Update(self):
2595         """
2596         Calling this method immediately repaints the invalidated area of the window
2597         and all of its children recursively while this would usually only happen when
2598         the flow of control returns to the event loop.
2599
2600         :note: Notice that this function doesn't invalidate any area of the window so
2601          nothing happens if nothing has been invalidated (i.e. marked as requiring a redraw).
2602          Use `Refresh` first if you want to immediately redraw the window unconditionally.
2603
2604         :note: Overridden from `wx.PyControl`.
2605         """
2606
2607         # does nothing
2608         pass
2609
2610
2611 # ----------------------------------------------------------------------
2612 # -- AuiNotebook class implementation --
2613
2614 class AuiNotebook(wx.PyPanel):
2615     """
2616     AuiNotebook is a notebook control which implements many features common in
2617     applications with dockable panes. Specifically, AuiNotebook implements functionality
2618     which allows the user to rearrange tab order via drag-and-drop, split the tab window
2619     into many different splitter configurations, and toggle through different themes to
2620     customize the control's look and feel.
2621
2622     An effort has been made to try to maintain an API as similar to that of `wx.Notebook`.
2623
2624     The default theme that is used is L{AuiDefaultTabArt}, which provides a modern, glossy
2625     look and feel. The theme can be changed by calling L{AuiNotebook.SetArtProvider}.
2626     """
2627
2628     def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
2629                  style=0, agwStyle=AUI_NB_DEFAULT_STYLE):
2630         """
2631         Default class constructor.
2632
2633         :param `parent`: the L{AuiNotebook} parent;
2634         :param `id`: an identifier for the control: a value of -1 is taken to mean a default;
2635         :param `pos`: the control position. A value of (-1, -1) indicates a default position,
2636          chosen by either the windowing system or wxPython, depending on platform;
2637         :param `size`: the control size. A value of (-1, -1) indicates a default size,
2638          chosen by either the windowing system or wxPython, depending on platform;
2639         :param `style`: the underlying `wx.PyPanel` window style;
2640         :param `agwStyle`: the AGW-specific window style. This can be a combination of the following bits:
2641
2642          ==================================== ==================================
2643          Flag name                            Description
2644          ==================================== ==================================
2645          ``AUI_NB_TOP``                       With this style, tabs are drawn along the top of the notebook
2646          ``AUI_NB_LEFT``                      With this style, tabs are drawn along the left of the notebook. Not implemented yet.
2647          ``AUI_NB_RIGHT``                     With this style, tabs are drawn along the right of the notebook. Not implemented yet.
2648          ``AUI_NB_BOTTOM``                    With this style, tabs are drawn along the bottom of the notebook
2649          ``AUI_NB_TAB_SPLIT``                 Allows the tab control to be split by dragging a tab
2650          ``AUI_NB_TAB_MOVE``                  Allows a tab to be moved horizontally by dragging
2651          ``AUI_NB_TAB_EXTERNAL_MOVE``         Allows a tab to be moved to another tab control
2652          ``AUI_NB_TAB_FIXED_WIDTH``           With this style, all tabs have the same width
2653          ``AUI_NB_SCROLL_BUTTONS``            With this style, left and right scroll buttons are displayed
2654          ``AUI_NB_WINDOWLIST_BUTTON``         With this style, a drop-down list of windows is available
2655          ``AUI_NB_CLOSE_BUTTON``              With this style, a close button is available on the tab bar
2656          ``AUI_NB_CLOSE_ON_ACTIVE_TAB``       With this style, a close button is available on the active tab
2657          ``AUI_NB_CLOSE_ON_ALL_TABS``         With this style, a close button is available on all tabs
2658          ``AUI_NB_MIDDLE_CLICK_CLOSE``        Allows to close L{AuiNotebook} tabs by mouse middle button click
2659          ``AUI_NB_SUB_NOTEBOOK``              This style is used by L{AuiManager} to create automatic AuiNotebooks
2660          ``AUI_NB_HIDE_ON_SINGLE_TAB``        Hides the tab window if only one tab is present
2661          ``AUI_NB_SMART_TABS``                Use Smart Tabbing, like ``Alt`` + ``Tab`` on Windows
2662          ``AUI_NB_USE_IMAGES_DROPDOWN``       Uses images on dropdown window list menu instead of check items
2663          ``AUI_NB_CLOSE_ON_TAB_LEFT``         Draws the tab close button on the left instead of on the right (a la Camino browser)
2664          ``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
2665          ``AUI_NB_DRAW_DND_TAB``              Draws an image representation of a tab while dragging (on by default)
2666          ``AUI_NB_ORDER_BY_ACCESS``           Tab navigation order by last access time for the tabs
2667          ``AUI_NB_NO_TAB_FOCUS``              Don't draw tab focus rectangle
2668          ==================================== ==================================
2669
2670          Default value for `agwStyle` is:
2671          ``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``
2672
2673         """
2674
2675         self._curpage = -1
2676         self._tab_id_counter = AuiBaseTabCtrlId
2677         self._dummy_wnd = None
2678         self._hide_tabs = False
2679         self._sash_dclick_unsplit = False
2680         self._tab_ctrl_height = 20
2681         self._requested_bmp_size = wx.Size(-1, -1)
2682         self._requested_tabctrl_height = -1
2683         self._textCtrl = None
2684         self._tabBounds = (-1, -1)
2685         self._click_tab = None
2686
2687         wx.PyPanel.__init__(self, parent, id, pos, size, style|wx.BORDER_NONE|wx.TAB_TRAVERSAL)
2688         self._mgr = framemanager.AuiManager()
2689         self._tabs = AuiTabContainer(self)
2690
2691         self.InitNotebook(agwStyle)
2692
2693
2694     def GetTabContainer(self):
2695         """ Returns the instance of L{AuiTabContainer}. """
2696
2697         return self._tabs
2698
2699
2700     def InitNotebook(self, agwStyle):
2701         """
2702         Contains common initialization code called by all constructors.
2703
2704         :param `agwStyle`: the notebook style.
2705
2706         :see: L{__init__}
2707         """
2708
2709         self.SetName("AuiNotebook")
2710         self._agwFlags = agwStyle
2711
2712         self._popupWin = None
2713         self._naviIcon = None
2714         self._imageList = None
2715         self._last_drag_x = 0
2716
2717         self._normal_font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
2718         self._selected_font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
2719         self._selected_font.SetWeight(wx.BOLD)
2720
2721         self.SetArtProvider(TA.AuiDefaultTabArt())
2722
2723         self._dummy_wnd = wx.Window(self, wx.ID_ANY, wx.Point(0, 0), wx.Size(0, 0))
2724         self._dummy_wnd.SetSize((200, 200))
2725         self._dummy_wnd.Show(False)
2726
2727         self._mgr.SetManagedWindow(self)
2728         self._mgr.SetAGWFlags(AUI_MGR_DEFAULT)
2729         self._mgr.SetDockSizeConstraint(1.0, 1.0) # no dock size constraint
2730
2731         self._mgr.AddPane(self._dummy_wnd, framemanager.AuiPaneInfo().Name("dummy").Bottom().CaptionVisible(False).Show(False))
2732         self._mgr.Update()
2733
2734         self.Bind(wx.EVT_SIZE, self.OnSize)
2735         self.Bind(wx.EVT_CHILD_FOCUS, self.OnChildFocusNotebook)
2736         self.Bind(EVT_AUINOTEBOOK_PAGE_CHANGING, self.OnTabClicked,
2737                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2738         self.Bind(EVT_AUINOTEBOOK_BEGIN_DRAG, self.OnTabBeginDrag,
2739                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2740         self.Bind(EVT_AUINOTEBOOK_END_DRAG, self.OnTabEndDrag,
2741                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2742         self.Bind(EVT_AUINOTEBOOK_DRAG_MOTION, self.OnTabDragMotion,
2743                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2744         self.Bind(EVT_AUINOTEBOOK_CANCEL_DRAG, self.OnTabCancelDrag,
2745                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2746         self.Bind(EVT_AUINOTEBOOK_BUTTON, self.OnTabButton,
2747                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2748         self.Bind(EVT_AUINOTEBOOK_TAB_MIDDLE_DOWN, self.OnTabMiddleDown,
2749                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2750         self.Bind(EVT_AUINOTEBOOK_TAB_MIDDLE_UP, self.OnTabMiddleUp,
2751                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2752         self.Bind(EVT_AUINOTEBOOK_TAB_RIGHT_DOWN, self.OnTabRightDown,
2753                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2754         self.Bind(EVT_AUINOTEBOOK_TAB_RIGHT_UP, self.OnTabRightUp,
2755                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2756         self.Bind(EVT_AUINOTEBOOK_BG_DCLICK, self.OnTabBgDClick,
2757                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2758         self.Bind(EVT_AUINOTEBOOK_TAB_DCLICK, self.OnTabDClick,
2759                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2760
2761         self.Bind(wx.EVT_NAVIGATION_KEY, self.OnNavigationKeyNotebook)
2762
2763
2764     def SetArtProvider(self, art):
2765         """
2766         Sets the art provider to be used by the notebook.
2767
2768         :param `art`: an art provider.
2769         """
2770
2771         self._tabs.SetArtProvider(art)
2772         self.UpdateTabCtrlHeight(force=True)
2773
2774
2775     def SavePerspective(self):
2776         """
2777         Saves the entire user interface layout into an encoded string, which can then
2778         be stored by the application (probably using `wx.Config`). When a perspective
2779         is restored using L{LoadPerspective}, the entire user interface will return
2780         to the state it was when the perspective was saved.
2781         """
2782
2783         # Build list of panes/tabs
2784         tabs = ""
2785         all_panes = self._mgr.GetAllPanes()
2786
2787         for pane in all_panes:
2788
2789             if pane.name == "dummy":
2790                 continue
2791
2792             tabframe = pane.window
2793
2794             if tabs:
2795                 tabs += "|"
2796
2797             tabs += pane.name + "="
2798
2799             # add tab id's
2800             page_count = tabframe._tabs.GetPageCount()
2801
2802             for p in xrange(page_count):
2803
2804                 page = tabframe._tabs.GetPage(p)
2805                 page_idx = self._tabs.GetIdxFromWindow(page.window)
2806
2807                 if p:
2808                     tabs += ","
2809
2810                 if p == tabframe._tabs.GetActivePage():
2811                     tabs += "+"
2812                 elif page_idx == self._curpage:
2813                     tabs += "*"
2814
2815                 tabs += "%u"%page_idx
2816
2817         tabs += "@"
2818
2819         # Add frame perspective
2820         tabs += self._mgr.SavePerspective()
2821
2822         return tabs
2823
2824
2825     def LoadPerspective(self, layout):
2826         """
2827         Loads a layout which was saved with L{SavePerspective}.
2828
2829         :param `layout`: a string which contains a saved L{AuiNotebook} layout.
2830         """
2831
2832         # Remove all tab ctrls (but still keep them in main index)
2833         tab_count = self._tabs.GetPageCount()
2834         for i in xrange(tab_count):
2835             wnd = self._tabs.GetWindowFromIdx(i)
2836
2837             # find out which onscreen tab ctrl owns this tab
2838             ctrl, ctrl_idx = self.FindTab(wnd)
2839             if not ctrl:
2840                 return False
2841
2842             # remove the tab from ctrl
2843             if not ctrl.RemovePage(wnd):
2844                 return False
2845
2846         self.RemoveEmptyTabFrames()
2847
2848         sel_page = 0
2849         tabs = layout[0:layout.index("@")]
2850         to_break1 = False
2851
2852         while 1:
2853
2854             if "|" not in tabs:
2855                 to_break1 = True
2856                 tab_part = tabs
2857             else:
2858                 tab_part = tabs[0:tabs.index('|')]
2859
2860             if "=" not in tab_part:
2861                 # No pages in this perspective...
2862                 return False
2863
2864             # Get pane name
2865             pane_name = tab_part[0:tab_part.index("=")]
2866
2867             # create a new tab frame
2868             new_tabs = TabFrame(self)
2869             self._tab_id_counter += 1
2870             new_tabs._tabs = AuiTabCtrl(self, self._tab_id_counter)
2871             new_tabs._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone())
2872             new_tabs.SetTabCtrlHeight(self._tab_ctrl_height)
2873             new_tabs._tabs.SetAGWFlags(self._agwFlags)
2874             dest_tabs = new_tabs._tabs
2875
2876             # create a pane info structure with the information
2877             # about where the pane should be added
2878             pane_info = framemanager.AuiPaneInfo().Name(pane_name).Bottom().CaptionVisible(False)
2879             self._mgr.AddPane(new_tabs, pane_info)
2880
2881             # Get list of tab id's and move them to pane
2882             tab_list = tab_part[tab_part.index("=")+1:]
2883             to_break2, active_found = False, False
2884
2885             while 1:
2886                 if "," not in tab_list:
2887                     to_break2 = True
2888                     tab = tab_list
2889                 else:
2890                     tab = tab_list[0:tab_list.index(",")]
2891                     tab_list = tab_list[tab_list.index(",")+1:]
2892
2893                 # Check if this page has an 'active' marker
2894                 c = tab[0]
2895                 if c in ['+', '*']:
2896                     tab = tab[1:]
2897
2898                 tab_idx = int(tab)
2899                 if tab_idx >= self.GetPageCount():
2900                     to_break1 = True
2901                     break
2902
2903                 # Move tab to pane
2904                 page = self._tabs.GetPage(tab_idx)
2905                 newpage_idx = dest_tabs.GetPageCount()
2906                 dest_tabs.InsertPage(page.window, page, newpage_idx)
2907
2908                 if c == '+':
2909                     dest_tabs.SetActivePage(newpage_idx)
2910                     active_found = True
2911                 elif c == '*':
2912                     sel_page = tab_idx
2913
2914                 if to_break2:
2915                     break
2916
2917             if not active_found:
2918                 dest_tabs.SetActivePage(0)
2919
2920             new_tabs.DoSizing()
2921             dest_tabs.DoShowHide()
2922             dest_tabs.Refresh()
2923
2924             if to_break1:
2925                 break
2926
2927             tabs = tabs[tabs.index('|')+1:]
2928
2929         # Load the frame perspective
2930         frames = layout[layout.index('@')+1:]
2931         self._mgr.LoadPerspective(frames)
2932
2933         # Force refresh of selection
2934         self._curpage = -1
2935         self.SetSelection(sel_page)
2936
2937         return True
2938
2939
2940     def SetTabCtrlHeight(self, height):
2941         """
2942         Sets the tab height.
2943
2944         By default, the tab control height is calculated by measuring the text
2945         height and bitmap sizes on the tab captions.
2946
2947         Calling this method will override that calculation and set the tab control
2948         to the specified height parameter. A call to this method will override
2949         any call to L{SetUniformBitmapSize}. Specifying -1 as the height will
2950         return the control to its default auto-sizing behaviour.
2951
2952         :param `height`: the tab control area height.
2953         """
2954
2955         self._requested_tabctrl_height = height
2956
2957         # if window is already initialized, recalculate the tab height
2958         if self._dummy_wnd:
2959             self.UpdateTabCtrlHeight()
2960
2961
2962     def SetUniformBitmapSize(self, size):
2963         """
2964         Ensures that all tabs will have the same height, even if some tabs
2965         don't have bitmaps. Passing ``wx.DefaultSize`` to this
2966         function will instruct the control to use dynamic tab height, which is
2967         the default behaviour. Under the default behaviour, when a tab with a
2968         large bitmap is added, the tab control's height will automatically
2969         increase to accommodate the larger bitmap.
2970
2971         :param `size`: an instance of `wx.Size` specifying the tab bitmap size.
2972         """
2973
2974         self._requested_bmp_size = wx.Size(*size)
2975
2976         # if window is already initialized, recalculate the tab height
2977         if self._dummy_wnd:
2978             self.UpdateTabCtrlHeight()
2979
2980
2981     def UpdateTabCtrlHeight(self, force=False):
2982         """
2983         UpdateTabCtrlHeight() does the actual tab resizing. It's meant
2984         to be used interally.
2985
2986         :param `force`: ``True`` to force the tab art to repaint.
2987         """
2988
2989         # get the tab ctrl height we will use
2990         height = self.CalculateTabCtrlHeight()
2991
2992         # if the tab control height needs to change, update
2993         # all of our tab controls with the new height
2994         if self._tab_ctrl_height != height or force:
2995             art = self._tabs.GetArtProvider()
2996
2997             self._tab_ctrl_height = height
2998
2999             all_panes = self._mgr.GetAllPanes()
3000             for pane in all_panes:
3001
3002                 if pane.name == "dummy":
3003                     continue
3004
3005                 tab_frame = pane.window
3006                 tabctrl = tab_frame._tabs
3007                 tab_frame.SetTabCtrlHeight(self._tab_ctrl_height)
3008                 tabctrl.SetArtProvider(art.Clone())
3009                 tab_frame.DoSizing()
3010
3011
3012     def UpdateHintWindowSize(self):
3013         """ Updates the L{AuiManager} hint window size. """
3014
3015         size = self.CalculateNewSplitSize()
3016
3017         # the placeholder hint window should be set to this size
3018         info = self._mgr.GetPane("dummy")
3019
3020         if info.IsOk():
3021             info.MinSize(size)
3022             info.BestSize(size)
3023             self._dummy_wnd.SetSize(size)
3024
3025
3026     def CalculateNewSplitSize(self):
3027         """ Calculates the size of the new split. """
3028
3029         # count number of tab controls
3030         tab_ctrl_count = 0
3031         all_panes = self._mgr.GetAllPanes()
3032
3033         for pane in all_panes:
3034             if pane.name == "dummy":
3035                 continue
3036
3037             tab_ctrl_count += 1
3038
3039         # if there is only one tab control, the first split
3040         # should happen around the middle
3041         if tab_ctrl_count < 2:
3042             new_split_size = self.GetClientSize()
3043             new_split_size.x /= 2
3044             new_split_size.y /= 2
3045
3046         else:
3047
3048             # this is in place of a more complicated calculation
3049             # that needs to be implemented
3050             new_split_size = wx.Size(180, 180)
3051
3052         return new_split_size
3053
3054
3055     def CalculateTabCtrlHeight(self):
3056         """ Calculates the tab control area height. """
3057
3058         # if a fixed tab ctrl height is specified,
3059         # just return that instead of calculating a
3060         # tab height
3061         if self._requested_tabctrl_height != -1:
3062             return self._requested_tabctrl_height
3063
3064         # find out new best tab height
3065         art = self._tabs.GetArtProvider()
3066
3067         return art.GetBestTabCtrlSize(self, self._tabs.GetPages(), self._requested_bmp_size)
3068
3069
3070     def GetArtProvider(self):
3071         """ Returns the associated art provider. """
3072
3073         return self._tabs.GetArtProvider()
3074
3075
3076     def SetAGWWindowStyleFlag(self, agwStyle):
3077         """
3078         Sets the AGW-specific style of the window.
3079
3080         :param `agwStyle`: the new window style. This can be a combination of the following bits:
3081
3082          ==================================== ==================================
3083          Flag name                            Description
3084          ==================================== ==================================
3085          ``AUI_NB_TOP``                       With this style, tabs are drawn along the top of the notebook
3086          ``AUI_NB_LEFT``                      With this style, tabs are drawn along the left of the notebook. Not implemented yet.
3087          ``AUI_NB_RIGHT``                     With this style, tabs are drawn along the right of the notebook. Not implemented yet.
3088          ``AUI_NB_BOTTOM``                    With this style, tabs are drawn along the bottom of the notebook
3089          ``AUI_NB_TAB_SPLIT``                 Allows the tab control to be split by dragging a tab
3090          ``AUI_NB_TAB_MOVE``                  Allows a tab to be moved horizontally by dragging
3091          ``AUI_NB_TAB_EXTERNAL_MOVE``         Allows a tab to be moved to another tab control
3092          ``AUI_NB_TAB_FIXED_WIDTH``           With this style, all tabs have the same width
3093          ``AUI_NB_SCROLL_BUTTONS``            With this style, left and right scroll buttons are displayed
3094          ``AUI_NB_WINDOWLIST_BUTTON``         With this style, a drop-down list of windows is available
3095          ``AUI_NB_CLOSE_BUTTON``              With this style, a close button is available on the tab bar
3096          ``AUI_NB_CLOSE_ON_ACTIVE_TAB``       With this style, a close button is available on the active tab
3097          ``AUI_NB_CLOSE_ON_ALL_TABS``         With this style, a close button is available on all tabs
3098          ``AUI_NB_MIDDLE_CLICK_CLOSE``        Allows to close L{AuiNotebook} tabs by mouse middle button click
3099          ``AUI_NB_SUB_NOTEBOOK``              This style is used by L{AuiManager} to create automatic AuiNotebooks
3100          ``AUI_NB_HIDE_ON_SINGLE_TAB``        Hides the tab window if only one tab is present
3101          ``AUI_NB_SMART_TABS``                Use Smart Tabbing, like ``Alt`` + ``Tab`` on Windows
3102          ``AUI_NB_USE_IMAGES_DROPDOWN``       Uses images on dropdown window list menu instead of check items
3103          ``AUI_NB_CLOSE_ON_TAB_LEFT``         Draws the tab close button on the left instead of on the right (a la Camino browser)
3104          ``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
3105          ``AUI_NB_DRAW_DND_TAB``              Draws an image representation of a tab while dragging (on by default)
3106          ``AUI_NB_ORDER_BY_ACCESS``           Tab navigation order by last access time for the tabs
3107          ``AUI_NB_NO_TAB_FOCUS``              Don't draw tab focus rectangle
3108          ==================================== ==================================
3109
3110         :note: Please note that some styles cannot be changed after the window
3111          creation and that `Refresh` might need to be be called after changing the
3112          others for the change to take place immediately.
3113
3114         :todo: Implementation of flags ``AUI_NB_RIGHT`` and ``AUI_NB_LEFT``.
3115         """
3116
3117         self._agwFlags = agwStyle
3118
3119         # if the control is already initialized
3120         if self._mgr.GetManagedWindow() == self:
3121
3122             # let all of the tab children know about the new style
3123
3124             all_panes = self._mgr.GetAllPanes()
3125             for pane in all_panes:
3126                 if pane.name == "dummy":
3127                     continue
3128
3129                 tabframe = pane.window
3130                 tabctrl = tabframe._tabs
3131                 tabctrl.SetAGWFlags(self._agwFlags)
3132                 tabframe.DoSizing()
3133                 tabctrl.Refresh()
3134                 tabctrl.Update()
3135
3136
3137     def GetAGWWindowStyleFlag(self):
3138         """
3139         Returns the AGW-specific style of the window.
3140
3141         :see: L{SetAGWWindowStyleFlag} for a list of possible AGW-specific window styles.
3142         """
3143
3144         return self._agwFlags
3145
3146
3147     def AddPage(self, page, caption, select=False, bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap, control=None):
3148         """
3149         Adds a page. If the `select` parameter is ``True``, calling this will generate a
3150         page change event.
3151
3152         :param `page`: the page to be added;
3153         :param `caption`: specifies the text for the new page;
3154         :param `select`: specifies whether the page should be selected;
3155         :param `bitmap`: the `wx.Bitmap` to display in the enabled tab;
3156         :param `disabled_bitmap`: the `wx.Bitmap` to display in the disabled tab;
3157         :param `control`: a `wx.Window` instance inside a tab (or ``None``).
3158         """
3159
3160         return self.InsertPage(self.GetPageCount(), page, caption, select, bitmap, disabled_bitmap, control)
3161
3162
3163     def InsertPage(self, page_idx, page, caption, select=False, bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap,
3164                    control=None):
3165         """
3166         This is similar to L{AddPage}, but allows the ability to specify the insert location.
3167
3168         :param `page_idx`: specifies the position for the new page;
3169         :param `page`: the page to be added;
3170         :param `caption`: specifies the text for the new page;
3171         :param `select`: specifies whether the page should be selected;
3172         :param `bitmap`: the `wx.Bitmap` to display in the enabled tab;
3173         :param `disabled_bitmap`: the `wx.Bitmap` to display in the disabled tab;
3174         :param `control`: a `wx.Window` instance inside a tab (or ``None``).
3175         """
3176
3177         if not page:
3178             return False
3179
3180         page.Reparent(self)
3181         info = AuiNotebookPage()
3182         info.window = page
3183         info.caption = caption
3184         info.bitmap = bitmap
3185         info.active = False
3186         info.control = control
3187
3188         originalPaneMgr = framemanager.GetManager(page)
3189         if originalPaneMgr:
3190             originalPane = originalPaneMgr.GetPane(page)
3191
3192             if originalPane:
3193                 info.hasCloseButton = originalPane.HasCloseButton()
3194
3195         if bitmap.IsOk() and not disabled_bitmap.IsOk():
3196             disabled_bitmap = MakeDisabledBitmap(bitmap)
3197             info.dis_bitmap = disabled_bitmap
3198
3199         # if there are currently no tabs, the first added
3200         # tab must be active
3201         if self._tabs.GetPageCount() == 0:
3202             info.active = True
3203
3204         self._tabs.InsertPage(page, info, page_idx)
3205
3206         # if that was the first page added, even if
3207         # select is False, it must become the "current page"
3208         # (though no select events will be fired)
3209         if not select and self._tabs.GetPageCount() == 1:
3210             select = True
3211
3212         active_tabctrl = self.GetActiveTabCtrl()
3213         if page_idx >= active_tabctrl.GetPageCount():
3214             active_tabctrl.AddPage(page, info)
3215         else:
3216             active_tabctrl.InsertPage(page, info, page_idx)
3217
3218         force = False
3219         if control:
3220             force = True
3221             control.Reparent(active_tabctrl)
3222             control.Show()
3223
3224         self.UpdateTabCtrlHeight(force=force)
3225         self.DoSizing()
3226         active_tabctrl.DoShowHide()
3227
3228         # adjust selected index
3229         if self._curpage >= page_idx:
3230             self._curpage += 1
3231
3232         if select:
3233             self.SetSelectionToWindow(page)
3234
3235         return True
3236
3237
3238     def DeletePage(self, page_idx):
3239         """
3240         Deletes a page at the given index. Calling this method will generate a page
3241         change event.
3242
3243         :param `page_idx`: the page index to be deleted.
3244
3245         :note: L{DeletePage} removes a tab from the multi-notebook, and destroys the window as well.
3246
3247         :see: L{RemovePage}
3248         """
3249
3250         if page_idx >= self._tabs.GetPageCount():
3251             return False
3252
3253         wnd = self._tabs.GetWindowFromIdx(page_idx)
3254         # hide the window in advance, as this will
3255         # prevent flicker
3256         wnd.Show(False)
3257
3258         self.RemoveControlFromPage(page_idx)
3259
3260         if not self.RemovePage(page_idx):
3261             return False
3262
3263         wnd.Destroy()
3264
3265         return True
3266
3267
3268     def RemovePage(self, page_idx):
3269         """
3270         Removes a page, without deleting the window pointer.
3271
3272         :param `page_idx`: the page index to be removed.
3273
3274         :note: L{RemovePage} removes a tab from the multi-notebook, but does not destroy the window.
3275
3276         :see: L{DeletePage}
3277         """
3278
3279         # save active window pointer
3280         active_wnd = None
3281         if self._curpage >= 0:
3282             active_wnd = self._tabs.GetWindowFromIdx(self._curpage)
3283
3284         # save pointer of window being deleted
3285         wnd = self._tabs.GetWindowFromIdx(page_idx)
3286         new_active = None
3287
3288         # make sure we found the page
3289         if not wnd:
3290             return False
3291
3292         # find out which onscreen tab ctrl owns this tab
3293         ctrl, ctrl_idx = self.FindTab(wnd)
3294         if not ctrl:
3295             return False
3296
3297         currentPage = ctrl.GetPage(ctrl_idx)
3298         is_curpage = (self._curpage == page_idx)
3299         is_active_in_split = currentPage.active
3300
3301         # remove the tab from main catalog
3302         if not self._tabs.RemovePage(wnd):
3303             return False
3304
3305         # remove the tab from the onscreen tab ctrl
3306         ctrl.RemovePage(wnd)
3307
3308         if is_active_in_split:
3309
3310             ctrl_new_page_count = ctrl.GetPageCount()
3311
3312             if ctrl_idx >= ctrl_new_page_count:
3313                 ctrl_idx = ctrl_new_page_count - 1
3314
3315             if ctrl_idx >= 0 and ctrl_idx < ctrl.GetPageCount():
3316
3317                 ctrl_idx = self.FindNextActiveTab(ctrl_idx, ctrl)
3318
3319                 # set new page as active in the tab split
3320                 ctrl.SetActivePage(ctrl_idx)
3321
3322                 # if the page deleted was the current page for the
3323                 # entire tab control, then record the window
3324                 # pointer of the new active page for activation
3325                 if is_curpage:
3326                     new_active = ctrl.GetWindowFromIdx(ctrl_idx)
3327
3328         else:
3329
3330             # we are not deleting the active page, so keep it the same
3331             new_active = active_wnd
3332
3333         if not new_active:
3334
3335             # we haven't yet found a new page to active,
3336             # so select the next page from the main tab
3337             # catalogue
3338
3339             if 0 <= page_idx < self._tabs.GetPageCount():
3340                 new_active = self._tabs.GetPage(page_idx).window
3341             if not new_active and self._tabs.GetPageCount() > 0:
3342                 new_active = self._tabs.GetPage(0).window
3343
3344         self.RemoveEmptyTabFrames()
3345
3346         # set new active pane
3347         if new_active:
3348             if not self.IsBeingDeleted():
3349                 self._curpage = -1
3350                 self.SetSelectionToWindow(new_active)
3351         else:
3352             self._curpage = -1
3353             self._tabs.SetNoneActive()
3354
3355         return True
3356
3357
3358     def FindNextActiveTab(self, ctrl_idx, ctrl):
3359         """
3360         Finds the next active tab (used mainly when L{AuiNotebook} has inactive/disabled
3361         tabs in it).
3362
3363         :param `ctrl_idx`: the index of the first (most obvious) tab to check for active status;
3364         :param `ctrl`: an instance of L{AuiTabCtrl}.
3365         """
3366
3367         if self.GetEnabled(ctrl_idx):
3368             return ctrl_idx
3369
3370         for indx in xrange(ctrl_idx, ctrl.GetPageCount()):
3371             if self.GetEnabled(indx):
3372                 return indx
3373
3374         for indx in xrange(ctrl_idx, -1, -1):
3375             if self.GetEnabled(indx):
3376                 return indx
3377
3378         return 0
3379
3380
3381     def HideAllTabs(self, hidden=True):
3382         """
3383         Hides all tabs on the L{AuiNotebook} control.
3384
3385         :param `hidden`: if ``True`` hides all tabs.
3386         """
3387
3388         self._hide_tabs = hidden
3389
3390
3391     def SetSashDClickUnsplit(self, unsplit=True):
3392         """
3393         Sets whether to unsplit a splitted L{AuiNotebook} when double-clicking on a sash.
3394
3395         :param `unsplit`: ``True`` to unsplit on sash double-clicking, ``False`` otherwise.
3396         """
3397
3398         self._sash_dclick_unsplit = unsplit
3399
3400
3401     def GetSashDClickUnsplit(self):
3402         """
3403         Returns whether a splitted L{AuiNotebook} can be unsplitted by double-clicking
3404         on the splitter sash.
3405         """
3406
3407         return self._sash_dclick_unsplit
3408
3409
3410     def SetMinMaxTabWidth(self, minTabWidth, maxTabWidth):
3411         """
3412         Sets the minimum and/or the maximum tab widths for L{AuiNotebook} when the
3413         ``AUI_NB_TAB_FIXED_WIDTH`` style is defined.
3414
3415         Pass -1 to either `minTabWidth` or `maxTabWidth` to reset to the default tab
3416         width behaviour for L{AuiNotebook}.
3417
3418         :param `minTabWidth`: the minimum allowed tab width, in pixels;
3419         :param `maxTabWidth`: the maximum allowed tab width, in pixels.
3420
3421         :note: Minimum and maximum tabs widths are used only when the ``AUI_NB_TAB_FIXED_WIDTH``
3422          style is present.
3423         """
3424
3425         if minTabWidth > maxTabWidth:
3426             raise Exception("Minimum tab width must be less or equal than maximum tab width")
3427
3428         self._tabBounds = (minTabWidth, maxTabWidth)
3429         self.SetAGWWindowStyleFlag(self._agwFlags)
3430
3431
3432     def GetMinMaxTabWidth(self):
3433         """
3434         Returns the minimum and the maximum tab widths for L{AuiNotebook} when the
3435         ``AUI_NB_TAB_FIXED_WIDTH`` style is defined.
3436
3437         :note: Minimum and maximum tabs widths are used only when the ``AUI_NB_TAB_FIXED_WIDTH``
3438          style is present.
3439
3440         :see: L{SetMinMaxTabWidth} for more information.
3441         """
3442
3443         return self._tabBounds
3444
3445
3446     def GetPageIndex(self, page_wnd):
3447         """
3448         Returns the page index for the specified window. If the window is not
3449         found in the notebook, ``wx.NOT_FOUND`` is returned.
3450         """
3451
3452         return self._tabs.GetIdxFromWindow(page_wnd)
3453
3454
3455     def SetPageText(self, page_idx, text):
3456         """
3457         Sets the tab label for the page.
3458
3459         :param `page_idx`: the page index;
3460         :param `text`: the new tab label.
3461         """
3462
3463         if page_idx >= self._tabs.GetPageCount():
3464             return False
3465
3466         # update our own tab catalog
3467         page_info = self._tabs.GetPage(page_idx)
3468         should_refresh = page_info.caption != text
3469         page_info.caption = text
3470
3471         # update what's on screen
3472         ctrl, ctrl_idx = self.FindTab(page_info.window)
3473         if not ctrl:
3474             return False
3475
3476         info = ctrl.GetPage(ctrl_idx)
3477         should_refresh = should_refresh or info.caption != text
3478         info.caption = text
3479
3480         if should_refresh:
3481             ctrl.Refresh()
3482             ctrl.Update()
3483
3484         self.UpdateTabCtrlHeight(force=True)
3485
3486         return True
3487
3488
3489     def GetPageText(self, page_idx):
3490         """
3491         Returns the tab label for the page.
3492
3493         :param `page_idx`: the page index.
3494         """
3495
3496         if page_idx >= self._tabs.GetPageCount():
3497             return ""
3498
3499         # update our own tab catalog
3500         page_info = self._tabs.GetPage(page_idx)
3501         return page_info.caption
3502
3503
3504     def SetPageBitmap(self, page_idx, bitmap):
3505         """
3506         Sets the tab bitmap for the page.
3507
3508         :param `page_idx`: the page index;
3509         :param `bitmap`: an instance of `wx.Bitmap`.
3510         """
3511
3512         if page_idx >= self._tabs.GetPageCount():
3513             return False
3514
3515         # update our own tab catalog
3516         page_info = self._tabs.GetPage(page_idx)
3517         should_refresh = page_info.bitmap is not bitmap
3518         page_info.bitmap = bitmap
3519         if bitmap.IsOk() and not page_info.dis_bitmap.IsOk():
3520             page_info.dis_bitmap = MakeDisabledBitmap(bitmap)
3521
3522         # tab height might have changed
3523         self.UpdateTabCtrlHeight()
3524
3525         # update what's on screen
3526         ctrl, ctrl_idx = self.FindTab(page_info.window)
3527         if not ctrl:
3528             return False
3529
3530         info = ctrl.GetPage(ctrl_idx)
3531         should_refresh = should_refresh or info.bitmap is not bitmap
3532         info.bitmap = bitmap
3533         info.dis_bitmap = page_info.dis_bitmap
3534         if should_refresh:
3535             ctrl.Refresh()
3536             ctrl.Update()
3537
3538         return True
3539
3540
3541     def GetPageBitmap(self, page_idx):
3542         """
3543         Returns the tab bitmap for the page.
3544
3545         :param `page_idx`: the page index.
3546         """
3547
3548         if page_idx >= self._tabs.GetPageCount():
3549             return wx.NullBitmap
3550
3551         # update our own tab catalog
3552         page_info = self._tabs.GetPage(page_idx)
3553         return page_info.bitmap
3554
3555
3556     def SetImageList(self, imageList):
3557         """
3558         Sets the image list for the L{AuiNotebook} control.
3559
3560         :param `imageList`: an instance of `wx.ImageList`.
3561         """
3562
3563         self._imageList = imageList
3564
3565
3566     def AssignImageList(self, imageList):
3567         """
3568         Sets the image list for the L{AuiNotebook} control.
3569
3570         :param `imageList`: an instance of `wx.ImageList`.
3571         """
3572
3573         self.SetImageList(imageList)
3574
3575
3576     def GetImageList(self):
3577         """ Returns the associated image list (if any). """
3578
3579         return self._imageList
3580
3581
3582     def SetPageImage(self, page, image):
3583         """
3584         Sets the image index for the given page.
3585
3586         :param `page`: the page index;
3587         :param `image`: an index into the image list which was set with L{SetImageList}.
3588         """
3589
3590         if page >= self._tabs.GetPageCount():
3591             return False
3592
3593         if not isinstance(image, types.IntType):
3594             raise Exception("The image parameter must be an integer, you passed " \
3595                             "%s"%repr(image))
3596
3597         if not self._imageList:
3598             raise Exception("To use SetPageImage you need to associate an image list " \
3599                             "Using SetImageList or AssignImageList")
3600
3601         if image >= self._imageList.GetImageCount():
3602             raise Exception("Invalid image index (%d), the image list contains only" \
3603                             " (%d) bitmaps"%(image, self._imageList.GetImageCount()))
3604
3605         if image == -1:
3606             self.SetPageBitmap(page, wx.NullBitmap)
3607             return
3608
3609         bitmap = self._imageList.GetBitmap(image)
3610         self.SetPageBitmap(page, bitmap)
3611
3612
3613     def GetPageImage(self, page):
3614         """
3615         Returns the image index for the given page.
3616
3617         :param `page`: the given page for which to retrieve the image index.
3618         """
3619
3620         if page >= self._tabs.GetPageCount():
3621             return False
3622
3623         bitmap = self.GetPageBitmap(page)
3624         for indx in xrange(self._imageList.GetImageCount()):
3625             imgListBmp = self._imageList.GetBitmap(indx)
3626             if imgListBmp == bitmap:
3627                 return indx
3628
3629         return wx.NOT_FOUND
3630
3631
3632     def SetPageTextColour(self, page_idx, colour):
3633         """
3634         Sets the tab text colour for the page.
3635
3636         :param `page_idx`: the page index;
3637         :param `colour`: an instance of `wx.Colour`.
3638         """
3639
3640         if page_idx >= self._tabs.GetPageCount():
3641             return False
3642
3643         # update our own tab catalog
3644         page_info = self._tabs.GetPage(page_idx)
3645         should_refresh = page_info.text_colour != colour
3646         page_info.text_colour = colour
3647
3648         # update what's on screen
3649         ctrl, ctrl_idx = self.FindTab(page_info.window)
3650         if not ctrl:
3651             return False
3652
3653         info = ctrl.GetPage(ctrl_idx)
3654         should_refresh = should_refresh or info.text_colour != colour
3655         info.text_colour = page_info.text_colour
3656
3657         if should_refresh:
3658             ctrl.Refresh()
3659             ctrl.Update()
3660
3661         return True
3662
3663
3664     def GetPageTextColour(self, page_idx):
3665         """
3666         Returns the tab text colour for the page.
3667
3668         :param `page_idx`: the page index.
3669         """
3670
3671         if page_idx >= self._tabs.GetPageCount():
3672             return wx.NullColour
3673
3674         # update our own tab catalog
3675         page_info = self._tabs.GetPage(page_idx)
3676         return page_info.text_colour
3677
3678
3679     def AddControlToPage(self, page_idx, control):
3680         """
3681         Adds a control inside a tab (not in the tab area).
3682
3683         :param `page_idx`: the page index;
3684         :param `control`: an instance of `wx.Window`.
3685         """
3686
3687         if page_idx >= self._tabs.GetPageCount():
3688             return False
3689
3690         # update our own tab catalog
3691         page_info = self._tabs.GetPage(page_idx)
3692         page_info.control = control
3693
3694         # tab height might have changed
3695         self.UpdateTabCtrlHeight(force=True)
3696
3697         # update what's on screen
3698         ctrl, ctrl_idx = self.FindTab(page_info.window)
3699         if not ctrl:
3700             return False
3701
3702         control.Reparent(ctrl)
3703
3704         info = ctrl.GetPage(ctrl_idx)
3705         info.control = control
3706         ctrl.Refresh()
3707         ctrl.Update()
3708
3709         return True
3710
3711
3712     def RemoveControlFromPage(self, page_idx):
3713         """
3714         Removes a control from a tab (not from the tab area).
3715
3716         :param `page_idx`: the page index.
3717         """
3718
3719         if page_idx >= self._tabs.GetPageCount():
3720             return False
3721
3722         page_info = self._tabs.GetPage(page_idx)
3723         if page_info.control is None:
3724             return False
3725
3726         page_info.control.Destroy()
3727         page_info.control = None
3728
3729         # tab height might have changed
3730         self.UpdateTabCtrlHeight(force=True)
3731
3732         # update what's on screen
3733         ctrl, ctrl_idx = self.FindTab(page_info.window)
3734         if not ctrl:
3735             return False
3736
3737         info = ctrl.GetPage(ctrl_idx)
3738         info.control = None
3739         ctrl.Refresh()
3740         ctrl.Update()
3741
3742         return True
3743
3744
3745     def SetCloseButton(self, page_idx, hasCloseButton):
3746         """
3747         Sets whether a tab should display a close button or not.
3748
3749         :param `page_idx`: the page index;
3750         :param `hasCloseButton`: ``True`` if the page displays a close button.
3751
3752         :note: This can only be called if ``AUI_NB_CLOSE_ON_ALL_TABS`` is specified.
3753         """
3754
3755         if page_idx >= self._tabs.GetPageCount():
3756             return False
3757
3758         if self._agwFlags & AUI_NB_CLOSE_ON_ALL_TABS == 0:
3759             raise Exception("SetCloseButton can only be used with AUI_NB_CLOSE_ON_ALL_TABS style.")
3760
3761         # update our own tab catalog
3762         page_info = self._tabs.GetPage(page_idx)
3763         page_info.hasCloseButton = hasCloseButton
3764
3765         # update what's on screen
3766         ctrl, ctrl_idx = self.FindTab(page_info.window)
3767         if not ctrl:
3768             return False
3769
3770         info = ctrl.GetPage(ctrl_idx)
3771         info.hasCloseButton = page_info.hasCloseButton
3772         ctrl.Refresh()
3773         ctrl.Update()
3774
3775         return True
3776
3777
3778     def HasCloseButton(self, page_idx):
3779         """
3780         Returns whether a tab displays a close button or not.
3781
3782         :param `page_idx`: the page index.
3783
3784         :note: This can only be called if ``AUI_NB_CLOSE_ON_ALL_TABS`` is specified.
3785         """
3786
3787         if page_idx >= self._tabs.GetPageCount():
3788             return False
3789
3790         page_info = self._tabs.GetPage(page_idx)
3791         return page_info.hasCloseButton
3792
3793
3794     def GetSelection(self):
3795         """ Returns the index of the currently active page, or -1 if none was selected. """
3796
3797         return self._curpage
3798
3799
3800     def GetCurrentPage(self):
3801         """ Returns the currently active page (not the index), or ``None`` if none was selected. """
3802
3803         if self._curpage >= 0 and self._curpage < self._tabs.GetPageCount():
3804             return self.GetPage(self._curpage)
3805
3806         return None
3807
3808
3809     def EnsureVisible(self, indx):
3810         """
3811         Ensures the input page index `indx` is visible.
3812
3813         :param `indx`: the page index.
3814         """
3815
3816         self._tabs.MakeTabVisible(indx, self)
3817
3818
3819     def SetSelection(self, new_page, force=False):
3820         """
3821         Sets the page selection. Calling this method will generate a page change event.
3822
3823         :param `new_page`: the index of the new selection;
3824         :param `force`: whether to force the selection or not.
3825         """
3826         wnd = self._tabs.GetWindowFromIdx(new_page)
3827
3828         #Update page access time
3829         self._tabs.GetPages()[new_page].access_time = datetime.datetime.now()
3830
3831         if not wnd or not self.GetEnabled(new_page):
3832             return self._curpage
3833
3834         # don't change the page unless necessary
3835         # however, clicking again on a tab should give it the focus.
3836         if new_page == self._curpage and not force:
3837
3838             ctrl, ctrl_idx = self.FindTab(wnd)
3839             if wx.Window.FindFocus() != ctrl:
3840                 ctrl.SetFocus()
3841
3842             return self._curpage
3843
3844         evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, self.GetId())
3845         evt.SetSelection(new_page)
3846         evt.SetOldSelection(self._curpage)
3847         evt.SetEventObject(self)
3848
3849         if not self.GetEventHandler().ProcessEvent(evt) or evt.IsAllowed():
3850
3851             old_curpage = self._curpage
3852             self._curpage = new_page
3853
3854             # program allows the page change
3855             evt.SetEventType(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGED)
3856             self.GetEventHandler().ProcessEvent(evt)
3857
3858             if not evt.IsAllowed(): # event is no longer allowed after handler
3859                 return self._curpage
3860
3861             ctrl, ctrl_idx = self.FindTab(wnd)
3862
3863             if ctrl:
3864                 self._tabs.SetActivePage(wnd)
3865                 ctrl.SetActivePage(ctrl_idx)
3866                 self.DoSizing()
3867                 ctrl.DoShowHide()
3868                 ctrl.MakeTabVisible(ctrl_idx, ctrl)
3869
3870                 # set fonts
3871                 all_panes = self._mgr.GetAllPanes()
3872                 for pane in all_panes:
3873                     if pane.name == "dummy":
3874                         continue
3875
3876                     tabctrl = pane.window._tabs
3877                     if tabctrl != ctrl:
3878                         tabctrl.SetSelectedFont(self._normal_font)
3879                     else:
3880                         tabctrl.SetSelectedFont(self._selected_font)
3881
3882                     tabctrl.Refresh()
3883                     tabctrl.Update()
3884
3885                 # Set the focus to the page if we're not currently focused on the tab.
3886                 # This is Firefox-like behaviour.
3887                 if wnd.IsShownOnScreen() and wx.Window.FindFocus() != ctrl:
3888                     wnd.SetFocus()
3889
3890                 return old_curpage
3891
3892         return self._curpage
3893
3894
3895     def SetSelectionToWindow(self, win):
3896         """
3897         Sets the selection based on the input window `win`.
3898
3899         :param `win`: a `wx.Window` derived window.
3900         """
3901
3902         idx = self._tabs.GetIdxFromWindow(win)
3903
3904         if idx == wx.NOT_FOUND:
3905             raise Exception("invalid notebook page")
3906
3907         if not self.GetEnabled(idx):
3908             return
3909
3910         # since a tab was clicked, let the parent know that we received
3911         # the focus, even if we will assign that focus immediately
3912         # to the child tab in the SetSelection call below
3913         # (the child focus event will also let AuiManager, if any,
3914         # know that the notebook control has been activated)
3915
3916         parent = self.GetParent()
3917         if parent:
3918             eventFocus = wx.ChildFocusEvent(self)
3919             parent.GetEventHandler().ProcessEvent(eventFocus)
3920
3921         self.SetSelection(idx)
3922
3923
3924     def SetSelectionToPage(self, page):
3925         """
3926         Sets the selection based on the input page.
3927
3928         :param `page`: an instance of L{AuiNotebookPage}.
3929         """
3930
3931         self.SetSelectionToWindow(page.window)
3932
3933
3934     def GetPageCount(self):
3935         """ Returns the number of pages in the notebook. """
3936
3937         return self._tabs.GetPageCount()
3938
3939
3940     def GetPage(self, page_idx):
3941         """
3942         Returns the page specified by the given index.
3943
3944         :param `page_idx`: the page index.
3945         """
3946
3947         if page_idx >= self._tabs.GetPageCount():
3948             raise Exception("invalid notebook page")
3949
3950         return self._tabs.GetWindowFromIdx(page_idx)
3951
3952
3953     def GetPageInfo(self, page_idx):
3954         """
3955         Returns the L{AuiNotebookPage} info structure specified by the given index.
3956
3957         :param `page_idx`: the page index.
3958         """
3959
3960         if page_idx >= self._tabs.GetPageCount():
3961             raise Exception("invalid notebook page")
3962
3963         return self._tabs.GetPage(page_idx)
3964
3965
3966     def GetEnabled(self, page_idx):
3967         """
3968         Returns whether the page specified by the index `page_idx` is enabled.
3969
3970         :param `page_idx`: the page index.
3971         """
3972
3973         return self._tabs.GetEnabled(page_idx)
3974
3975
3976     def EnableTab(self, page_idx, enable=True):
3977         """
3978         Enables/disables a page in the notebook.
3979
3980         :param `page_idx`: the page index;
3981         :param `enable`: ``True`` to enable the page, ``False`` to disable it.
3982         """
3983
3984         self._tabs.EnableTab(page_idx, enable)
3985         self.Refresh()
3986
3987
3988     def DoSizing(self):
3989         """ Performs all sizing operations in each tab control. """
3990
3991         all_panes = self._mgr.GetAllPanes()
3992         for pane in all_panes:
3993             if pane.name == "dummy":
3994                 continue
3995
3996             tabframe = pane.window
3997             tabframe.DoSizing()
3998
3999
4000     def GetAuiManager(self):
4001         """ Returns the associated L{AuiManager}. """
4002
4003         return self._mgr
4004
4005
4006     def GetActiveTabCtrl(self):
4007         """
4008         Returns the active tab control. It is called to determine which control
4009         gets new windows being added.
4010         """
4011
4012         if self._curpage >= 0 and self._curpage < self._tabs.GetPageCount():
4013
4014             # find the tab ctrl with the current page
4015             ctrl, idx = self.FindTab(self._tabs.GetPage(self._curpage).window)
4016             if ctrl:
4017                 return ctrl
4018
4019         # no current page, just find the first tab ctrl
4020         all_panes = self._mgr.GetAllPanes()
4021         for pane in all_panes:
4022             if pane.name == "dummy":
4023                 continue
4024
4025             tabframe = pane.window
4026             return tabframe._tabs
4027
4028         # If there is no tabframe at all, create one
4029         tabframe = TabFrame(self)
4030         tabframe.SetTabCtrlHeight(self._tab_ctrl_height)
4031         self._tab_id_counter += 1
4032         tabframe._tabs = AuiTabCtrl(self, self._tab_id_counter)
4033
4034         tabframe._tabs.SetAGWFlags(self._agwFlags)
4035         tabframe._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone())
4036         self._mgr.AddPane(tabframe, framemanager.AuiPaneInfo().Center().CaptionVisible(False).
4037                           PaneBorder((self._agwFlags & AUI_NB_SUB_NOTEBOOK) == 0))
4038
4039         self._mgr.Update()
4040
4041         return tabframe._tabs
4042
4043
4044     def FindTab(self, page):
4045         """
4046         Finds the tab control that currently contains the window as well
4047         as the index of the window in the tab control. It returns ``True`` if the
4048         window was found, otherwise ``False``.
4049
4050         :param `page`: an instance of L{AuiNotebookPage}.
4051         """
4052
4053         all_panes = self._mgr.GetAllPanes()
4054         for pane in all_panes:
4055             if pane.name == "dummy":
4056                 continue
4057
4058             tabframe = pane.window
4059
4060             page_idx = tabframe._tabs.GetIdxFromWindow(page)
4061
4062             if page_idx != -1:
4063
4064                 ctrl = tabframe._tabs
4065                 idx = page_idx
4066                 return ctrl, idx
4067
4068         return None, wx.NOT_FOUND
4069
4070
4071     def Split(self, page, direction):
4072         """
4073         Performs a split operation programmatically.
4074
4075         :param `page`: indicates the page that will be split off. This page will also become
4076          the active page after the split.
4077         :param `direction`: specifies where the pane should go, it should be one of the
4078          following: ``wx.TOP``, ``wx.BOTTOM``, ``wx.LEFT``, or ``wx.RIGHT``.
4079         """
4080
4081         cli_size = self.GetClientSize()
4082
4083         # get the page's window pointer
4084         wnd = self.GetPage(page)
4085         if not wnd:
4086             return
4087
4088         # notebooks with 1 or less pages can't be split
4089         if self.GetPageCount() < 2:
4090             return
4091
4092         # find out which tab control the page currently belongs to
4093
4094         src_tabs, src_idx = self.FindTab(wnd)
4095         if not src_tabs:
4096             return
4097
4098         # choose a split size
4099         if self.GetPageCount() > 2:
4100             split_size = self.CalculateNewSplitSize()
4101         else:
4102             # because there are two panes, always split them
4103             # equally
4104             split_size = self.GetClientSize()
4105             split_size.x /= 2
4106             split_size.y /= 2
4107
4108         # create a new tab frame
4109         new_tabs = TabFrame(self)
4110         new_tabs._rect = wx.RectPS(wx.Point(0, 0), split_size)
4111         new_tabs.SetTabCtrlHeight(self._tab_ctrl_height)
4112         self._tab_id_counter += 1
4113         new_tabs._tabs = AuiTabCtrl(self, self._tab_id_counter)
4114
4115         new_tabs._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone())
4116         new_tabs._tabs.SetAGWFlags(self._agwFlags)
4117         dest_tabs = new_tabs._tabs
4118
4119         page_info = src_tabs.GetPage(src_idx)
4120         if page_info.control:
4121             self.ReparentControl(page_info.control, dest_tabs)
4122
4123         # create a pane info structure with the information
4124         # about where the pane should be added
4125         pane_info = framemanager.AuiPaneInfo().Bottom().CaptionVisible(False)
4126
4127         if direction == wx.LEFT:
4128
4129             pane_info.Left()
4130             mouse_pt = wx.Point(0, cli_size.y/2)
4131
4132         elif direction == wx.RIGHT:
4133
4134             pane_info.Right()
4135             mouse_pt = wx.Point(cli_size.x, cli_size.y/2)
4136
4137         elif direction == wx.TOP:
4138
4139             pane_info.Top()
4140             mouse_pt = wx.Point(cli_size.x/2, 0)
4141
4142         elif direction == wx.BOTTOM:
4143
4144             pane_info.Bottom()
4145             mouse_pt = wx.Point(cli_size.x/2, cli_size.y)
4146
4147         self._mgr.AddPane(new_tabs, pane_info, mouse_pt)
4148         self._mgr.Update()
4149
4150         # remove the page from the source tabs
4151         page_info.active = False
4152
4153         src_tabs.RemovePage(page_info.window)
4154
4155         if src_tabs.GetPageCount() > 0:
4156             src_tabs.SetActivePage(0)
4157             src_tabs.DoShowHide()
4158             src_tabs.Refresh()
4159
4160         # add the page to the destination tabs
4161         dest_tabs.InsertPage(page_info.window, page_info, 0)
4162
4163         if src_tabs.GetPageCount() == 0:
4164             self.RemoveEmptyTabFrames()
4165
4166         self.DoSizing()
4167         dest_tabs.DoShowHide()
4168         dest_tabs.Refresh()
4169
4170         # force the set selection function reset the selection
4171         self._curpage = -1
4172
4173         # set the active page to the one we just split off
4174         self.SetSelectionToPage(page_info)
4175
4176         self.UpdateHintWindowSize()
4177
4178
4179     def UnSplit(self):
4180         """ Restores original view after a tab split. """
4181
4182         self.Freeze()
4183
4184         # remember the tab now selected
4185         nowSelected = self.GetSelection()
4186         # select first tab as destination
4187         self.SetSelection(0)
4188         # iterate all other tabs
4189         for idx in xrange(1, self.GetPageCount()):
4190             # get win reference
4191             win = self.GetPage(idx)
4192             # get tab title
4193             title = self.GetPageText(idx)
4194             # get page bitmap
4195             bmp = self.GetPageBitmap(idx)
4196             # remove from notebook
4197             self.RemovePage(idx)
4198             # re-add in the same position so it will tab
4199             self.InsertPage(idx, win, title, False, bmp)
4200         # restore orignial selected tab
4201         self.SetSelection(nowSelected)
4202
4203         self.Thaw()
4204
4205
4206     def ReparentControl(self, control, dest_tabs):
4207         """
4208         Reparents a control added inside a tab.
4209
4210         :param `control`: an instance of `wx.Window`;
4211         :param `dest_tabs`: the destination L{AuiTabCtrl}.
4212         """
4213
4214         control.Hide()
4215         control.Reparent(dest_tabs)
4216
4217
4218     def UnsplitDClick(self, part, sash_size, pos):
4219         """
4220         Unsplit the L{AuiNotebook} on sash double-click.
4221
4222         :param `part`: an UI part representing the sash;
4223         :param `sash_size`: the sash size;
4224         :param `pos`: the double-click mouse position.
4225
4226         :warning: Due to a bug on MSW, for disabled pages `wx.FindWindowAtPoint`
4227          returns the wrong window. See http://trac.wxwidgets.org/ticket/2942
4228         """
4229
4230         if not self._sash_dclick_unsplit:
4231             # Unsplit not allowed
4232             return
4233
4234         pos1 = wx.Point(*pos)
4235         pos2 = wx.Point(*pos)
4236         if part.orientation == wx.HORIZONTAL:
4237             pos1.y -= 2*sash_size
4238             pos2.y += 2*sash_size + self.GetTabCtrlHeight()
4239         elif part.orientation == wx.VERTICAL:
4240             pos1.x -= 2*sash_size
4241             pos2.x += 2*sash_size
4242         else:
4243             raise Exception("Invalid UI part orientation")
4244
4245         pos1, pos2 = self.ClientToScreen(pos1), self.ClientToScreen(pos2)
4246         win1, win2 = wx.FindWindowAtPoint(pos1), wx.FindWindowAtPoint(pos2)
4247
4248         if isinstance(win1, wx.ScrollBar):
4249             # Hopefully it will work
4250             pos1 = wx.Point(*pos)
4251             shift = wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X) + 2*(sash_size+1)
4252             if part.orientation == wx.HORIZONTAL:
4253                 pos1.y -= shift
4254             else:
4255                 pos1.x -= shift
4256
4257             pos1 = self.ClientToScreen(pos1)
4258             win1 = wx.FindWindowAtPoint(pos1)
4259
4260         if isinstance(win2, wx.ScrollBar):
4261             pos2 = wx.Point(*pos)
4262             shift = wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X) + 2*(sash_size+1)
4263             if part.orientation == wx.HORIZONTAL:
4264                 pos2.y += shift
4265             else:
4266                 pos2.x += shift
4267
4268             pos2 = self.ClientToScreen(pos2)
4269             win2 = wx.FindWindowAtPoint(pos2)
4270
4271         if not win1 or not win2:
4272             # How did we get here?
4273             return
4274
4275         if isinstance(win1, AuiNotebook) or isinstance(win2, AuiNotebook):
4276             # This is a bug on MSW, for disabled pages wx.FindWindowAtPoint
4277             # returns the wrong window.
4278             # See http://trac.wxwidgets.org/ticket/2942
4279             return
4280
4281         tab_frame1, tab_frame2 = self.GetTabFrameFromWindow(win1), self.GetTabFrameFromWindow(win2)
4282
4283         if not tab_frame1 or not tab_frame2:
4284             return
4285
4286         tab_ctrl_1, tab_ctrl_2 = tab_frame1._tabs, tab_frame2._tabs
4287
4288         if tab_ctrl_1.GetPageCount() > tab_ctrl_2.GetPageCount():
4289             src_tabs = tab_ctrl_2
4290             dest_tabs = tab_ctrl_1
4291         else:
4292             src_tabs = tab_ctrl_1
4293             dest_tabs = tab_ctrl_2
4294
4295         selection = -1
4296         page_count = dest_tabs.GetPageCount()
4297
4298         for page in xrange(src_tabs.GetPageCount()-1, -1, -1):
4299             # remove the page from the source tabs
4300             page_info = src_tabs.GetPage(page)
4301             if page_info.active:
4302                 selection = page_count + page
4303             src_tabs.RemovePage(page_info.window)
4304
4305             # add the page to the destination tabs
4306             dest_tabs.AddPage(page_info.window, page_info)
4307             if page_info.control:
4308                 self.ReparentControl(page_info.control, dest_tabs)
4309
4310         self.RemoveEmptyTabFrames()
4311
4312         dest_tabs.DoShowHide()
4313         self.DoSizing()
4314         dest_tabs.Refresh()
4315         self._mgr.Update()
4316         if selection > 0:
4317             wx.CallAfter(dest_tabs.MakeTabVisible, selection, self)
4318
4319
4320     def OnSize(self, event):
4321         """
4322         Handles the ``wx.EVT_SIZE`` event for L{AuiNotebook}.
4323
4324         :param `event`: a `wx.SizeEvent` event to be processed.
4325         """
4326
4327         self.UpdateHintWindowSize()
4328         event.Skip()
4329
4330
4331     def OnTabClicked(self, event):
4332         """
4333         Handles the ``EVT_AUINOTEBOOK_PAGE_CHANGING`` event for L{AuiNotebook}.
4334
4335         :param `event`: a L{AuiNotebookEvent} event to be processed.
4336         """
4337
4338         if self._textCtrl is not None:
4339             self._textCtrl.StopEditing()
4340
4341         ctrl = event.GetEventObject()
4342         assert ctrl != None
4343
4344         wnd = ctrl.GetWindowFromIdx(event.GetSelection())
4345         assert wnd != None
4346
4347         self.SetSelectionToWindow(wnd)
4348
4349
4350     def OnTabBgDClick(self, event):
4351         """
4352         Handles the ``EVT_AUINOTEBOOK_BG_DCLICK`` event for L{AuiNotebook}.
4353
4354         :param `event`: a L{AuiNotebookEvent} event to be processed.
4355         """
4356
4357         if self._textCtrl is not None:
4358             self._textCtrl.StopEditing()
4359
4360         # notify owner that the tabbar background has been double-clicked
4361         e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK, self.GetId())
4362         e.SetEventObject(self)
4363         self.GetEventHandler().ProcessEvent(e)
4364
4365
4366     def OnTabDClick(self, event):
4367         """
4368         Handles the ``EVT_AUINOTEBOOK_TAB_DCLICK`` event for L{AuiNotebook}.
4369
4370         :param `event`: a L{AuiNotebookEvent} event to be processed.
4371         """
4372
4373         # notify owner that the tabbar background has been double-clicked
4374         e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_DCLICK, self.GetId())
4375         e.SetEventObject(self)
4376         self.GetEventHandler().ProcessEvent(e)
4377
4378         tabs = event.GetEventObject()
4379         if not tabs.GetEnabled(event.GetSelection()):
4380             return
4381
4382         if not self.IsRenamable(event.GetSelection()):
4383             return
4384
4385         self.EditTab(event.GetSelection())
4386
4387
4388     def OnTabBeginDrag(self, event):
4389         """
4390         Handles the ``EVT_AUINOTEBOOK_BEGIN_DRAG`` event for L{AuiNotebook}.
4391
4392         :param `event`: a L{AuiNotebookEvent} event to be processed.
4393         """
4394
4395         tabs = event.GetEventObject()
4396         if not tabs.GetEnabled(event.GetSelection()):
4397             return
4398
4399         self._last_drag_x = 0
4400
4401
4402     def OnTabDragMotion(self, event):
4403         """
4404         Handles the ``EVT_AUINOTEBOOK_DRAG_MOTION`` event for L{AuiNotebook}.
4405
4406         :param `event`: a L{AuiNotebookEvent} event to be processed.
4407         """
4408
4409         tabs = event.GetEventObject()
4410         if not tabs.GetEnabled(event.GetSelection()):
4411             return
4412
4413         if self._textCtrl is not None:
4414             self._textCtrl.StopEditing()
4415
4416         screen_pt = wx.GetMousePosition()
4417         client_pt = self.ScreenToClient(screen_pt)
4418         zero = wx.Point(0, 0)
4419
4420         src_tabs = event.GetEventObject()
4421         dest_tabs = self.GetTabCtrlFromPoint(client_pt)
4422
4423         if dest_tabs == src_tabs:
4424
4425             # always hide the hint for inner-tabctrl drag
4426             self._mgr.HideHint()
4427
4428             # if tab moving is not allowed, leave
4429             if not self._agwFlags & AUI_NB_TAB_MOVE:
4430                 return
4431
4432             pt = dest_tabs.ScreenToClient(screen_pt)
4433
4434             # this is an inner-tab drag/reposition
4435             dest_location_tab = dest_tabs.TabHitTest(pt.x, pt.y)
4436
4437             if dest_location_tab:
4438
4439                 src_idx = event.GetSelection()
4440                 dest_idx = dest_tabs.GetIdxFromWindow(dest_location_tab)
4441
4442                 # prevent jumpy drag
4443                 if (src_idx == dest_idx) or dest_idx == -1 or \
4444                    (src_idx > dest_idx and self._last_drag_x <= pt.x) or \
4445                    (src_idx < dest_idx and self._last_drag_x >= pt.x):
4446
4447                     self._last_drag_x = pt.x
4448                     return
4449
4450                 src_tab = dest_tabs.GetWindowFromIdx(src_idx)
4451                 dest_tabs.MovePage(src_tab, dest_idx)
4452                 self._tabs.MovePage(self._tabs.GetPage(src_idx).window, dest_idx)
4453                 dest_tabs.SetActivePage(dest_idx)
4454                 dest_tabs.DoShowHide()
4455                 dest_tabs.Refresh()
4456                 self._last_drag_x = pt.x
4457
4458             return
4459
4460         # if external drag is allowed, check if the tab is being dragged
4461         # over a different AuiNotebook control
4462         if self._agwFlags & AUI_NB_TAB_EXTERNAL_MOVE:
4463
4464             tab_ctrl = wx.FindWindowAtPoint(screen_pt)
4465
4466             # if we aren't over any window, stop here
4467             if not tab_ctrl:
4468                 if self._agwFlags & AUI_NB_TAB_FLOAT:
4469                     if self.IsMouseWellOutsideWindow():
4470                         hintRect = wx.RectPS(screen_pt, (400, 300))
4471                         # Use CallAfter so we overwrite the hint that might be
4472                         # shown by our superclass:
4473                         wx.CallAfter(self._mgr.ShowHint, hintRect)
4474                 return
4475
4476             # make sure we are not over the hint window
4477             if not isinstance(tab_ctrl, wx.Frame):
4478                 while tab_ctrl:
4479                     if isinstance(tab_ctrl, AuiTabCtrl):
4480                         break
4481
4482                     tab_ctrl = tab_ctrl.GetParent()
4483
4484                 if tab_ctrl:
4485                     nb = tab_ctrl.GetParent()
4486
4487                     if nb != self:
4488
4489                         hint_rect = tab_ctrl.GetClientRect()
4490                         hint_rect.x, hint_rect.y = tab_ctrl.ClientToScreenXY(hint_rect.x, hint_rect.y)
4491                         self._mgr.ShowHint(hint_rect)
4492                         return
4493
4494             else:
4495
4496                 if not dest_tabs:
4497                     # we are either over a hint window, or not over a tab
4498                     # window, and there is no where to drag to, so exit
4499                     return
4500
4501         if self._agwFlags & AUI_NB_TAB_FLOAT:
4502             if self.IsMouseWellOutsideWindow():
4503                 hintRect = wx.RectPS(screen_pt, (400, 300))
4504                 # Use CallAfter so we overwrite the hint that might be
4505                 # shown by our superclass:
4506                 wx.CallAfter(self._mgr.ShowHint, hintRect)
4507                 return
4508
4509         # if there are less than two panes, split can't happen, so leave
4510         if self._tabs.GetPageCount() < 2:
4511             return
4512
4513         # if tab moving is not allowed, leave
4514         if not self._agwFlags & AUI_NB_TAB_SPLIT:
4515             return
4516
4517         if dest_tabs:
4518
4519             hint_rect = dest_tabs.GetRect()
4520             hint_rect.x, hint_rect.y = self.ClientToScreenXY(hint_rect.x, hint_rect.y)
4521             self._mgr.ShowHint(hint_rect)
4522
4523         else:
4524             rect = self._mgr.CalculateHintRect(self._dummy_wnd, client_pt, zero)
4525             if rect.IsEmpty():
4526                 self._mgr.HideHint()
4527                 return
4528
4529             hit_wnd = wx.FindWindowAtPoint(screen_pt)
4530             if hit_wnd and not isinstance(hit_wnd, AuiNotebook):
4531                 tab_frame = self.GetTabFrameFromWindow(hit_wnd)
4532                 if tab_frame:
4533                     hint_rect = wx.Rect(*tab_frame._rect)
4534                     hint_rect.x, hint_rect.y = self.ClientToScreenXY(hint_rect.x, hint_rect.y)
4535                     rect.Intersect(hint_rect)
4536                     self._mgr.ShowHint(rect)
4537                 else:
4538                     self._mgr.DrawHintRect(self._dummy_wnd, client_pt, zero)
4539             else:
4540                 self._mgr.DrawHintRect(self._dummy_wnd, client_pt, zero)
4541
4542
4543     def OnTabEndDrag(self, event):
4544         """
4545         Handles the ``EVT_AUINOTEBOOK_END_DRAG`` event for L{AuiNotebook}.
4546
4547         :param `event`: a L{AuiNotebookEvent} event to be processed.
4548         """
4549
4550         tabs = event.GetEventObject()
4551         if not tabs.GetEnabled(event.GetSelection()):
4552             return
4553
4554         self._mgr.HideHint()
4555
4556         src_tabs = event.GetEventObject()
4557         if not src_tabs:
4558             raise Exception("no source object?")
4559
4560         # get the mouse position, which will be used to determine the drop point
4561         mouse_screen_pt = wx.GetMousePosition()
4562         mouse_client_pt = self.ScreenToClient(mouse_screen_pt)
4563
4564         # check for an external move
4565         if self._agwFlags & AUI_NB_TAB_EXTERNAL_MOVE:
4566             tab_ctrl = wx.FindWindowAtPoint(mouse_screen_pt)
4567
4568             while tab_ctrl:
4569
4570                 if isinstance(tab_ctrl, AuiTabCtrl):
4571                     break
4572
4573                 tab_ctrl = tab_ctrl.GetParent()
4574
4575             if tab_ctrl:
4576
4577                 nb = tab_ctrl.GetParent()
4578
4579                 if nb != self:
4580
4581                     # find out from the destination control
4582                     # if it's ok to drop this tab here
4583                     e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_ALLOW_DND, self.GetId())
4584                     e.SetSelection(event.GetSelection())
4585                     e.SetOldSelection(event.GetSelection())
4586                     e.SetEventObject(self)
4587                     e.SetDragSource(self)
4588                     e.Veto() # dropping must be explicitly approved by control owner
4589
4590                     nb.GetEventHandler().ProcessEvent(e)
4591
4592                     if not e.IsAllowed():
4593
4594                         # no answer or negative answer
4595                         self._mgr.HideHint()
4596                         return
4597
4598                     # drop was allowed
4599                     src_idx = event.GetSelection()
4600                     src_page = src_tabs.GetWindowFromIdx(src_idx)
4601
4602                     # Check that it's not an impossible parent relationship
4603                     p = nb
4604                     while p and not p.IsTopLevel():
4605                         if p == src_page:
4606                             return
4607
4608                         p = p.GetParent()
4609
4610                     # get main index of the page
4611                     main_idx = self._tabs.GetIdxFromWindow(src_page)
4612                     if main_idx == wx.NOT_FOUND:
4613                         raise Exception("no source page?")
4614
4615                     # make a copy of the page info
4616                     page_info = self._tabs.GetPage(main_idx)
4617
4618                     # remove the page from the source notebook
4619                     self.RemovePage(main_idx)
4620
4621                     # reparent the page
4622                     src_page.Reparent(nb)
4623
4624                     # Reparent the control in a tab (if any)
4625                     if page_info.control:
4626                         self.ReparentControl(page_info.control, tab_ctrl)
4627
4628                     # find out the insert idx
4629                     dest_tabs = tab_ctrl
4630                     pt = dest_tabs.ScreenToClient(mouse_screen_pt)
4631
4632                     target = dest_tabs.TabHitTest(pt.x, pt.y)
4633                     insert_idx = -1
4634                     if target:
4635                         insert_idx = dest_tabs.GetIdxFromWindow(target)
4636
4637                     # add the page to the new notebook
4638                     if insert_idx == -1:
4639                         insert_idx = dest_tabs.GetPageCount()
4640
4641                     dest_tabs.InsertPage(page_info.window, page_info, insert_idx)
4642                     nb._tabs.AddPage(page_info.window, page_info)
4643
4644                     nb.DoSizing()
4645                     dest_tabs.DoShowHide()
4646                     dest_tabs.Refresh()
4647
4648                     # set the selection in the destination tab control
4649                     nb.SetSelectionToPage(page_info)
4650
4651                     # notify owner that the tab has been dragged
4652                     e2 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, self.GetId())
4653                     e2.SetSelection(event.GetSelection())
4654                     e2.SetOldSelection(event.GetSelection())
4655                     e2.SetEventObject(self)
4656                     self.GetEventHandler().ProcessEvent(e2)
4657
4658                     # notify the target notebook that the tab has been dragged
4659                     e3 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, nb.GetId())
4660                     e3.SetSelection(insert_idx)
4661                     e3.SetOldSelection(insert_idx)
4662                     e3.SetEventObject(nb)
4663                     nb.GetEventHandler().ProcessEvent(e3)
4664
4665                     return
4666
4667         if self._agwFlags & AUI_NB_TAB_FLOAT:
4668             self._mgr.HideHint()
4669             if self.IsMouseWellOutsideWindow():
4670                 # Use CallAfter so we our superclass can deal with the event first
4671                 wx.CallAfter(self.FloatPage, self.GetSelection())
4672                 event.Skip()
4673                 return
4674
4675         # only perform a tab split if it's allowed
4676         dest_tabs = None
4677
4678         if self._agwFlags & AUI_NB_TAB_SPLIT and self._tabs.GetPageCount() >= 2:
4679
4680             # If the pointer is in an existing tab frame, do a tab insert
4681             hit_wnd = wx.FindWindowAtPoint(mouse_screen_pt)
4682             tab_frame = self.GetTabFrameFromTabCtrl(hit_wnd)
4683             insert_idx = -1
4684
4685             if tab_frame:
4686
4687                 dest_tabs = tab_frame._tabs
4688
4689                 if dest_tabs == src_tabs:
4690                     return
4691
4692                 pt = dest_tabs.ScreenToClient(mouse_screen_pt)
4693                 target = dest_tabs.TabHitTest(pt.x, pt.y)
4694
4695                 if target:
4696                     insert_idx = dest_tabs.GetIdxFromWindow(target)
4697
4698             else:
4699
4700                 zero = wx.Point(0, 0)
4701                 rect = self._mgr.CalculateHintRect(self._dummy_wnd, mouse_client_pt, zero)
4702
4703                 if rect.IsEmpty():
4704                     # there is no suitable drop location here, exit out
4705                     return
4706
4707                 # If there is no tabframe at all, create one
4708                 new_tabs = TabFrame(self)
4709                 new_tabs._rect = wx.RectPS(wx.Point(0, 0), self.CalculateNewSplitSize())
4710                 new_tabs.SetTabCtrlHeight(self._tab_ctrl_height)
4711                 self._tab_id_counter += 1
4712                 new_tabs._tabs = AuiTabCtrl(self, self._tab_id_counter)
4713                 new_tabs._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone())
4714                 new_tabs._tabs.SetAGWFlags(self._agwFlags)
4715
4716                 self._mgr.AddPane(new_tabs, framemanager.AuiPaneInfo().Bottom().CaptionVisible(False), mouse_client_pt)
4717                 self._mgr.Update()
4718                 dest_tabs = new_tabs._tabs
4719
4720             # remove the page from the source tabs
4721             page_info = src_tabs.GetPage(event.GetSelection())
4722
4723             if page_info.control:
4724                 self.ReparentControl(page_info.control, dest_tabs)
4725
4726             page_info.active = False
4727             src_tabs.RemovePage(page_info.window)
4728
4729             if src_tabs.GetPageCount() > 0:
4730                 src_tabs.SetActivePage(0)
4731                 src_tabs.DoShowHide()
4732                 src_tabs.Refresh()
4733
4734             # add the page to the destination tabs
4735             if insert_idx == -1:
4736                 insert_idx = dest_tabs.GetPageCount()
4737
4738             dest_tabs.InsertPage(page_info.window, page_info, insert_idx)
4739
4740             if src_tabs.GetPageCount() == 0:
4741                 self.RemoveEmptyTabFrames()
4742
4743             self.DoSizing()
4744             dest_tabs.DoShowHide()
4745             dest_tabs.Refresh()
4746
4747             # force the set selection function reset the selection
4748             self._curpage = -1
4749
4750             # set the active page to the one we just split off
4751             self.SetSelectionToPage(page_info)
4752
4753             self.UpdateHintWindowSize()
4754
4755         # notify owner that the tab has been dragged
4756         e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, self.GetId())
4757         e.SetSelection(event.GetSelection())
4758         e.SetOldSelection(event.GetSelection())
4759         e.SetEventObject(self)
4760         self.GetEventHandler().ProcessEvent(e)
4761
4762
4763     def OnTabCancelDrag(self, event):
4764         """
4765         Handles the ``EVT_AUINOTEBOOK_CANCEL_DRAG`` event for L{AuiNotebook}.
4766
4767         :param `event`: a L{AuiNotebookEvent} event to be processed.
4768         """
4769
4770         tabs = event.GetEventObject()
4771         if not tabs.GetEnabled(event.GetSelection()):
4772             return
4773
4774         self._mgr.HideHint()
4775
4776         src_tabs = event.GetEventObject()
4777         if not src_tabs:
4778             raise Exception("no source object?")
4779
4780
4781     def IsMouseWellOutsideWindow(self):
4782         """ Returns whether the mouse is well outside the L{AuiNotebook} screen rectangle. """
4783
4784         screen_rect = self.GetScreenRect()
4785         screen_rect.Inflate(50, 50)
4786
4787         return not screen_rect.Contains(wx.GetMousePosition())
4788
4789
4790     def FloatPage(self, page_index):
4791         """
4792         Float the page in `page_index` by reparenting it to a floating frame.
4793
4794         :param `page_index`: the index of the page to be floated.
4795
4796         :warning: When the notebook is more or less full screen, tabs cannot be dragged far
4797          enough outside of the notebook to become floating pages.
4798         """
4799
4800         root_manager = framemanager.GetManager(self)
4801         page_title = self.GetPageText(page_index)
4802         page_contents = self.GetPage(page_index)
4803         page_bitmap = self.GetPageBitmap(page_index)
4804         text_colour = self.GetPageTextColour(page_index)
4805         info = self.GetPageInfo(page_index)
4806
4807         if root_manager and root_manager != self._mgr:
4808             root_manager = framemanager.GetManager(self)
4809
4810             if hasattr(page_contents, "__floating_size__"):
4811                 floating_size = wx.Size(*page_contents.__floating_size__)
4812             else:
4813                 floating_size = page_contents.GetBestSize()
4814                 if floating_size == wx.DefaultSize:
4815                     floating_size = wx.Size(300, 200)
4816
4817             page_contents.__page_index__ = page_index
4818             page_contents.__aui_notebook__ = self
4819             page_contents.__text_colour__ = text_colour
4820             page_contents.__control__ = info.control
4821
4822             if info.control:
4823                 info.control.Reparent(page_contents)
4824                 info.control.Hide()
4825                 info.control = None
4826
4827             self.RemovePage(page_index)
4828             self.RemoveEmptyTabFrames()
4829
4830             pane_info = framemanager.AuiPaneInfo().Float().FloatingPosition(wx.GetMousePosition()). \
4831                         FloatingSize(floating_size).BestSize(floating_size).Name("__floating__%s"%page_title). \
4832                         Caption(page_title).Icon(page_bitmap)
4833             root_manager.AddPane(page_contents, pane_info)
4834             root_manager.Bind(framemanager.EVT_AUI_PANE_CLOSE, self.OnCloseFloatingPage)
4835             self.GetActiveTabCtrl().DoShowHide()
4836             self.DoSizing()
4837             root_manager.Update()
4838
4839         else:
4840             frame = wx.Frame(self, title=page_title,
4841                              style=wx.DEFAULT_FRAME_STYLE|wx.FRAME_TOOL_WINDOW|
4842                                    wx.FRAME_FLOAT_ON_PARENT | wx.FRAME_NO_TASKBAR)
4843
4844             if info.control:
4845                 info.control.Reparent(frame)
4846                 info.control.Hide()
4847
4848             frame.bitmap = page_bitmap
4849             frame.page_index = page_index
4850             frame.text_colour = text_colour
4851             frame.control = info.control
4852             page_contents.Reparent(frame)
4853             frame.Bind(wx.EVT_CLOSE, self.OnCloseFloatingPage)
4854             frame.Move(wx.GetMousePosition())
4855             frame.Show()
4856             self.RemovePage(page_index)
4857
4858             self.RemoveEmptyTabFrames()
4859
4860         wx.CallAfter(self.RemoveEmptyTabFrames)
4861
4862
4863     def OnCloseFloatingPage(self, event):
4864         """
4865         Handles the ``wx.EVT_CLOSE`` event for a floating page in L{AuiNotebook}.
4866
4867         :param `event`: a `wx.CloseEvent` event to be processed.
4868         """
4869
4870         root_manager = framemanager.GetManager(self)
4871         if root_manager and root_manager != self._mgr:
4872             pane = event.pane
4873             if pane.name.startswith("__floating__"):
4874                 self.ReDockPage(pane)
4875                 return
4876
4877             event.Skip()
4878         else:
4879             event.Skip()
4880             frame = event.GetEventObject()
4881             page_title = frame.GetTitle()
4882             page_contents = list(frame.GetChildren())[-1]
4883             page_contents.Reparent(self)
4884             self.InsertPage(frame.page_index, page_contents, page_title, select=True, bitmap=frame.bitmap, control=frame.control)
4885
4886             if frame.control:
4887                 src_tabs, idx = self.FindTab(page_contents)
4888                 frame.control.Reparent(src_tabs)
4889                 frame.control.Hide()
4890                 frame.control = None
4891
4892             self.SetPageTextColour(frame.page_index, frame.text_colour)
4893
4894
4895     def ReDockPage(self, pane):
4896         """
4897         Re-docks a floating L{AuiNotebook} tab in the original position, when possible.
4898
4899         :param `pane`: an instance of L{framemanager.AuiPaneInfo}.
4900         """
4901
4902         root_manager = framemanager.GetManager(self)
4903
4904         pane.window.__floating_size__ = wx.Size(*pane.floating_size)
4905         page_index = pane.window.__page_index__
4906         text_colour = pane.window.__text_colour__
4907         control = pane.window.__control__
4908
4909         root_manager.DetachPane(pane.window)
4910         self.InsertPage(page_index, pane.window, pane.caption, True, pane.icon, control=control)
4911
4912         self.SetPageTextColour(page_index, text_colour)
4913         self.GetActiveTabCtrl().DoShowHide()
4914         self.DoSizing()
4915         if control:
4916             self.UpdateTabCtrlHeight(force=True)
4917
4918         self._mgr.Update()
4919         root_manager.Update()
4920
4921
4922     def GetTabCtrlFromPoint(self, pt):
4923         """
4924         Returns the tab control at the specified point.
4925
4926         :param `pt`: a `wx.Point` object.
4927         """
4928
4929         # if we've just removed the last tab from the source
4930         # tab set, the remove the tab control completely
4931         all_panes = self._mgr.GetAllPanes()
4932         for pane in all_panes:
4933             if pane.name == "dummy":
4934                 continue
4935
4936             tabframe = pane.window
4937             if tabframe._tab_rect.Contains(pt):
4938                 return tabframe._tabs
4939
4940         return None
4941
4942
4943     def GetTabFrameFromTabCtrl(self, tab_ctrl):
4944         """
4945         Returns the tab frame associated with a tab control.
4946
4947         :param `tab_ctrl`: an instance of L{AuiTabCtrl}.
4948         """
4949
4950         # if we've just removed the last tab from the source
4951         # tab set, the remove the tab control completely
4952         all_panes = self._mgr.GetAllPanes()
4953         for pane in all_panes:
4954             if pane.name == "dummy":
4955                 continue
4956
4957             tabframe = pane.window
4958             if tabframe._tabs == tab_ctrl:
4959                 return tabframe
4960
4961         return None
4962
4963
4964     def GetTabFrameFromWindow(self, wnd):
4965         """
4966         Returns the tab frame associated with a window.
4967
4968         :param `wnd`: an instance of `wx.Window`.
4969         """
4970
4971         all_panes = self._mgr.GetAllPanes()
4972         for pane in all_panes:
4973             if pane.name == "dummy":
4974                 continue
4975
4976             tabframe = pane.window
4977             for page in tabframe._tabs.GetPages():
4978                 if wnd == page.window:
4979                     return tabframe
4980
4981         return None
4982
4983
4984     def RemoveEmptyTabFrames(self):
4985         """ Removes all the empty tab frames. """
4986
4987         # if we've just removed the last tab from the source
4988         # tab set, the remove the tab control completely
4989         all_panes = self._mgr.GetAllPanes()
4990
4991         for indx in xrange(len(all_panes)-1, -1, -1):
4992             pane = all_panes[indx]
4993             if pane.name == "dummy":
4994                 continue
4995
4996             tab_frame = pane.window
4997             if tab_frame._tabs.GetPageCount() == 0:
4998                 self._mgr.DetachPane(tab_frame)
4999                 tab_frame._tabs.Destroy()
5000                 tab_frame._tabs = None
5001                 del tab_frame
5002
5003         # check to see if there is still a center pane
5004         # if there isn't, make a frame the center pane
5005         first_good = None
5006         center_found = False
5007
5008         all_panes = self._mgr.GetAllPanes()
5009         for pane in all_panes:
5010             if pane.name == "dummy":
5011                 continue
5012
5013             if pane.dock_direction == AUI_DOCK_CENTRE:
5014                 center_found = True
5015             if not first_good:
5016                 first_good = pane.window
5017
5018         if not center_found and first_good:
5019             self._mgr.GetPane(first_good).Centre()
5020
5021         if not self.IsBeingDeleted():
5022             self._mgr.Update()
5023
5024
5025     def OnChildFocusNotebook(self, event):
5026         """
5027         Handles the ``wx.EVT_CHILD_FOCUS`` event for L{AuiNotebook}.
5028
5029         :param `event`: a `wx.ChildFocusEvent` event to be processed.
5030         """
5031
5032         # if we're dragging a tab, don't change the current selection.
5033         # This code prevents a bug that used to happen when the hint window
5034         # was hidden.  In the bug, the focus would return to the notebook
5035         # child, which would then enter this handler and call
5036         # SetSelection, which is not desired turn tab dragging.
5037
5038         event.Skip()
5039
5040         all_panes = self._mgr.GetAllPanes()
5041         for pane in all_panes:
5042             if pane.name == "dummy":
5043                 continue
5044             tabframe = pane.window
5045             if tabframe._tabs.IsDragging():
5046                 return
5047
5048 ##        # change the tab selection to the child
5049 ##        # which was focused
5050 ##        idx = self._tabs.GetIdxFromWindow(event.GetWindow())
5051 ##        if idx != -1 and idx != self._curpage:
5052 ##            self.SetSelection(idx)
5053
5054
5055     def SetNavigatorIcon(self, bmp):
5056         """
5057         Sets the icon used by the L{TabNavigatorWindow}.
5058
5059         :param `bmp`: an instance of `wx.Bitmap`.
5060         """
5061
5062         if isinstance(bmp, wx.Bitmap) and bmp.IsOk():
5063             # Make sure image is proper size
5064             if bmp.GetSize() != (16, 16):
5065                 img = bmp.ConvertToImage()
5066                 img.Rescale(16, 16, wx.IMAGE_QUALITY_HIGH)
5067                 bmp = wx.BitmapFromImage(img)
5068             self._naviIcon = bmp
5069         else:
5070             raise TypeError, "SetNavigatorIcon requires a valid bitmap"
5071
5072
5073     def OnNavigationKeyNotebook(self, event):
5074         """
5075         Handles the ``wx.EVT_NAVIGATION_KEY`` event for L{AuiNotebook}.
5076
5077         :param `event`: a `wx.NavigationKeyEvent` event to be processed.
5078         """
5079
5080         if event.IsWindowChange():
5081             if self._agwFlags & AUI_NB_SMART_TABS:
5082                 if not self._popupWin:
5083                     self._popupWin = TabNavigatorWindow(self, self._naviIcon)
5084                     self._popupWin.SetReturnCode(wx.ID_OK)
5085                     self._popupWin.ShowModal()
5086                     idx = self._popupWin.GetSelectedPage()
5087                     self._popupWin.Destroy()
5088                     self._popupWin = None
5089                     # Need to do CallAfter so that the selection and its
5090                     # associated events get processed outside the context of
5091                     # this key event. Not doing so causes odd issues with the
5092                     # window focus under certain use cases on Windows.
5093                     wx.CallAfter(self.SetSelection, idx, True)
5094                 else:
5095                     # a dialog is already opened
5096                     self._popupWin.OnNavigationKey(event)
5097                     return
5098             else:
5099                 # change pages
5100                 # FIXME: the problem with this is that if we have a split notebook,
5101                 # we selection may go all over the place.
5102                 self.AdvanceSelection(event.GetDirection())
5103
5104         else:
5105             # we get this event in 3 cases
5106             #
5107             # a) one of our pages might have generated it because the user TABbed
5108             # out from it in which case we should propagate the event upwards and
5109             # our parent will take care of setting the focus to prev/next sibling
5110             #
5111             # or
5112             #
5113             # b) the parent panel wants to give the focus to us so that we
5114             # forward it to our selected page. We can't deal with this in
5115             # OnSetFocus() because we don't know which direction the focus came
5116             # from in this case and so can't choose between setting the focus to
5117             # first or last panel child
5118             #
5119             # or
5120             #
5121             # c) we ourselves (see MSWTranslateMessage) generated the event
5122             #
5123             parent = self.GetParent()
5124
5125             # the wxObject* casts are required to avoid MinGW GCC 2.95.3 ICE
5126             isFromParent = event.GetEventObject() == parent
5127             isFromSelf = event.GetEventObject() == self
5128
5129             if isFromParent or isFromSelf:
5130
5131                 # no, it doesn't come from child, case (b) or (c): forward to a
5132                 # page but only if direction is backwards (TAB) or from ourselves,
5133                 if self.GetSelection() != wx.NOT_FOUND and (not event.GetDirection() or isFromSelf):
5134
5135                     # so that the page knows that the event comes from it's parent
5136                     # and is being propagated downwards
5137                     event.SetEventObject(self)
5138
5139                     page = self.GetPage(self.GetSelection())
5140                     if not page.GetEventHandler().ProcessEvent(event):
5141                         page.SetFocus()
5142
5143                     #else: page manages focus inside it itself
5144
5145                 else: # otherwise set the focus to the notebook itself
5146
5147                     self.SetFocus()
5148
5149             else:
5150
5151                 # send this event back for the 'wraparound' focus.
5152                 winFocus = event.GetCurrentFocus()
5153
5154                 if winFocus:
5155                     event.SetEventObject(self)
5156                     winFocus.GetEventHandler().ProcessEvent(event)
5157
5158
5159     def OnTabButton(self, event):
5160         """
5161         Handles the ``EVT_AUINOTEBOOK_BUTTON`` event for L{AuiNotebook}.
5162
5163         :param `event`: a L{AuiNotebookEvent} event to be processed.
5164         """
5165
5166         tabs = event.GetEventObject()
5167         button_id = event.GetInt()
5168
5169         if button_id == AUI_BUTTON_CLOSE:
5170
5171             selection = event.GetSelection()
5172
5173             if selection == -1:
5174
5175                 # if the close button is to the right, use the active
5176                 # page selection to determine which page to close
5177                 selection = tabs.GetActivePage()
5178
5179             if selection == -1 or not tabs.GetEnabled(selection):
5180                 return
5181
5182             if selection != -1:
5183
5184                 close_wnd = tabs.GetWindowFromIdx(selection)
5185
5186                 if close_wnd.GetName() == "__fake__page__":
5187                     # This is a notebook preview
5188                     previous_active, page_status = close_wnd.__previousStatus
5189                     for page, status in zip(tabs.GetPages(), page_status):
5190                         page.enabled = status
5191
5192                     main_idx = self._tabs.GetIdxFromWindow(close_wnd)
5193                     self.DeletePage(main_idx)
5194
5195                     if previous_active >= 0:
5196                         tabs.SetActivePage(previous_active)
5197                         page_count = tabs.GetPageCount()
5198                         selection = -1
5199
5200                         for page in xrange(page_count):
5201                             # remove the page from the source tabs
5202                             page_info = tabs.GetPage(page)
5203                             if page_info.active:
5204                                 selection = page
5205                                 break
5206
5207                         tabs.DoShowHide()
5208                         self.DoSizing()
5209                         tabs.Refresh()
5210
5211                         if selection >= 0:
5212                             wx.CallAfter(tabs.MakeTabVisible, selection, self)
5213
5214                     # Don't fire the event
5215                     return
5216
5217                 # ask owner if it's ok to close the tab
5218                 e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE, self.GetId())
5219                 idx = self._tabs.GetIdxFromWindow(close_wnd)
5220                 e.SetSelection(idx)
5221                 e.SetOldSelection(event.GetSelection())
5222                 e.SetEventObject(self)
5223                 self.GetEventHandler().ProcessEvent(e)
5224                 if not e.IsAllowed():
5225                     return
5226
5227                 if repr(close_wnd.__class__).find("AuiMDIChildFrame") >= 0:
5228                     close_wnd.Close()
5229
5230                 else:
5231                     main_idx = self._tabs.GetIdxFromWindow(close_wnd)
5232                     self.DeletePage(main_idx)
5233
5234                 # notify owner that the tab has been closed
5235                 e2 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSED, self.GetId())
5236                 e2.SetSelection(idx)
5237                 e2.SetEventObject(self)
5238                 self.GetEventHandler().ProcessEvent(e2)
5239
5240                 if self.GetPageCount() == 0:
5241                     mgr = self.GetAuiManager()
5242                     win = mgr.GetManagedWindow()
5243                     win.SendSizeEvent()
5244
5245
5246     def OnTabMiddleDown(self, event):
5247         """
5248         Handles the ``EVT_AUINOTEBOOK_TAB_MIDDLE_DOWN`` event for L{AuiNotebook}.
5249
5250         :param `event`: a L{AuiNotebookEvent} event to be processed.
5251         """
5252
5253         tabs = event.GetEventObject()
5254         if not tabs.GetEnabled(event.GetSelection()):
5255             return
5256
5257         # patch event through to owner
5258         wnd = tabs.GetWindowFromIdx(event.GetSelection())
5259
5260         e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_DOWN, self.GetId())
5261         e.SetSelection(self._tabs.GetIdxFromWindow(wnd))
5262         e.SetEventObject(self)
5263         self.GetEventHandler().ProcessEvent(e)
5264
5265
5266     def OnTabMiddleUp(self, event):
5267         """
5268         Handles the ``EVT_AUINOTEBOOK_TAB_MIDDLE_UP`` event for L{AuiNotebook}.
5269
5270         :param `event`: a L{AuiNotebookEvent} event to be processed.
5271         """
5272
5273         tabs = event.GetEventObject()
5274         if not tabs.GetEnabled(event.GetSelection()):
5275             return
5276
5277         # if the AUI_NB_MIDDLE_CLICK_CLOSE is specified, middle
5278         # click should act like a tab close action.  However, first
5279         # give the owner an opportunity to handle the middle up event
5280         # for custom action
5281
5282         wnd = tabs.GetWindowFromIdx(event.GetSelection())
5283
5284         e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_UP, self.GetId())
5285         e.SetSelection(self._tabs.GetIdxFromWindow(wnd))
5286         e.SetEventObject(self)
5287         if self.GetEventHandler().ProcessEvent(e):
5288             return
5289         if not e.IsAllowed():
5290             return
5291
5292         # check if we are supposed to close on middle-up
5293         if self._agwFlags & AUI_NB_MIDDLE_CLICK_CLOSE == 0:
5294             return
5295
5296         # simulate the user pressing the close button on the tab
5297         event.SetInt(AUI_BUTTON_CLOSE)
5298         self.OnTabButton(event)
5299
5300
5301     def OnTabRightDown(self, event):
5302         """
5303         Handles the ``EVT_AUINOTEBOOK_TAB_RIGHT_DOWN`` event for L{AuiNotebook}.
5304
5305         :param `event`: a L{AuiNotebookEvent} event to be processed.
5306         """
5307
5308         tabs = event.GetEventObject()
5309         if not tabs.GetEnabled(event.GetSelection()):
5310             return
5311
5312         # patch event through to owner
5313         wnd = tabs.GetWindowFromIdx(event.GetSelection())
5314
5315         e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_DOWN, self.GetId())
5316         e.SetSelection(self._tabs.GetIdxFromWindow(wnd))
5317         e.SetEventObject(self)
5318         self.GetEventHandler().ProcessEvent(e)
5319
5320
5321     def OnTabRightUp(self, event):
5322         """
5323         Handles the ``EVT_AUINOTEBOOK_TAB_RIGHT_UP`` event for L{AuiNotebook}.
5324
5325         :param `event`: a L{AuiNotebookEvent} event to be processed.
5326         """
5327
5328         tabs = event.GetEventObject()
5329         if not tabs.GetEnabled(event.GetSelection()):
5330             return
5331
5332         # patch event through to owner
5333         wnd = tabs.GetWindowFromIdx(event.GetSelection())
5334
5335         e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP, self.GetId())
5336         e.SetSelection(self._tabs.GetIdxFromWindow(wnd))
5337         e.SetEventObject(self)
5338         self.GetEventHandler().ProcessEvent(e)
5339
5340
5341     def SetNormalFont(self, font):
5342         """
5343         Sets the normal font for drawing tab labels.
5344
5345         :param `font`: a `wx.Font` object.
5346         """
5347
5348         self._normal_font = font
5349         self.GetArtProvider().SetNormalFont(font)
5350
5351
5352     def SetSelectedFont(self, font):
5353         """
5354         Sets the selected tab font for drawing tab labels.
5355
5356         :param `font`: a `wx.Font` object.
5357         """
5358
5359         self._selected_font = font
5360         self.GetArtProvider().SetSelectedFont(font)
5361
5362
5363     def SetMeasuringFont(self, font):
5364         """
5365         Sets the font for calculating text measurements.
5366
5367         :param `font`: a `wx.Font` object.
5368         """
5369
5370         self.GetArtProvider().SetMeasuringFont(font)
5371
5372
5373     def SetFont(self, font):
5374         """
5375         Sets the tab font.
5376
5377         :param `font`: a `wx.Font` object.
5378
5379         :note: Overridden from `wx.PyPanel`.
5380         """
5381
5382         wx.PyPanel.SetFont(self, font)
5383
5384         selectedFont = wx.Font(font.GetPointSize(), font.GetFamily(),
5385                                font.GetStyle(), wx.BOLD, font.GetUnderlined(),
5386                                font.GetFaceName(), font.GetEncoding())
5387
5388         self.SetNormalFont(font)
5389         self.SetSelectedFont(selectedFont)
5390         self.SetMeasuringFont(selectedFont)
5391
5392         # Recalculate tab container size based on new font
5393         self.UpdateTabCtrlHeight(force=False)
5394         self.DoSizing()
5395
5396         return True
5397
5398
5399     def GetTabCtrlHeight(self):
5400         """ Returns the tab control height. """
5401
5402         return self._tab_ctrl_height
5403
5404
5405     def GetHeightForPageHeight(self, pageHeight):
5406         """
5407         Gets the height of the notebook for a given page height.
5408
5409         :param `pageHeight`: the given page height.
5410         """
5411
5412         self.UpdateTabCtrlHeight()
5413
5414         tabCtrlHeight = self.GetTabCtrlHeight()
5415         decorHeight = 2
5416         return tabCtrlHeight + pageHeight + decorHeight
5417
5418
5419     def AdvanceSelection(self, forward=True, wrap=True):
5420         """
5421         Cycles through the tabs.
5422
5423         :param `forward`: whether to advance forward or backward;
5424         :param `wrap`: ``True`` to return to the first tab if we reach the last tab.
5425
5426         :note: The call to this function generates the page changing events.
5427         """
5428
5429         tabCtrl = self.GetActiveTabCtrl()
5430         newPage = -1
5431
5432         focusWin = tabCtrl.FindFocus()
5433         activePage = tabCtrl.GetActivePage()
5434         lenPages = len(tabCtrl.GetPages())
5435
5436         if lenPages == 1:
5437             return False
5438
5439         if forward:
5440             if lenPages > 1:
5441
5442                 if activePage == -1 or activePage == lenPages - 1:
5443                     if not wrap:
5444                         return False
5445
5446                     newPage = 0
5447
5448                 elif activePage < lenPages - 1:
5449                     newPage = activePage + 1
5450
5451         else:
5452
5453             if lenPages > 1:
5454                 if activePage == -1 or activePage == 0:
5455                     if not wrap:
5456                         return False
5457
5458                     newPage = lenPages - 1
5459
5460                 elif activePage > 0:
5461                     newPage = activePage - 1
5462
5463
5464         if newPage != -1:
5465             if not self.GetEnabled(newPage):
5466                 return False
5467
5468             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, tabCtrl.GetId())
5469             e.SetSelection(newPage)
5470             e.SetOldSelection(activePage)
5471             e.SetEventObject(tabCtrl)
5472             self.GetEventHandler().ProcessEvent(e)
5473
5474 ##        if focusWin:
5475 ##            focusWin.SetFocus()
5476
5477         return True
5478
5479
5480     def ShowWindowMenu(self):
5481         """
5482         Shows the window menu for the active tab control associated with this
5483         notebook, and returns ``True`` if a selection was made.
5484         """
5485
5486         tabCtrl = self.GetActiveTabCtrl()
5487         idx = tabCtrl.GetArtProvider().ShowDropDown(tabCtrl, tabCtrl.GetPages(), tabCtrl.GetActivePage())
5488
5489         if not self.GetEnabled(idx):
5490             return False
5491
5492         if idx != -1:
5493             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, tabCtrl.GetId())
5494             e.SetSelection(idx)
5495             e.SetOldSelection(tabCtrl.GetActivePage())
5496             e.SetEventObject(tabCtrl)
5497             self.GetEventHandler().ProcessEvent(e)
5498
5499             return True
5500
5501         else:
5502
5503             return False
5504
5505
5506     def AddTabAreaButton(self, id, location, normal_bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap):
5507         """
5508         Adds a button in the tab area.
5509
5510         :param `id`: the button identifier. This can be one of the following:
5511
5512          ==============================  =================================
5513          Button Identifier               Description
5514          ==============================  =================================
5515          ``AUI_BUTTON_CLOSE``            Shows a close button on the tab area
5516          ``AUI_BUTTON_WINDOWLIST``       Shows a window list button on the tab area
5517          ``AUI_BUTTON_LEFT``             Shows a left button on the tab area
5518          ``AUI_BUTTON_RIGHT``            Shows a right button on the tab area
5519          ==============================  =================================
5520
5521         :param `location`: the button location. Can be ``wx.LEFT`` or ``wx.RIGHT``;
5522         :param `normal_bitmap`: the bitmap for an enabled tab;
5523         :param `disabled_bitmap`: the bitmap for a disabled tab.
5524         """
5525
5526         active_tabctrl = self.GetActiveTabCtrl()
5527         active_tabctrl.AddButton(id, location, normal_bitmap, disabled_bitmap)
5528
5529
5530     def RemoveTabAreaButton(self, id):
5531         """
5532         Removes a button from the tab area.
5533
5534         :param `id`: the button identifier. See L{AddTabAreaButton} for a list of button identifiers.
5535
5536         :see: L{AddTabAreaButton}
5537         """
5538
5539         active_tabctrl = self.GetActiveTabCtrl()
5540         active_tabctrl.RemoveButton(id)
5541
5542
5543     def HasMultiplePages(self):
5544         """
5545         This method should be overridden to return ``True`` if this window has multiple pages. All
5546         standard class with multiple pages such as `wx.Notebook`, `wx.Listbook` and `wx.Treebook`
5547         already override it to return ``True`` and user-defined classes with similar behaviour
5548         should do it as well to allow the library to handle such windows appropriately.
5549
5550         :note: Overridden from `wx.PyPanel`.
5551         """
5552
5553         return True
5554
5555
5556     def GetDefaultBorder(self):
5557         """ Returns the default border style for L{AuiNotebook}. """
5558
5559         return wx.BORDER_NONE
5560
5561
5562     def NotebookPreview(self, thumbnail_size=200):
5563         """
5564         Generates a preview of all the pages in the notebook (MSW and GTK only).
5565
5566         :param `thumbnail_size`: the maximum size of every page thumbnail.
5567
5568         :note: this functionality is currently unavailable on wxMac.
5569         """
5570
5571         if wx.Platform == "__WXMAC__":
5572             return False
5573
5574         tabCtrl = self.GetActiveTabCtrl()
5575         activePage = tabCtrl.GetActivePage()
5576         pages = tabCtrl.GetPages()
5577
5578         pageStatus, pageText = [], []
5579
5580         for indx, page in enumerate(pages):
5581
5582             pageStatus.append(page.enabled)
5583
5584             if not page.enabled:
5585                 continue
5586
5587             self.SetSelectionToPage(page)
5588             pageText.append(page.caption)
5589
5590             rect = page.window.GetScreenRect()
5591             bmp = RescaleScreenShot(TakeScreenShot(rect), thumbnail_size)
5592
5593             page.enabled = False
5594             if indx == 0:
5595                 il = wx.ImageList(bmp.GetWidth(), bmp.GetHeight(), True)
5596
5597             il.Add(bmp)
5598
5599         # create the list control
5600         listCtrl = wx.ListCtrl(self, style=wx.LC_ICON|wx.LC_AUTOARRANGE|wx.LC_HRULES|wx.LC_VRULES,
5601                                name="__fake__page__")
5602
5603         # assign the image list to it
5604         listCtrl.AssignImageList(il, wx.IMAGE_LIST_NORMAL)
5605         listCtrl.__previousStatus = [activePage, pageStatus]
5606
5607         # create some items for the list
5608         for indx, text in enumerate(pageText):
5609             listCtrl.InsertImageStringItem(10000, text, indx)
5610
5611         self.AddPage(listCtrl, "AuiNotebook Preview", True, bitmap=auinotebook_preview.GetBitmap(), disabled_bitmap=wx.NullBitmap)
5612         return True
5613
5614
5615     def SetRenamable(self, page_idx, renamable):
5616         """
5617         Sets whether a tab can be renamed via a left double-click or not.
5618
5619         :param `page_idx`: the page index;
5620         :param `renamable`: ``True`` if the page can be renamed.
5621         """
5622
5623         if page_idx >= self._tabs.GetPageCount():
5624             return False
5625
5626         # update our own tab catalog
5627         page_info = self._tabs.GetPage(page_idx)
5628         page_info.renamable = renamable
5629
5630         # update what's on screen
5631         ctrl, ctrl_idx = self.FindTab(page_info.window)
5632         if not ctrl:
5633             return False
5634
5635         info = ctrl.GetPage(ctrl_idx)
5636         info.renamable = page_info.renamable
5637
5638         return True
5639
5640
5641     def IsRenamable(self, page_idx):
5642         """
5643         Returns whether a tab can be renamed or not.
5644
5645         :param `page_idx`: the page index.
5646
5647         :returns: ``True`` is a page can be renamed, ``False`` otherwise.
5648         """
5649
5650         if page_idx >= self._tabs.GetPageCount():
5651             return False
5652
5653         page_info = self._tabs.GetPage(page_idx)
5654         return page_info.renamable
5655
5656
5657     def OnRenameCancelled(self, page_index):
5658         """
5659         Called by L{TabTextCtrl}, to cancel the changes and to send the
5660         `EVT_AUINOTEBOOK_END_LABEL_EDIT` event.
5661
5662         :param `page_index`: the page index in the notebook.
5663         """
5664
5665         # let owner know that the edit was cancelled
5666         evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_END_LABEL_EDIT, self.GetId())
5667
5668         evt.SetSelection(page_index)
5669         evt.SetEventObject(self)
5670         evt.SetLabel("")
5671         evt.SetEditCanceled(True)
5672         self.GetEventHandler().ProcessEvent(evt)
5673
5674
5675     def OnRenameAccept(self, page_index, value):
5676         """
5677         Called by L{TabTextCtrl}, to accept the changes and to send the
5678         `EVT_AUINOTEBOOK_END_LABEL_EDIT` event.
5679
5680         :param `page_index`: the page index in the notebook;
5681         :param `value`: the new label for the tab.
5682         """
5683
5684         evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_END_LABEL_EDIT, self.GetId())
5685         evt.SetSelection(page_index)
5686         evt.SetEventObject(self)
5687         evt.SetLabel(value)
5688         evt.SetEditCanceled(False)
5689
5690         return not self.GetEventHandler().ProcessEvent(evt) or evt.IsAllowed()
5691
5692
5693     def ResetTextControl(self):
5694         """ Called by L{TabTextCtrl} when it marks itself for deletion. """
5695
5696         if not self._textCtrl:
5697             return
5698
5699         self._textCtrl.Destroy()
5700         self._textCtrl = None
5701
5702         # tab height might have changed
5703         self.UpdateTabCtrlHeight(force=True)
5704
5705
5706     def EditTab(self, page_index):
5707         """
5708         Starts the editing of an item label, sending a `EVT_AUINOTEBOOK_BEGIN_LABEL_EDIT` event.
5709
5710         :param `page_index`: the page index we want to edit.
5711         """
5712
5713         if page_index >= self._tabs.GetPageCount():
5714             return False
5715
5716         if not self.IsRenamable(page_index):
5717             return False
5718
5719         page_info = self._tabs.GetPage(page_index)
5720         ctrl, ctrl_idx = self.FindTab(page_info.window)
5721         if not ctrl:
5722             return False
5723
5724         evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BEGIN_LABEL_EDIT, self.GetId())
5725         evt.SetSelection(page_index)
5726         evt.SetEventObject(self)
5727         if self.GetEventHandler().ProcessEvent(evt) and not evt.IsAllowed():
5728             # vetoed by user
5729             return False
5730
5731         if self._textCtrl is not None and page_info != self._textCtrl.item():
5732             self._textCtrl.StopEditing()
5733
5734         self._textCtrl = TabTextCtrl(ctrl, page_info, page_index)
5735         self._textCtrl.SetFocus()
5736
5737         return True