corpus
[iramuteq] / agw / 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
2686         wx.PyPanel.__init__(self, parent, id, pos, size, style|wx.BORDER_NONE|wx.TAB_TRAVERSAL)
2687         self._mgr = framemanager.AuiManager()
2688         self._tabs = AuiTabContainer(self)
2689
2690         self.InitNotebook(agwStyle)
2691
2692
2693     def GetTabContainer(self):
2694         """ Returns the instance of L{AuiTabContainer}. """
2695
2696         return self._tabs
2697
2698
2699     def InitNotebook(self, agwStyle):
2700         """
2701         Contains common initialization code called by all constructors.
2702
2703         :param `agwStyle`: the notebook style.
2704
2705         :see: L{__init__}
2706         """
2707
2708         self.SetName("AuiNotebook")
2709         self._agwFlags = agwStyle
2710
2711         self._popupWin = None
2712         self._naviIcon = None
2713         self._imageList = None
2714         self._last_drag_x = 0
2715
2716         self._normal_font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
2717         self._selected_font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
2718         self._selected_font.SetWeight(wx.BOLD)
2719
2720         self.SetArtProvider(TA.AuiDefaultTabArt())
2721
2722         self._dummy_wnd = wx.Window(self, wx.ID_ANY, wx.Point(0, 0), wx.Size(0, 0))
2723         self._dummy_wnd.SetSize((200, 200))
2724         self._dummy_wnd.Show(False)
2725
2726         self._mgr.SetManagedWindow(self)
2727         self._mgr.SetAGWFlags(AUI_MGR_DEFAULT)
2728         self._mgr.SetDockSizeConstraint(1.0, 1.0) # no dock size constraint
2729
2730         self._mgr.AddPane(self._dummy_wnd, framemanager.AuiPaneInfo().Name("dummy").Bottom().CaptionVisible(False).Show(False))
2731         self._mgr.Update()
2732
2733         self.Bind(wx.EVT_SIZE, self.OnSize)
2734         self.Bind(wx.EVT_CHILD_FOCUS, self.OnChildFocusNotebook)
2735         self.Bind(EVT_AUINOTEBOOK_PAGE_CHANGING, self.OnTabClicked,
2736                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2737         self.Bind(EVT_AUINOTEBOOK_BEGIN_DRAG, self.OnTabBeginDrag,
2738                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2739         self.Bind(EVT_AUINOTEBOOK_END_DRAG, self.OnTabEndDrag,
2740                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2741         self.Bind(EVT_AUINOTEBOOK_DRAG_MOTION, self.OnTabDragMotion,
2742                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2743         self.Bind(EVT_AUINOTEBOOK_CANCEL_DRAG, self.OnTabCancelDrag,
2744                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2745         self.Bind(EVT_AUINOTEBOOK_BUTTON, self.OnTabButton,
2746                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2747         self.Bind(EVT_AUINOTEBOOK_TAB_MIDDLE_DOWN, self.OnTabMiddleDown,
2748                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2749         self.Bind(EVT_AUINOTEBOOK_TAB_MIDDLE_UP, self.OnTabMiddleUp,
2750                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2751         self.Bind(EVT_AUINOTEBOOK_TAB_RIGHT_DOWN, self.OnTabRightDown,
2752                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2753         self.Bind(EVT_AUINOTEBOOK_TAB_RIGHT_UP, self.OnTabRightUp,
2754                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2755         self.Bind(EVT_AUINOTEBOOK_BG_DCLICK, self.OnTabBgDClick,
2756                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2757         self.Bind(EVT_AUINOTEBOOK_TAB_DCLICK, self.OnTabDClick,
2758                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2759
2760         self.Bind(wx.EVT_NAVIGATION_KEY, self.OnNavigationKeyNotebook)
2761
2762
2763     def SetArtProvider(self, art):
2764         """
2765         Sets the art provider to be used by the notebook.
2766
2767         :param `art`: an art provider.
2768         """
2769
2770         self._tabs.SetArtProvider(art)
2771         self.UpdateTabCtrlHeight(force=True)
2772
2773
2774     def SavePerspective(self):
2775         """
2776         Saves the entire user interface layout into an encoded string, which can then
2777         be stored by the application (probably using `wx.Config`). When a perspective
2778         is restored using L{LoadPerspective}, the entire user interface will return
2779         to the state it was when the perspective was saved.
2780         """
2781
2782         # Build list of panes/tabs
2783         tabs = ""
2784         all_panes = self._mgr.GetAllPanes()
2785
2786         for pane in all_panes:
2787
2788             if pane.name == "dummy":
2789                 continue
2790
2791             tabframe = pane.window
2792
2793             if tabs:
2794                 tabs += "|"
2795
2796             tabs += pane.name + "="
2797
2798             # add tab id's
2799             page_count = tabframe._tabs.GetPageCount()
2800
2801             for p in xrange(page_count):
2802
2803                 page = tabframe._tabs.GetPage(p)
2804                 page_idx = self._tabs.GetIdxFromWindow(page.window)
2805
2806                 if p:
2807                     tabs += ","
2808
2809                 if p == tabframe._tabs.GetActivePage():
2810                     tabs += "+"
2811                 elif page_idx == self._curpage:
2812                     tabs += "*"
2813
2814                 tabs += "%u"%page_idx
2815
2816         tabs += "@"
2817
2818         # Add frame perspective
2819         tabs += self._mgr.SavePerspective()
2820
2821         return tabs
2822
2823
2824     def LoadPerspective(self, layout):
2825         """
2826         Loads a layout which was saved with L{SavePerspective}.
2827
2828         :param `layout`: a string which contains a saved L{AuiNotebook} layout.
2829         """
2830
2831         # Remove all tab ctrls (but still keep them in main index)
2832         tab_count = self._tabs.GetPageCount()
2833         for i in xrange(tab_count):
2834             wnd = self._tabs.GetWindowFromIdx(i)
2835
2836             # find out which onscreen tab ctrl owns this tab
2837             ctrl, ctrl_idx = self.FindTab(wnd)
2838             if not ctrl:
2839                 return False
2840
2841             # remove the tab from ctrl
2842             if not ctrl.RemovePage(wnd):
2843                 return False
2844
2845         self.RemoveEmptyTabFrames()
2846
2847         sel_page = 0
2848         tabs = layout[0:layout.index("@")]
2849         to_break1 = False
2850
2851         while 1:
2852
2853             if "|" not in tabs:
2854                 to_break1 = True
2855                 tab_part = tabs
2856             else:
2857                 tab_part = tabs[0:tabs.index('|')]
2858
2859             if "=" not in tab_part:
2860                 # No pages in this perspective...
2861                 return False
2862
2863             # Get pane name
2864             pane_name = tab_part[0:tab_part.index("=")]
2865
2866             # create a new tab frame
2867             new_tabs = TabFrame(self)
2868             self._tab_id_counter += 1
2869             new_tabs._tabs = AuiTabCtrl(self, self._tab_id_counter)
2870             new_tabs._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone())
2871             new_tabs.SetTabCtrlHeight(self._tab_ctrl_height)
2872             new_tabs._tabs.SetAGWFlags(self._agwFlags)
2873             dest_tabs = new_tabs._tabs
2874
2875             # create a pane info structure with the information
2876             # about where the pane should be added
2877             pane_info = framemanager.AuiPaneInfo().Name(pane_name).Bottom().CaptionVisible(False)
2878             self._mgr.AddPane(new_tabs, pane_info)
2879
2880             # Get list of tab id's and move them to pane
2881             tab_list = tab_part[tab_part.index("=")+1:]
2882             to_break2, active_found = False, False
2883
2884             while 1:
2885                 if "," not in tab_list:
2886                     to_break2 = True
2887                     tab = tab_list
2888                 else:
2889                     tab = tab_list[0:tab_list.index(",")]
2890                     tab_list = tab_list[tab_list.index(",")+1:]
2891
2892                 # Check if this page has an 'active' marker
2893                 c = tab[0]
2894                 if c in ['+', '*']:
2895                     tab = tab[1:]
2896
2897                 tab_idx = int(tab)
2898                 if tab_idx >= self.GetPageCount():
2899                     to_break1 = True
2900                     break
2901
2902                 # Move tab to pane
2903                 page = self._tabs.GetPage(tab_idx)
2904                 newpage_idx = dest_tabs.GetPageCount()
2905                 dest_tabs.InsertPage(page.window, page, newpage_idx)
2906
2907                 if c == '+':
2908                     dest_tabs.SetActivePage(newpage_idx)
2909                     active_found = True
2910                 elif c == '*':
2911                     sel_page = tab_idx
2912
2913                 if to_break2:
2914                     break
2915
2916             if not active_found:
2917                 dest_tabs.SetActivePage(0)
2918
2919             new_tabs.DoSizing()
2920             dest_tabs.DoShowHide()
2921             dest_tabs.Refresh()
2922
2923             if to_break1:
2924                 break
2925
2926             tabs = tabs[tabs.index('|')+1:]
2927
2928         # Load the frame perspective
2929         frames = layout[layout.index('@')+1:]
2930         self._mgr.LoadPerspective(frames)
2931
2932         # Force refresh of selection
2933         self._curpage = -1
2934         self.SetSelection(sel_page)
2935
2936         return True
2937
2938
2939     def SetTabCtrlHeight(self, height):
2940         """
2941         Sets the tab height.
2942
2943         By default, the tab control height is calculated by measuring the text
2944         height and bitmap sizes on the tab captions.
2945
2946         Calling this method will override that calculation and set the tab control
2947         to the specified height parameter. A call to this method will override
2948         any call to L{SetUniformBitmapSize}. Specifying -1 as the height will
2949         return the control to its default auto-sizing behaviour.
2950
2951         :param `height`: the tab control area height.
2952         """
2953
2954         self._requested_tabctrl_height = height
2955
2956         # if window is already initialized, recalculate the tab height
2957         if self._dummy_wnd:
2958             self.UpdateTabCtrlHeight()
2959
2960
2961     def SetUniformBitmapSize(self, size):
2962         """
2963         Ensures that all tabs will have the same height, even if some tabs
2964         don't have bitmaps. Passing ``wx.DefaultSize`` to this
2965         function will instruct the control to use dynamic tab height, which is
2966         the default behaviour. Under the default behaviour, when a tab with a
2967         large bitmap is added, the tab control's height will automatically
2968         increase to accommodate the larger bitmap.
2969
2970         :param `size`: an instance of `wx.Size` specifying the tab bitmap size.
2971         """
2972
2973         self._requested_bmp_size = wx.Size(*size)
2974
2975         # if window is already initialized, recalculate the tab height
2976         if self._dummy_wnd:
2977             self.UpdateTabCtrlHeight()
2978
2979
2980     def UpdateTabCtrlHeight(self, force=False):
2981         """
2982         UpdateTabCtrlHeight() does the actual tab resizing. It's meant
2983         to be used interally.
2984
2985         :param `force`: ``True`` to force the tab art to repaint.
2986         """
2987
2988         # get the tab ctrl height we will use
2989         height = self.CalculateTabCtrlHeight()
2990
2991         # if the tab control height needs to change, update
2992         # all of our tab controls with the new height
2993         if self._tab_ctrl_height != height or force:
2994             art = self._tabs.GetArtProvider()
2995
2996             self._tab_ctrl_height = height
2997
2998             all_panes = self._mgr.GetAllPanes()
2999             for pane in all_panes:
3000
3001                 if pane.name == "dummy":
3002                     continue
3003
3004                 tab_frame = pane.window
3005                 tabctrl = tab_frame._tabs
3006                 tab_frame.SetTabCtrlHeight(self._tab_ctrl_height)
3007                 tabctrl.SetArtProvider(art.Clone())
3008                 tab_frame.DoSizing()
3009
3010
3011     def UpdateHintWindowSize(self):
3012         """ Updates the L{AuiManager} hint window size. """
3013
3014         size = self.CalculateNewSplitSize()
3015
3016         # the placeholder hint window should be set to this size
3017         info = self._mgr.GetPane("dummy")
3018
3019         if info.IsOk():
3020             info.MinSize(size)
3021             info.BestSize(size)
3022             self._dummy_wnd.SetSize(size)
3023
3024
3025     def CalculateNewSplitSize(self):
3026         """ Calculates the size of the new split. """
3027
3028         # count number of tab controls
3029         tab_ctrl_count = 0
3030         all_panes = self._mgr.GetAllPanes()
3031
3032         for pane in all_panes:
3033             if pane.name == "dummy":
3034                 continue
3035
3036             tab_ctrl_count += 1
3037
3038         # if there is only one tab control, the first split
3039         # should happen around the middle
3040         if tab_ctrl_count < 2:
3041             new_split_size = self.GetClientSize()
3042             new_split_size.x /= 2
3043             new_split_size.y /= 2
3044
3045         else:
3046
3047             # this is in place of a more complicated calculation
3048             # that needs to be implemented
3049             new_split_size = wx.Size(180, 180)
3050
3051         return new_split_size
3052
3053
3054     def CalculateTabCtrlHeight(self):
3055         """ Calculates the tab control area height. """
3056
3057         # if a fixed tab ctrl height is specified,
3058         # just return that instead of calculating a
3059         # tab height
3060         if self._requested_tabctrl_height != -1:
3061             return self._requested_tabctrl_height
3062
3063         # find out new best tab height
3064         art = self._tabs.GetArtProvider()
3065
3066         return art.GetBestTabCtrlSize(self, self._tabs.GetPages(), self._requested_bmp_size)
3067
3068
3069     def GetArtProvider(self):
3070         """ Returns the associated art provider. """
3071
3072         return self._tabs.GetArtProvider()
3073
3074
3075     def SetAGWWindowStyleFlag(self, agwStyle):
3076         """
3077         Sets the AGW-specific style of the window.
3078
3079         :param `agwStyle`: the new window style. This can be a combination of the following bits:
3080
3081          ==================================== ==================================
3082          Flag name                            Description
3083          ==================================== ==================================
3084          ``AUI_NB_TOP``                       With this style, tabs are drawn along the top of the notebook
3085          ``AUI_NB_LEFT``                      With this style, tabs are drawn along the left of the notebook. Not implemented yet.
3086          ``AUI_NB_RIGHT``                     With this style, tabs are drawn along the right of the notebook. Not implemented yet.
3087          ``AUI_NB_BOTTOM``                    With this style, tabs are drawn along the bottom of the notebook
3088          ``AUI_NB_TAB_SPLIT``                 Allows the tab control to be split by dragging a tab
3089          ``AUI_NB_TAB_MOVE``                  Allows a tab to be moved horizontally by dragging
3090          ``AUI_NB_TAB_EXTERNAL_MOVE``         Allows a tab to be moved to another tab control
3091          ``AUI_NB_TAB_FIXED_WIDTH``           With this style, all tabs have the same width
3092          ``AUI_NB_SCROLL_BUTTONS``            With this style, left and right scroll buttons are displayed
3093          ``AUI_NB_WINDOWLIST_BUTTON``         With this style, a drop-down list of windows is available
3094          ``AUI_NB_CLOSE_BUTTON``              With this style, a close button is available on the tab bar
3095          ``AUI_NB_CLOSE_ON_ACTIVE_TAB``       With this style, a close button is available on the active tab
3096          ``AUI_NB_CLOSE_ON_ALL_TABS``         With this style, a close button is available on all tabs
3097          ``AUI_NB_MIDDLE_CLICK_CLOSE``        Allows to close L{AuiNotebook} tabs by mouse middle button click
3098          ``AUI_NB_SUB_NOTEBOOK``              This style is used by L{AuiManager} to create automatic AuiNotebooks
3099          ``AUI_NB_HIDE_ON_SINGLE_TAB``        Hides the tab window if only one tab is present
3100          ``AUI_NB_SMART_TABS``                Use Smart Tabbing, like ``Alt`` + ``Tab`` on Windows
3101          ``AUI_NB_USE_IMAGES_DROPDOWN``       Uses images on dropdown window list menu instead of check items
3102          ``AUI_NB_CLOSE_ON_TAB_LEFT``         Draws the tab close button on the left instead of on the right (a la Camino browser)
3103          ``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
3104          ``AUI_NB_DRAW_DND_TAB``              Draws an image representation of a tab while dragging (on by default)
3105          ``AUI_NB_ORDER_BY_ACCESS``           Tab navigation order by last access time for the tabs
3106          ``AUI_NB_NO_TAB_FOCUS``              Don't draw tab focus rectangle
3107          ==================================== ==================================
3108
3109         :note: Please note that some styles cannot be changed after the window
3110          creation and that `Refresh` might need to be be called after changing the
3111          others for the change to take place immediately.
3112
3113         :todo: Implementation of flags ``AUI_NB_RIGHT`` and ``AUI_NB_LEFT``.
3114         """
3115
3116         self._agwFlags = agwStyle
3117
3118         # if the control is already initialized
3119         if self._mgr.GetManagedWindow() == self:
3120
3121             # let all of the tab children know about the new style
3122
3123             all_panes = self._mgr.GetAllPanes()
3124             for pane in all_panes:
3125                 if pane.name == "dummy":
3126                     continue
3127
3128                 tabframe = pane.window
3129                 tabctrl = tabframe._tabs
3130                 tabctrl.SetAGWFlags(self._agwFlags)
3131                 tabframe.DoSizing()
3132                 tabctrl.Refresh()
3133                 tabctrl.Update()
3134
3135
3136     def GetAGWWindowStyleFlag(self):
3137         """
3138         Returns the AGW-specific style of the window.
3139
3140         :see: L{SetAGWWindowStyleFlag} for a list of possible AGW-specific window styles.
3141         """
3142
3143         return self._agwFlags
3144
3145
3146     def AddPage(self, page, caption, select=False, bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap, control=None):
3147         """
3148         Adds a page. If the `select` parameter is ``True``, calling this will generate a
3149         page change event.
3150
3151         :param `page`: the page to be added;
3152         :param `caption`: specifies the text for the new page;
3153         :param `select`: specifies whether the page should be selected;
3154         :param `bitmap`: the `wx.Bitmap` to display in the enabled tab;
3155         :param `disabled_bitmap`: the `wx.Bitmap` to display in the disabled tab;
3156         :param `control`: a `wx.Window` instance inside a tab (or ``None``).
3157         """
3158
3159         return self.InsertPage(self.GetPageCount(), page, caption, select, bitmap, disabled_bitmap, control)
3160
3161
3162     def InsertPage(self, page_idx, page, caption, select=False, bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap,
3163                    control=None):
3164         """
3165         This is similar to L{AddPage}, but allows the ability to specify the insert location.
3166
3167         :param `page_idx`: specifies the position for the new page;
3168         :param `page`: the page to be added;
3169         :param `caption`: specifies the text for the new page;
3170         :param `select`: specifies whether the page should be selected;
3171         :param `bitmap`: the `wx.Bitmap` to display in the enabled tab;
3172         :param `disabled_bitmap`: the `wx.Bitmap` to display in the disabled tab;
3173         :param `control`: a `wx.Window` instance inside a tab (or ``None``).
3174         """
3175
3176         if not page:
3177             return False
3178
3179         page.Reparent(self)
3180         info = AuiNotebookPage()
3181         info.window = page
3182         info.caption = caption
3183         info.bitmap = bitmap
3184         info.active = False
3185         info.control = control
3186
3187         originalPaneMgr = framemanager.GetManager(page)
3188         if originalPaneMgr:
3189             originalPane = originalPaneMgr.GetPane(page)
3190
3191             if originalPane:
3192                 info.hasCloseButton = originalPane.HasCloseButton()
3193
3194         if bitmap.IsOk() and not disabled_bitmap.IsOk():
3195             disabled_bitmap = MakeDisabledBitmap(bitmap)
3196             info.dis_bitmap = disabled_bitmap
3197
3198         # if there are currently no tabs, the first added
3199         # tab must be active
3200         if self._tabs.GetPageCount() == 0:
3201             info.active = True
3202
3203         self._tabs.InsertPage(page, info, page_idx)
3204
3205         # if that was the first page added, even if
3206         # select is False, it must become the "current page"
3207         # (though no select events will be fired)
3208         if not select and self._tabs.GetPageCount() == 1:
3209             select = True
3210
3211         active_tabctrl = self.GetActiveTabCtrl()
3212         if page_idx >= active_tabctrl.GetPageCount():
3213             active_tabctrl.AddPage(page, info)
3214         else:
3215             active_tabctrl.InsertPage(page, info, page_idx)
3216
3217         force = False
3218         if control:
3219             force = True
3220             control.Reparent(active_tabctrl)
3221             control.Show()
3222
3223         self.UpdateTabCtrlHeight(force=force)
3224         self.DoSizing()
3225         active_tabctrl.DoShowHide()
3226
3227         # adjust selected index
3228         if self._curpage >= page_idx:
3229             self._curpage += 1
3230
3231         if select:
3232             self.SetSelectionToWindow(page)
3233
3234         return True
3235
3236
3237     def DeletePage(self, page_idx):
3238         """
3239         Deletes a page at the given index. Calling this method will generate a page
3240         change event.
3241
3242         :param `page_idx`: the page index to be deleted.
3243
3244         :note: L{DeletePage} removes a tab from the multi-notebook, and destroys the window as well.
3245
3246         :see: L{RemovePage}
3247         """
3248
3249         if page_idx >= self._tabs.GetPageCount():
3250             return False
3251
3252         wnd = self._tabs.GetWindowFromIdx(page_idx)
3253         # hide the window in advance, as this will
3254         # prevent flicker
3255         wnd.Show(False)
3256
3257         self.RemoveControlFromPage(page_idx)
3258
3259         if not self.RemovePage(page_idx):
3260             return False
3261
3262         wnd.Destroy()
3263
3264         return True
3265
3266
3267     def RemovePage(self, page_idx):
3268         """
3269         Removes a page, without deleting the window pointer.
3270
3271         :param `page_idx`: the page index to be removed.
3272
3273         :note: L{RemovePage} removes a tab from the multi-notebook, but does not destroy the window.
3274
3275         :see: L{DeletePage}
3276         """
3277
3278         # save active window pointer
3279         active_wnd = None
3280         if self._curpage >= 0:
3281             active_wnd = self._tabs.GetWindowFromIdx(self._curpage)
3282
3283         # save pointer of window being deleted
3284         wnd = self._tabs.GetWindowFromIdx(page_idx)
3285         new_active = None
3286
3287         # make sure we found the page
3288         if not wnd:
3289             return False
3290
3291         # find out which onscreen tab ctrl owns this tab
3292         ctrl, ctrl_idx = self.FindTab(wnd)
3293         if not ctrl:
3294             return False
3295
3296         currentPage = ctrl.GetPage(ctrl_idx)
3297         is_curpage = (self._curpage == page_idx)
3298         is_active_in_split = currentPage.active
3299
3300         # remove the tab from main catalog
3301         if not self._tabs.RemovePage(wnd):
3302             return False
3303
3304         # remove the tab from the onscreen tab ctrl
3305         ctrl.RemovePage(wnd)
3306
3307         if is_active_in_split:
3308
3309             ctrl_new_page_count = ctrl.GetPageCount()
3310
3311             if ctrl_idx >= ctrl_new_page_count:
3312                 ctrl_idx = ctrl_new_page_count - 1
3313
3314             if ctrl_idx >= 0 and ctrl_idx < ctrl.GetPageCount():
3315
3316                 ctrl_idx = self.FindNextActiveTab(ctrl_idx, ctrl)
3317
3318                 # set new page as active in the tab split
3319                 ctrl.SetActivePage(ctrl_idx)
3320
3321                 # if the page deleted was the current page for the
3322                 # entire tab control, then record the window
3323                 # pointer of the new active page for activation
3324                 if is_curpage:
3325                     new_active = ctrl.GetWindowFromIdx(ctrl_idx)
3326
3327         else:
3328
3329             # we are not deleting the active page, so keep it the same
3330             new_active = active_wnd
3331
3332         if not new_active:
3333
3334             # we haven't yet found a new page to active,
3335             # so select the next page from the main tab
3336             # catalogue
3337
3338             if 0 <= page_idx < self._tabs.GetPageCount():
3339                 new_active = self._tabs.GetPage(page_idx).window
3340             if not new_active and self._tabs.GetPageCount() > 0:
3341                 new_active = self._tabs.GetPage(0).window
3342
3343         self.RemoveEmptyTabFrames()
3344
3345         # set new active pane
3346         if new_active:
3347             if not self.IsBeingDeleted():
3348                 self._curpage = -1
3349                 self.SetSelectionToWindow(new_active)
3350         else:
3351             self._curpage = -1
3352             self._tabs.SetNoneActive()
3353
3354         return True
3355
3356
3357     def FindNextActiveTab(self, ctrl_idx, ctrl):
3358         """
3359         Finds the next active tab (used mainly when L{AuiNotebook} has inactive/disabled
3360         tabs in it).
3361
3362         :param `ctrl_idx`: the index of the first (most obvious) tab to check for active status;
3363         :param `ctrl`: an instance of L{AuiTabCtrl}.
3364         """
3365
3366         if self.GetEnabled(ctrl_idx):
3367             return ctrl_idx
3368
3369         for indx in xrange(ctrl_idx, ctrl.GetPageCount()):
3370             if self.GetEnabled(indx):
3371                 return indx
3372
3373         for indx in xrange(ctrl_idx, -1, -1):
3374             if self.GetEnabled(indx):
3375                 return indx
3376
3377         return 0
3378
3379
3380     def HideAllTabs(self, hidden=True):
3381         """
3382         Hides all tabs on the L{AuiNotebook} control.
3383
3384         :param `hidden`: if ``True`` hides all tabs.
3385         """
3386
3387         self._hide_tabs = hidden
3388
3389
3390     def SetSashDClickUnsplit(self, unsplit=True):
3391         """
3392         Sets whether to unsplit a splitted L{AuiNotebook} when double-clicking on a sash.
3393
3394         :param `unsplit`: ``True`` to unsplit on sash double-clicking, ``False`` otherwise.
3395         """
3396
3397         self._sash_dclick_unsplit = unsplit
3398
3399
3400     def GetSashDClickUnsplit(self):
3401         """
3402         Returns whether a splitted L{AuiNotebook} can be unsplitted by double-clicking
3403         on the splitter sash.
3404         """
3405
3406         return self._sash_dclick_unsplit
3407
3408
3409     def SetMinMaxTabWidth(self, minTabWidth, maxTabWidth):
3410         """
3411         Sets the minimum and/or the maximum tab widths for L{AuiNotebook} when the
3412         ``AUI_NB_TAB_FIXED_WIDTH`` style is defined.
3413
3414         Pass -1 to either `minTabWidth` or `maxTabWidth` to reset to the default tab
3415         width behaviour for L{AuiNotebook}.
3416
3417         :param `minTabWidth`: the minimum allowed tab width, in pixels;
3418         :param `maxTabWidth`: the maximum allowed tab width, in pixels.
3419
3420         :note: Minimum and maximum tabs widths are used only when the ``AUI_NB_TAB_FIXED_WIDTH``
3421          style is present.
3422         """
3423
3424         if minTabWidth > maxTabWidth:
3425             raise Exception("Minimum tab width must be less or equal than maximum tab width")
3426
3427         self._tabBounds = (minTabWidth, maxTabWidth)
3428         self.SetAGWWindowStyleFlag(self._agwFlags)
3429
3430
3431     def GetMinMaxTabWidth(self):
3432         """
3433         Returns the minimum and the maximum tab widths for L{AuiNotebook} when the
3434         ``AUI_NB_TAB_FIXED_WIDTH`` style is defined.
3435
3436         :note: Minimum and maximum tabs widths are used only when the ``AUI_NB_TAB_FIXED_WIDTH``
3437          style is present.
3438
3439         :see: L{SetMinMaxTabWidth} for more information.
3440         """
3441
3442         return self._tabBounds
3443
3444
3445     def GetPageIndex(self, page_wnd):
3446         """
3447         Returns the page index for the specified window. If the window is not
3448         found in the notebook, ``wx.NOT_FOUND`` is returned.
3449         """
3450
3451         return self._tabs.GetIdxFromWindow(page_wnd)
3452
3453
3454     def SetPageText(self, page_idx, text):
3455         """
3456         Sets the tab label for the page.
3457
3458         :param `page_idx`: the page index;
3459         :param `text`: the new tab label.
3460         """
3461
3462         if page_idx >= self._tabs.GetPageCount():
3463             return False
3464
3465         # update our own tab catalog
3466         page_info = self._tabs.GetPage(page_idx)
3467         should_refresh = page_info.caption != text
3468         page_info.caption = text
3469
3470         # update what's on screen
3471         ctrl, ctrl_idx = self.FindTab(page_info.window)
3472         if not ctrl:
3473             return False
3474
3475         info = ctrl.GetPage(ctrl_idx)
3476         should_refresh = should_refresh or info.caption != text
3477         info.caption = text
3478
3479         if should_refresh:
3480             ctrl.Refresh()
3481             ctrl.Update()
3482
3483         self.UpdateTabCtrlHeight(force=True)
3484
3485         return True
3486
3487
3488     def GetPageText(self, page_idx):
3489         """
3490         Returns the tab label for the page.
3491
3492         :param `page_idx`: the page index.
3493         """
3494
3495         if page_idx >= self._tabs.GetPageCount():
3496             return ""
3497
3498         # update our own tab catalog
3499         page_info = self._tabs.GetPage(page_idx)
3500         return page_info.caption
3501
3502
3503     def SetPageBitmap(self, page_idx, bitmap):
3504         """
3505         Sets the tab bitmap for the page.
3506
3507         :param `page_idx`: the page index;
3508         :param `bitmap`: an instance of `wx.Bitmap`.
3509         """
3510
3511         if page_idx >= self._tabs.GetPageCount():
3512             return False
3513
3514         # update our own tab catalog
3515         page_info = self._tabs.GetPage(page_idx)
3516         should_refresh = page_info.bitmap is not bitmap
3517         page_info.bitmap = bitmap
3518         if bitmap.IsOk() and not page_info.dis_bitmap.IsOk():
3519             page_info.dis_bitmap = MakeDisabledBitmap(bitmap)
3520
3521         # tab height might have changed
3522         self.UpdateTabCtrlHeight()
3523
3524         # update what's on screen
3525         ctrl, ctrl_idx = self.FindTab(page_info.window)
3526         if not ctrl:
3527             return False
3528
3529         info = ctrl.GetPage(ctrl_idx)
3530         should_refresh = should_refresh or info.bitmap is not bitmap
3531         info.bitmap = bitmap
3532         info.dis_bitmap = page_info.dis_bitmap
3533         if should_refresh:
3534             ctrl.Refresh()
3535             ctrl.Update()
3536
3537         return True
3538
3539
3540     def GetPageBitmap(self, page_idx):
3541         """
3542         Returns the tab bitmap for the page.
3543
3544         :param `page_idx`: the page index.
3545         """
3546
3547         if page_idx >= self._tabs.GetPageCount():
3548             return wx.NullBitmap
3549
3550         # update our own tab catalog
3551         page_info = self._tabs.GetPage(page_idx)
3552         return page_info.bitmap
3553
3554
3555     def SetImageList(self, imageList):
3556         """
3557         Sets the image list for the L{AuiNotebook} control.
3558
3559         :param `imageList`: an instance of `wx.ImageList`.
3560         """
3561
3562         self._imageList = imageList
3563
3564
3565     def AssignImageList(self, imageList):
3566         """
3567         Sets the image list for the L{AuiNotebook} control.
3568
3569         :param `imageList`: an instance of `wx.ImageList`.
3570         """
3571
3572         self.SetImageList(imageList)
3573
3574
3575     def GetImageList(self):
3576         """ Returns the associated image list (if any). """
3577
3578         return self._imageList
3579
3580
3581     def SetPageImage(self, page, image):
3582         """
3583         Sets the image index for the given page.
3584
3585         :param `page`: the page index;
3586         :param `image`: an index into the image list which was set with L{SetImageList}.
3587         """
3588
3589         if page >= self._tabs.GetPageCount():
3590             return False
3591
3592         if not isinstance(image, types.IntType):
3593             raise Exception("The image parameter must be an integer, you passed " \
3594                             "%s"%repr(image))
3595
3596         if not self._imageList:
3597             raise Exception("To use SetPageImage you need to associate an image list " \
3598                             "Using SetImageList or AssignImageList")
3599
3600         if image >= self._imageList.GetImageCount():
3601             raise Exception("Invalid image index (%d), the image list contains only" \
3602                             " (%d) bitmaps"%(image, self._imageList.GetImageCount()))
3603
3604         if image == -1:
3605             self.SetPageBitmap(page, wx.NullBitmap)
3606             return
3607
3608         bitmap = self._imageList.GetBitmap(image)
3609         self.SetPageBitmap(page, bitmap)
3610
3611
3612     def GetPageImage(self, page):
3613         """
3614         Returns the image index for the given page.
3615
3616         :param `page`: the given page for which to retrieve the image index.
3617         """
3618
3619         if page >= self._tabs.GetPageCount():
3620             return False
3621
3622         bitmap = self.GetPageBitmap(page)
3623         for indx in xrange(self._imageList.GetImageCount()):
3624             imgListBmp = self._imageList.GetBitmap(indx)
3625             if imgListBmp == bitmap:
3626                 return indx
3627
3628         return wx.NOT_FOUND
3629
3630
3631     def SetPageTextColour(self, page_idx, colour):
3632         """
3633         Sets the tab text colour for the page.
3634
3635         :param `page_idx`: the page index;
3636         :param `colour`: an instance of `wx.Colour`.
3637         """
3638
3639         if page_idx >= self._tabs.GetPageCount():
3640             return False
3641
3642         # update our own tab catalog
3643         page_info = self._tabs.GetPage(page_idx)
3644         should_refresh = page_info.text_colour != colour
3645         page_info.text_colour = colour
3646
3647         # update what's on screen
3648         ctrl, ctrl_idx = self.FindTab(page_info.window)
3649         if not ctrl:
3650             return False
3651
3652         info = ctrl.GetPage(ctrl_idx)
3653         should_refresh = should_refresh or info.text_colour != colour
3654         info.text_colour = page_info.text_colour
3655
3656         if should_refresh:
3657             ctrl.Refresh()
3658             ctrl.Update()
3659
3660         return True
3661
3662
3663     def GetPageTextColour(self, page_idx):
3664         """
3665         Returns the tab text colour for the page.
3666
3667         :param `page_idx`: the page index.
3668         """
3669
3670         if page_idx >= self._tabs.GetPageCount():
3671             return wx.NullColour
3672
3673         # update our own tab catalog
3674         page_info = self._tabs.GetPage(page_idx)
3675         return page_info.text_colour
3676
3677
3678     def AddControlToPage(self, page_idx, control):
3679         """
3680         Adds a control inside a tab (not in the tab area).
3681
3682         :param `page_idx`: the page index;
3683         :param `control`: an instance of `wx.Window`.
3684         """
3685
3686         if page_idx >= self._tabs.GetPageCount():
3687             return False
3688
3689         # update our own tab catalog
3690         page_info = self._tabs.GetPage(page_idx)
3691         page_info.control = control
3692
3693         # tab height might have changed
3694         self.UpdateTabCtrlHeight(force=True)
3695
3696         # update what's on screen
3697         ctrl, ctrl_idx = self.FindTab(page_info.window)
3698         if not ctrl:
3699             return False
3700
3701         control.Reparent(ctrl)
3702
3703         info = ctrl.GetPage(ctrl_idx)
3704         info.control = control
3705         ctrl.Refresh()
3706         ctrl.Update()
3707
3708         return True
3709
3710
3711     def RemoveControlFromPage(self, page_idx):
3712         """
3713         Removes a control from a tab (not from the tab area).
3714
3715         :param `page_idx`: the page index.
3716         """
3717
3718         if page_idx >= self._tabs.GetPageCount():
3719             return False
3720
3721         page_info = self._tabs.GetPage(page_idx)
3722         if page_info.control is None:
3723             return False
3724
3725         page_info.control.Destroy()
3726         page_info.control = None
3727
3728         # tab height might have changed
3729         self.UpdateTabCtrlHeight(force=True)
3730
3731         # update what's on screen
3732         ctrl, ctrl_idx = self.FindTab(page_info.window)
3733         if not ctrl:
3734             return False
3735
3736         info = ctrl.GetPage(ctrl_idx)
3737         info.control = None
3738         ctrl.Refresh()
3739         ctrl.Update()
3740
3741         return True
3742
3743
3744     def SetCloseButton(self, page_idx, hasCloseButton):
3745         """
3746         Sets whether a tab should display a close button or not.
3747
3748         :param `page_idx`: the page index;
3749         :param `hasCloseButton`: ``True`` if the page displays a close button.
3750
3751         :note: This can only be called if ``AUI_NB_CLOSE_ON_ALL_TABS`` is specified.
3752         """
3753
3754         if page_idx >= self._tabs.GetPageCount():
3755             return False
3756
3757         if self._agwFlags & AUI_NB_CLOSE_ON_ALL_TABS == 0:
3758             raise Exception("SetCloseButton can only be used with AUI_NB_CLOSE_ON_ALL_TABS style.")
3759
3760         # update our own tab catalog
3761         page_info = self._tabs.GetPage(page_idx)
3762         page_info.hasCloseButton = hasCloseButton
3763
3764         # update what's on screen
3765         ctrl, ctrl_idx = self.FindTab(page_info.window)
3766         if not ctrl:
3767             return False
3768
3769         info = ctrl.GetPage(ctrl_idx)
3770         info.hasCloseButton = page_info.hasCloseButton
3771         ctrl.Refresh()
3772         ctrl.Update()
3773
3774         return True
3775
3776
3777     def HasCloseButton(self, page_idx):
3778         """
3779         Returns whether a tab displays a close button or not.
3780
3781         :param `page_idx`: the page index.
3782
3783         :note: This can only be called if ``AUI_NB_CLOSE_ON_ALL_TABS`` is specified.
3784         """
3785
3786         if page_idx >= self._tabs.GetPageCount():
3787             return False
3788
3789         page_info = self._tabs.GetPage(page_idx)
3790         return page_info.hasCloseButton
3791
3792
3793     def GetSelection(self):
3794         """ Returns the index of the currently active page, or -1 if none was selected. """
3795
3796         return self._curpage
3797
3798
3799     def GetCurrentPage(self):
3800         """ Returns the currently active page (not the index), or ``None`` if none was selected. """
3801
3802         if self._curpage >= 0 and self._curpage < self._tabs.GetPageCount():
3803             return self.GetPage(self._curpage)
3804
3805         return None
3806
3807
3808     def EnsureVisible(self, indx):
3809         """
3810         Ensures the input page index `indx` is visible.
3811
3812         :param `indx`: the page index.
3813         """
3814
3815         self._tabs.MakeTabVisible(indx, self)
3816
3817
3818     def SetSelection(self, new_page, force=False):
3819         """
3820         Sets the page selection. Calling this method will generate a page change event.
3821
3822         :param `new_page`: the index of the new selection;
3823         :param `force`: whether to force the selection or not.
3824         """
3825         wnd = self._tabs.GetWindowFromIdx(new_page)
3826
3827         #Update page access time
3828         self._tabs.GetPages()[new_page].access_time = datetime.datetime.now()
3829
3830         if not wnd or not self.GetEnabled(new_page):
3831             return self._curpage
3832
3833         # don't change the page unless necessary
3834         # however, clicking again on a tab should give it the focus.
3835         if new_page == self._curpage and not force:
3836
3837             ctrl, ctrl_idx = self.FindTab(wnd)
3838             if wx.Window.FindFocus() != ctrl:
3839                 ctrl.SetFocus()
3840
3841             return self._curpage
3842
3843         evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, self.GetId())
3844         evt.SetSelection(new_page)
3845         evt.SetOldSelection(self._curpage)
3846         evt.SetEventObject(self)
3847
3848         if not self.GetEventHandler().ProcessEvent(evt) or evt.IsAllowed():
3849
3850             old_curpage = self._curpage
3851             self._curpage = new_page
3852
3853             # program allows the page change
3854             evt.SetEventType(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGED)
3855             self.GetEventHandler().ProcessEvent(evt)
3856
3857             if not evt.IsAllowed(): # event is no longer allowed after handler
3858                 return self._curpage
3859
3860             ctrl, ctrl_idx = self.FindTab(wnd)
3861
3862             if ctrl:
3863                 self._tabs.SetActivePage(wnd)
3864                 ctrl.SetActivePage(ctrl_idx)
3865                 self.DoSizing()
3866                 ctrl.DoShowHide()
3867                 ctrl.MakeTabVisible(ctrl_idx, ctrl)
3868
3869                 # set fonts
3870                 all_panes = self._mgr.GetAllPanes()
3871                 for pane in all_panes:
3872                     if pane.name == "dummy":
3873                         continue
3874
3875                     tabctrl = pane.window._tabs
3876                     if tabctrl != ctrl:
3877                         tabctrl.SetSelectedFont(self._normal_font)
3878                     else:
3879                         tabctrl.SetSelectedFont(self._selected_font)
3880
3881                     tabctrl.Refresh()
3882                     tabctrl.Update()
3883
3884                 # Set the focus to the page if we're not currently focused on the tab.
3885                 # This is Firefox-like behaviour.
3886                 if wnd.IsShownOnScreen() and wx.Window.FindFocus() != ctrl:
3887                     wnd.SetFocus()
3888
3889                 return old_curpage
3890
3891         return self._curpage
3892
3893
3894     def SetSelectionToWindow(self, win):
3895         """
3896         Sets the selection based on the input window `win`.
3897
3898         :param `win`: a `wx.Window` derived window.
3899         """
3900
3901         idx = self._tabs.GetIdxFromWindow(win)
3902
3903         if idx == wx.NOT_FOUND:
3904             raise Exception("invalid notebook page")
3905
3906         if not self.GetEnabled(idx):
3907             return
3908
3909         # since a tab was clicked, let the parent know that we received
3910         # the focus, even if we will assign that focus immediately
3911         # to the child tab in the SetSelection call below
3912         # (the child focus event will also let AuiManager, if any,
3913         # know that the notebook control has been activated)
3914
3915         parent = self.GetParent()
3916         if parent:
3917             eventFocus = wx.ChildFocusEvent(self)
3918             parent.GetEventHandler().ProcessEvent(eventFocus)
3919
3920         self.SetSelection(idx)
3921
3922
3923     def SetSelectionToPage(self, page):
3924         """
3925         Sets the selection based on the input page.
3926
3927         :param `page`: an instance of L{AuiNotebookPage}.
3928         """
3929
3930         self.SetSelectionToWindow(page.window)
3931
3932
3933     def GetPageCount(self):
3934         """ Returns the number of pages in the notebook. """
3935
3936         return self._tabs.GetPageCount()
3937
3938
3939     def GetPage(self, page_idx):
3940         """
3941         Returns the page specified by the given index.
3942
3943         :param `page_idx`: the page index.
3944         """
3945
3946         if page_idx >= self._tabs.GetPageCount():
3947             raise Exception("invalid notebook page")
3948
3949         return self._tabs.GetWindowFromIdx(page_idx)
3950
3951
3952     def GetPageInfo(self, page_idx):
3953         """
3954         Returns the L{AuiNotebookPage} info structure specified by the given index.
3955
3956         :param `page_idx`: the page index.
3957         """
3958
3959         if page_idx >= self._tabs.GetPageCount():
3960             raise Exception("invalid notebook page")
3961
3962         return self._tabs.GetPage(page_idx)
3963
3964
3965     def GetEnabled(self, page_idx):
3966         """
3967         Returns whether the page specified by the index `page_idx` is enabled.
3968
3969         :param `page_idx`: the page index.
3970         """
3971
3972         return self._tabs.GetEnabled(page_idx)
3973
3974
3975     def EnableTab(self, page_idx, enable=True):
3976         """
3977         Enables/disables a page in the notebook.
3978
3979         :param `page_idx`: the page index;
3980         :param `enable`: ``True`` to enable the page, ``False`` to disable it.
3981         """
3982
3983         self._tabs.EnableTab(page_idx, enable)
3984         self.Refresh()
3985
3986
3987     def DoSizing(self):
3988         """ Performs all sizing operations in each tab control. """
3989
3990         all_panes = self._mgr.GetAllPanes()
3991         for pane in all_panes:
3992             if pane.name == "dummy":
3993                 continue
3994
3995             tabframe = pane.window
3996             tabframe.DoSizing()
3997
3998
3999     def GetAuiManager(self):
4000         """ Returns the associated L{AuiManager}. """
4001
4002         return self._mgr
4003
4004
4005     def GetActiveTabCtrl(self):
4006         """
4007         Returns the active tab control. It is called to determine which control
4008         gets new windows being added.
4009         """
4010
4011         if self._curpage >= 0 and self._curpage < self._tabs.GetPageCount():
4012
4013             # find the tab ctrl with the current page
4014             ctrl, idx = self.FindTab(self._tabs.GetPage(self._curpage).window)
4015             if ctrl:
4016                 return ctrl
4017
4018         # no current page, just find the first tab ctrl
4019         all_panes = self._mgr.GetAllPanes()
4020         for pane in all_panes:
4021             if pane.name == "dummy":
4022                 continue
4023
4024             tabframe = pane.window
4025             return tabframe._tabs
4026
4027         # If there is no tabframe at all, create one
4028         tabframe = TabFrame(self)
4029         tabframe.SetTabCtrlHeight(self._tab_ctrl_height)
4030         self._tab_id_counter += 1
4031         tabframe._tabs = AuiTabCtrl(self, self._tab_id_counter)
4032
4033         tabframe._tabs.SetAGWFlags(self._agwFlags)
4034         tabframe._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone())
4035         self._mgr.AddPane(tabframe, framemanager.AuiPaneInfo().Center().CaptionVisible(False).
4036                           PaneBorder((self._agwFlags & AUI_NB_SUB_NOTEBOOK) == 0))
4037
4038         self._mgr.Update()
4039
4040         return tabframe._tabs
4041
4042
4043     def FindTab(self, page):
4044         """
4045         Finds the tab control that currently contains the window as well
4046         as the index of the window in the tab control. It returns ``True`` if the
4047         window was found, otherwise ``False``.
4048
4049         :param `page`: an instance of L{AuiNotebookPage}.
4050         """
4051
4052         all_panes = self._mgr.GetAllPanes()
4053         for pane in all_panes:
4054             if pane.name == "dummy":
4055                 continue
4056
4057             tabframe = pane.window
4058
4059             page_idx = tabframe._tabs.GetIdxFromWindow(page)
4060
4061             if page_idx != -1:
4062
4063                 ctrl = tabframe._tabs
4064                 idx = page_idx
4065                 return ctrl, idx
4066
4067         return None, wx.NOT_FOUND
4068
4069
4070     def Split(self, page, direction):
4071         """
4072         Performs a split operation programmatically.
4073
4074         :param `page`: indicates the page that will be split off. This page will also become
4075          the active page after the split.
4076         :param `direction`: specifies where the pane should go, it should be one of the
4077          following: ``wx.TOP``, ``wx.BOTTOM``, ``wx.LEFT``, or ``wx.RIGHT``.
4078         """
4079
4080         cli_size = self.GetClientSize()
4081
4082         # get the page's window pointer
4083         wnd = self.GetPage(page)
4084         if not wnd:
4085             return
4086
4087         # notebooks with 1 or less pages can't be split
4088         if self.GetPageCount() < 2:
4089             return
4090
4091         # find out which tab control the page currently belongs to
4092
4093         src_tabs, src_idx = self.FindTab(wnd)
4094         if not src_tabs:
4095             return
4096
4097         # choose a split size
4098         if self.GetPageCount() > 2:
4099             split_size = self.CalculateNewSplitSize()
4100         else:
4101             # because there are two panes, always split them
4102             # equally
4103             split_size = self.GetClientSize()
4104             split_size.x /= 2
4105             split_size.y /= 2
4106
4107         # create a new tab frame
4108         new_tabs = TabFrame(self)
4109         new_tabs._rect = wx.RectPS(wx.Point(0, 0), split_size)
4110         new_tabs.SetTabCtrlHeight(self._tab_ctrl_height)
4111         self._tab_id_counter += 1
4112         new_tabs._tabs = AuiTabCtrl(self, self._tab_id_counter)
4113
4114         new_tabs._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone())
4115         new_tabs._tabs.SetAGWFlags(self._agwFlags)
4116         dest_tabs = new_tabs._tabs
4117
4118         page_info = src_tabs.GetPage(src_idx)
4119         if page_info.control:
4120             self.ReparentControl(page_info.control, dest_tabs)
4121
4122         # create a pane info structure with the information
4123         # about where the pane should be added
4124         pane_info = framemanager.AuiPaneInfo().Bottom().CaptionVisible(False)
4125
4126         if direction == wx.LEFT:
4127
4128             pane_info.Left()
4129             mouse_pt = wx.Point(0, cli_size.y/2)
4130
4131         elif direction == wx.RIGHT:
4132
4133             pane_info.Right()
4134             mouse_pt = wx.Point(cli_size.x, cli_size.y/2)
4135
4136         elif direction == wx.TOP:
4137
4138             pane_info.Top()
4139             mouse_pt = wx.Point(cli_size.x/2, 0)
4140
4141         elif direction == wx.BOTTOM:
4142
4143             pane_info.Bottom()
4144             mouse_pt = wx.Point(cli_size.x/2, cli_size.y)
4145
4146         self._mgr.AddPane(new_tabs, pane_info, mouse_pt)
4147         self._mgr.Update()
4148
4149         # remove the page from the source tabs
4150         page_info.active = False
4151
4152         src_tabs.RemovePage(page_info.window)
4153
4154         if src_tabs.GetPageCount() > 0:
4155             src_tabs.SetActivePage(0)
4156             src_tabs.DoShowHide()
4157             src_tabs.Refresh()
4158
4159         # add the page to the destination tabs
4160         dest_tabs.InsertPage(page_info.window, page_info, 0)
4161
4162         if src_tabs.GetPageCount() == 0:
4163             self.RemoveEmptyTabFrames()
4164
4165         self.DoSizing()
4166         dest_tabs.DoShowHide()
4167         dest_tabs.Refresh()
4168
4169         # force the set selection function reset the selection
4170         self._curpage = -1
4171
4172         # set the active page to the one we just split off
4173         self.SetSelectionToPage(page_info)
4174
4175         self.UpdateHintWindowSize()
4176
4177
4178     def UnSplit(self):
4179         """ Restores original view after a tab split. """
4180
4181         self.Freeze()
4182
4183         # remember the tab now selected
4184         nowSelected = self.GetSelection()
4185         # select first tab as destination
4186         self.SetSelection(0)
4187         # iterate all other tabs
4188         for idx in xrange(1, self.GetPageCount()):
4189             # get win reference
4190             win = self.GetPage(idx)
4191             # get tab title
4192             title = self.GetPageText(idx)
4193             # get page bitmap
4194             bmp = self.GetPageBitmap(idx)
4195             # remove from notebook
4196             self.RemovePage(idx)
4197             # re-add in the same position so it will tab
4198             self.InsertPage(idx, win, title, False, bmp)
4199         # restore orignial selected tab
4200         self.SetSelection(nowSelected)
4201
4202         self.Thaw()
4203
4204
4205     def ReparentControl(self, control, dest_tabs):
4206         """
4207         Reparents a control added inside a tab.
4208
4209         :param `control`: an instance of `wx.Window`;
4210         :param `dest_tabs`: the destination L{AuiTabCtrl}.
4211         """
4212
4213         control.Hide()
4214         control.Reparent(dest_tabs)
4215
4216
4217     def UnsplitDClick(self, part, sash_size, pos):
4218         """
4219         Unsplit the L{AuiNotebook} on sash double-click.
4220
4221         :param `part`: an UI part representing the sash;
4222         :param `sash_size`: the sash size;
4223         :param `pos`: the double-click mouse position.
4224
4225         :warning: Due to a bug on MSW, for disabled pages `wx.FindWindowAtPoint`
4226          returns the wrong window. See http://trac.wxwidgets.org/ticket/2942
4227         """
4228
4229         if not self._sash_dclick_unsplit:
4230             # Unsplit not allowed
4231             return
4232
4233         pos1 = wx.Point(*pos)
4234         pos2 = wx.Point(*pos)
4235         if part.orientation == wx.HORIZONTAL:
4236             pos1.y -= 2*sash_size
4237             pos2.y += 2*sash_size + self.GetTabCtrlHeight()
4238         elif part.orientation == wx.VERTICAL:
4239             pos1.x -= 2*sash_size
4240             pos2.x += 2*sash_size
4241         else:
4242             raise Exception("Invalid UI part orientation")
4243
4244         pos1, pos2 = self.ClientToScreen(pos1), self.ClientToScreen(pos2)
4245         win1, win2 = wx.FindWindowAtPoint(pos1), wx.FindWindowAtPoint(pos2)
4246
4247         if isinstance(win1, wx.ScrollBar):
4248             # Hopefully it will work
4249             pos1 = wx.Point(*pos)
4250             shift = wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X) + 2*(sash_size+1)
4251             if part.orientation == wx.HORIZONTAL:
4252                 pos1.y -= shift
4253             else:
4254                 pos1.x -= shift
4255
4256             pos1 = self.ClientToScreen(pos1)
4257             win1 = wx.FindWindowAtPoint(pos1)
4258
4259         if isinstance(win2, wx.ScrollBar):
4260             pos2 = wx.Point(*pos)
4261             shift = wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X) + 2*(sash_size+1)
4262             if part.orientation == wx.HORIZONTAL:
4263                 pos2.y += shift
4264             else:
4265                 pos2.x += shift
4266
4267             pos2 = self.ClientToScreen(pos2)
4268             win2 = wx.FindWindowAtPoint(pos2)
4269
4270         if not win1 or not win2:
4271             # How did we get here?
4272             return
4273
4274         if isinstance(win1, AuiNotebook) or isinstance(win2, AuiNotebook):
4275             # This is a bug on MSW, for disabled pages wx.FindWindowAtPoint
4276             # returns the wrong window.
4277             # See http://trac.wxwidgets.org/ticket/2942
4278             return
4279
4280         tab_frame1, tab_frame2 = self.GetTabFrameFromWindow(win1), self.GetTabFrameFromWindow(win2)
4281
4282         if not tab_frame1 or not tab_frame2:
4283             return
4284
4285         tab_ctrl_1, tab_ctrl_2 = tab_frame1._tabs, tab_frame2._tabs
4286
4287         if tab_ctrl_1.GetPageCount() > tab_ctrl_2.GetPageCount():
4288             src_tabs = tab_ctrl_2
4289             dest_tabs = tab_ctrl_1
4290         else:
4291             src_tabs = tab_ctrl_1
4292             dest_tabs = tab_ctrl_2
4293
4294         selection = -1
4295         page_count = dest_tabs.GetPageCount()
4296
4297         for page in xrange(src_tabs.GetPageCount()-1, -1, -1):
4298             # remove the page from the source tabs
4299             page_info = src_tabs.GetPage(page)
4300             if page_info.active:
4301                 selection = page_count + page
4302             src_tabs.RemovePage(page_info.window)
4303
4304             # add the page to the destination tabs
4305             dest_tabs.AddPage(page_info.window, page_info)
4306             if page_info.control:
4307                 self.ReparentControl(page_info.control, dest_tabs)
4308
4309         self.RemoveEmptyTabFrames()
4310
4311         dest_tabs.DoShowHide()
4312         self.DoSizing()
4313         dest_tabs.Refresh()
4314         self._mgr.Update()
4315         if selection > 0:
4316             wx.CallAfter(dest_tabs.MakeTabVisible, selection, self)
4317
4318
4319     def OnSize(self, event):
4320         """
4321         Handles the ``wx.EVT_SIZE`` event for L{AuiNotebook}.
4322
4323         :param `event`: a `wx.SizeEvent` event to be processed.
4324         """
4325
4326         self.UpdateHintWindowSize()
4327         event.Skip()
4328
4329
4330     def OnTabClicked(self, event):
4331         """
4332         Handles the ``EVT_AUINOTEBOOK_PAGE_CHANGING`` event for L{AuiNotebook}.
4333
4334         :param `event`: a L{AuiNotebookEvent} event to be processed.
4335         """
4336
4337         if self._textCtrl is not None:
4338             self._textCtrl.StopEditing()
4339
4340         ctrl = event.GetEventObject()
4341         assert ctrl != None
4342
4343         wnd = ctrl.GetWindowFromIdx(event.GetSelection())
4344         assert wnd != None
4345
4346         self.SetSelectionToWindow(wnd)
4347
4348
4349     def OnTabBgDClick(self, event):
4350         """
4351         Handles the ``EVT_AUINOTEBOOK_BG_DCLICK`` event for L{AuiNotebook}.
4352
4353         :param `event`: a L{AuiNotebookEvent} event to be processed.
4354         """
4355
4356         if self._textCtrl is not None:
4357             self._textCtrl.StopEditing()
4358
4359         # notify owner that the tabbar background has been double-clicked
4360         e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK, self.GetId())
4361         e.SetEventObject(self)
4362         self.GetEventHandler().ProcessEvent(e)
4363
4364
4365     def OnTabDClick(self, event):
4366         """
4367         Handles the ``EVT_AUINOTEBOOK_TAB_DCLICK`` event for L{AuiNotebook}.
4368
4369         :param `event`: a L{AuiNotebookEvent} event to be processed.
4370         """
4371
4372         # notify owner that the tabbar background has been double-clicked
4373         e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_DCLICK, self.GetId())
4374         e.SetEventObject(self)
4375         self.GetEventHandler().ProcessEvent(e)
4376
4377         tabs = event.GetEventObject()
4378         if not tabs.GetEnabled(event.GetSelection()):
4379             return
4380
4381         if not self.IsRenamable(event.GetSelection()):
4382             return
4383
4384         self.EditTab(event.GetSelection())
4385
4386
4387     def OnTabBeginDrag(self, event):
4388         """
4389         Handles the ``EVT_AUINOTEBOOK_BEGIN_DRAG`` event for L{AuiNotebook}.
4390
4391         :param `event`: a L{AuiNotebookEvent} event to be processed.
4392         """
4393
4394         tabs = event.GetEventObject()
4395         if not tabs.GetEnabled(event.GetSelection()):
4396             return
4397
4398         self._last_drag_x = 0
4399
4400
4401     def OnTabDragMotion(self, event):
4402         """
4403         Handles the ``EVT_AUINOTEBOOK_DRAG_MOTION`` event for L{AuiNotebook}.
4404
4405         :param `event`: a L{AuiNotebookEvent} event to be processed.
4406         """
4407
4408         tabs = event.GetEventObject()
4409         if not tabs.GetEnabled(event.GetSelection()):
4410             return
4411
4412         if self._textCtrl is not None:
4413             self._textCtrl.StopEditing()
4414
4415         screen_pt = wx.GetMousePosition()
4416         client_pt = self.ScreenToClient(screen_pt)
4417         zero = wx.Point(0, 0)
4418
4419         src_tabs = event.GetEventObject()
4420         dest_tabs = self.GetTabCtrlFromPoint(client_pt)
4421
4422         if dest_tabs == src_tabs:
4423
4424             # always hide the hint for inner-tabctrl drag
4425             self._mgr.HideHint()
4426
4427             # if tab moving is not allowed, leave
4428             if not self._agwFlags & AUI_NB_TAB_MOVE:
4429                 return
4430
4431             pt = dest_tabs.ScreenToClient(screen_pt)
4432
4433             # this is an inner-tab drag/reposition
4434             dest_location_tab = dest_tabs.TabHitTest(pt.x, pt.y)
4435
4436             if dest_location_tab:
4437
4438                 src_idx = event.GetSelection()
4439                 dest_idx = dest_tabs.GetIdxFromWindow(dest_location_tab)
4440
4441                 # prevent jumpy drag
4442                 if (src_idx == dest_idx) or dest_idx == -1 or \
4443                    (src_idx > dest_idx and self._last_drag_x <= pt.x) or \
4444                    (src_idx < dest_idx and self._last_drag_x >= pt.x):
4445
4446                     self._last_drag_x = pt.x
4447                     return
4448
4449                 src_tab = dest_tabs.GetWindowFromIdx(src_idx)
4450                 dest_tabs.MovePage(src_tab, dest_idx)
4451                 self._tabs.MovePage(self._tabs.GetPage(src_idx).window, dest_idx)
4452                 dest_tabs.SetActivePage(dest_idx)
4453                 dest_tabs.DoShowHide()
4454                 dest_tabs.Refresh()
4455                 self._last_drag_x = pt.x
4456
4457             return
4458
4459         # if external drag is allowed, check if the tab is being dragged
4460         # over a different AuiNotebook control
4461         if self._agwFlags & AUI_NB_TAB_EXTERNAL_MOVE:
4462
4463             tab_ctrl = wx.FindWindowAtPoint(screen_pt)
4464
4465             # if we aren't over any window, stop here
4466             if not tab_ctrl:
4467                 if self._agwFlags & AUI_NB_TAB_FLOAT:
4468                     if self.IsMouseWellOutsideWindow():
4469                         hintRect = wx.RectPS(screen_pt, (400, 300))
4470                         # Use CallAfter so we overwrite the hint that might be
4471                         # shown by our superclass:
4472                         wx.CallAfter(self._mgr.ShowHint, hintRect)
4473                 return
4474
4475             # make sure we are not over the hint window
4476             if not isinstance(tab_ctrl, wx.Frame):
4477                 while tab_ctrl:
4478                     if isinstance(tab_ctrl, AuiTabCtrl):
4479                         break
4480
4481                     tab_ctrl = tab_ctrl.GetParent()
4482
4483                 if tab_ctrl:
4484                     nb = tab_ctrl.GetParent()
4485
4486                     if nb != self:
4487
4488                         hint_rect = tab_ctrl.GetClientRect()
4489                         hint_rect.x, hint_rect.y = tab_ctrl.ClientToScreenXY(hint_rect.x, hint_rect.y)
4490                         self._mgr.ShowHint(hint_rect)
4491                         return
4492
4493             else:
4494
4495                 if not dest_tabs:
4496                     # we are either over a hint window, or not over a tab
4497                     # window, and there is no where to drag to, so exit
4498                     return
4499
4500         if self._agwFlags & AUI_NB_TAB_FLOAT:
4501             if self.IsMouseWellOutsideWindow():
4502                 hintRect = wx.RectPS(screen_pt, (400, 300))
4503                 # Use CallAfter so we overwrite the hint that might be
4504                 # shown by our superclass:
4505                 wx.CallAfter(self._mgr.ShowHint, hintRect)
4506                 return
4507
4508         # if there are less than two panes, split can't happen, so leave
4509         if self._tabs.GetPageCount() < 2:
4510             return
4511
4512         # if tab moving is not allowed, leave
4513         if not self._agwFlags & AUI_NB_TAB_SPLIT:
4514             return
4515
4516         if dest_tabs:
4517
4518             hint_rect = dest_tabs.GetRect()
4519             hint_rect.x, hint_rect.y = self.ClientToScreenXY(hint_rect.x, hint_rect.y)
4520             self._mgr.ShowHint(hint_rect)
4521
4522         else:
4523             rect = self._mgr.CalculateHintRect(self._dummy_wnd, client_pt, zero)
4524             if rect.IsEmpty():
4525                 self._mgr.HideHint()
4526                 return
4527
4528             hit_wnd = wx.FindWindowAtPoint(screen_pt)
4529             if hit_wnd and not isinstance(hit_wnd, AuiNotebook):
4530                 tab_frame = self.GetTabFrameFromWindow(hit_wnd)
4531                 if tab_frame:
4532                     hint_rect = wx.Rect(*tab_frame._rect)
4533                     hint_rect.x, hint_rect.y = self.ClientToScreenXY(hint_rect.x, hint_rect.y)
4534                     rect.Intersect(hint_rect)
4535                     self._mgr.ShowHint(rect)
4536                 else:
4537                     self._mgr.DrawHintRect(self._dummy_wnd, client_pt, zero)
4538             else:
4539                 self._mgr.DrawHintRect(self._dummy_wnd, client_pt, zero)
4540
4541
4542     def OnTabEndDrag(self, event):
4543         """
4544         Handles the ``EVT_AUINOTEBOOK_END_DRAG`` event for L{AuiNotebook}.
4545
4546         :param `event`: a L{AuiNotebookEvent} event to be processed.
4547         """
4548
4549         tabs = event.GetEventObject()
4550         if not tabs.GetEnabled(event.GetSelection()):
4551             return
4552
4553         self._mgr.HideHint()
4554
4555         src_tabs = event.GetEventObject()
4556         if not src_tabs:
4557             raise Exception("no source object?")
4558
4559         # get the mouse position, which will be used to determine the drop point
4560         mouse_screen_pt = wx.GetMousePosition()
4561         mouse_client_pt = self.ScreenToClient(mouse_screen_pt)
4562
4563         # check for an external move
4564         if self._agwFlags & AUI_NB_TAB_EXTERNAL_MOVE:
4565             tab_ctrl = wx.FindWindowAtPoint(mouse_screen_pt)
4566
4567             while tab_ctrl:
4568
4569                 if isinstance(tab_ctrl, AuiTabCtrl):
4570                     break
4571
4572                 tab_ctrl = tab_ctrl.GetParent()
4573
4574             if tab_ctrl:
4575
4576                 nb = tab_ctrl.GetParent()
4577
4578                 if nb != self:
4579
4580                     # find out from the destination control
4581                     # if it's ok to drop this tab here
4582                     e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_ALLOW_DND, self.GetId())
4583                     e.SetSelection(event.GetSelection())
4584                     e.SetOldSelection(event.GetSelection())
4585                     e.SetEventObject(self)
4586                     e.SetDragSource(self)
4587                     e.Veto() # dropping must be explicitly approved by control owner
4588
4589                     nb.GetEventHandler().ProcessEvent(e)
4590
4591                     if not e.IsAllowed():
4592
4593                         # no answer or negative answer
4594                         self._mgr.HideHint()
4595                         return
4596
4597                     # drop was allowed
4598                     src_idx = event.GetSelection()
4599                     src_page = src_tabs.GetWindowFromIdx(src_idx)
4600
4601                     # Check that it's not an impossible parent relationship
4602                     p = nb
4603                     while p and not p.IsTopLevel():
4604                         if p == src_page:
4605                             return
4606
4607                         p = p.GetParent()
4608
4609                     # get main index of the page
4610                     main_idx = self._tabs.GetIdxFromWindow(src_page)
4611                     if main_idx == wx.NOT_FOUND:
4612                         raise Exception("no source page?")
4613
4614                     # make a copy of the page info
4615                     page_info = self._tabs.GetPage(main_idx)
4616
4617                     # remove the page from the source notebook
4618                     self.RemovePage(main_idx)
4619
4620                     # reparent the page
4621                     src_page.Reparent(nb)
4622
4623                     # Reparent the control in a tab (if any)
4624                     if page_info.control:
4625                         self.ReparentControl(page_info.control, tab_ctrl)
4626
4627                     # find out the insert idx
4628                     dest_tabs = tab_ctrl
4629                     pt = dest_tabs.ScreenToClient(mouse_screen_pt)
4630
4631                     target = dest_tabs.TabHitTest(pt.x, pt.y)
4632                     insert_idx = -1
4633                     if target:
4634                         insert_idx = dest_tabs.GetIdxFromWindow(target)
4635
4636                     # add the page to the new notebook
4637                     if insert_idx == -1:
4638                         insert_idx = dest_tabs.GetPageCount()
4639
4640                     dest_tabs.InsertPage(page_info.window, page_info, insert_idx)
4641                     nb._tabs.AddPage(page_info.window, page_info)
4642
4643                     nb.DoSizing()
4644                     dest_tabs.DoShowHide()
4645                     dest_tabs.Refresh()
4646
4647                     # set the selection in the destination tab control
4648                     nb.SetSelectionToPage(page_info)
4649
4650                     # notify owner that the tab has been dragged
4651                     e2 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, self.GetId())
4652                     e2.SetSelection(event.GetSelection())
4653                     e2.SetOldSelection(event.GetSelection())
4654                     e2.SetEventObject(self)
4655                     self.GetEventHandler().ProcessEvent(e2)
4656
4657                     # notify the target notebook that the tab has been dragged
4658                     e3 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, nb.GetId())
4659                     e3.SetSelection(insert_idx)
4660                     e3.SetOldSelection(insert_idx)
4661                     e3.SetEventObject(nb)
4662                     nb.GetEventHandler().ProcessEvent(e3)
4663
4664                     return
4665
4666         if self._agwFlags & AUI_NB_TAB_FLOAT:
4667             self._mgr.HideHint()
4668             if self.IsMouseWellOutsideWindow():
4669                 # Use CallAfter so we our superclass can deal with the event first
4670                 wx.CallAfter(self.FloatPage, self.GetSelection())
4671                 event.Skip()
4672                 return
4673
4674         # only perform a tab split if it's allowed
4675         dest_tabs = None
4676
4677         if self._agwFlags & AUI_NB_TAB_SPLIT and self._tabs.GetPageCount() >= 2:
4678
4679             # If the pointer is in an existing tab frame, do a tab insert
4680             hit_wnd = wx.FindWindowAtPoint(mouse_screen_pt)
4681             tab_frame = self.GetTabFrameFromTabCtrl(hit_wnd)
4682             insert_idx = -1
4683
4684             if tab_frame:
4685
4686                 dest_tabs = tab_frame._tabs
4687
4688                 if dest_tabs == src_tabs:
4689                     return
4690
4691                 pt = dest_tabs.ScreenToClient(mouse_screen_pt)
4692                 target = dest_tabs.TabHitTest(pt.x, pt.y)
4693
4694                 if target:
4695                     insert_idx = dest_tabs.GetIdxFromWindow(target)
4696
4697             else:
4698
4699                 zero = wx.Point(0, 0)
4700                 rect = self._mgr.CalculateHintRect(self._dummy_wnd, mouse_client_pt, zero)
4701
4702                 if rect.IsEmpty():
4703                     # there is no suitable drop location here, exit out
4704                     return
4705
4706                 # If there is no tabframe at all, create one
4707                 new_tabs = TabFrame(self)
4708                 new_tabs._rect = wx.RectPS(wx.Point(0, 0), self.CalculateNewSplitSize())
4709                 new_tabs.SetTabCtrlHeight(self._tab_ctrl_height)
4710                 self._tab_id_counter += 1
4711                 new_tabs._tabs = AuiTabCtrl(self, self._tab_id_counter)
4712                 new_tabs._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone())
4713                 new_tabs._tabs.SetAGWFlags(self._agwFlags)
4714
4715                 self._mgr.AddPane(new_tabs, framemanager.AuiPaneInfo().Bottom().CaptionVisible(False), mouse_client_pt)
4716                 self._mgr.Update()
4717                 dest_tabs = new_tabs._tabs
4718
4719             # remove the page from the source tabs
4720             page_info = src_tabs.GetPage(event.GetSelection())
4721
4722             if page_info.control:
4723                 self.ReparentControl(page_info.control, dest_tabs)
4724
4725             page_info.active = False
4726             src_tabs.RemovePage(page_info.window)
4727
4728             if src_tabs.GetPageCount() > 0:
4729                 src_tabs.SetActivePage(0)
4730                 src_tabs.DoShowHide()
4731                 src_tabs.Refresh()
4732
4733             # add the page to the destination tabs
4734             if insert_idx == -1:
4735                 insert_idx = dest_tabs.GetPageCount()
4736
4737             dest_tabs.InsertPage(page_info.window, page_info, insert_idx)
4738
4739             if src_tabs.GetPageCount() == 0:
4740                 self.RemoveEmptyTabFrames()
4741
4742             self.DoSizing()
4743             dest_tabs.DoShowHide()
4744             dest_tabs.Refresh()
4745
4746             # force the set selection function reset the selection
4747             self._curpage = -1
4748
4749             # set the active page to the one we just split off
4750             self.SetSelectionToPage(page_info)
4751
4752             self.UpdateHintWindowSize()
4753
4754         # notify owner that the tab has been dragged
4755         e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, self.GetId())
4756         e.SetSelection(event.GetSelection())
4757         e.SetOldSelection(event.GetSelection())
4758         e.SetEventObject(self)
4759         self.GetEventHandler().ProcessEvent(e)
4760
4761
4762     def OnTabCancelDrag(self, event):
4763         """
4764         Handles the ``EVT_AUINOTEBOOK_CANCEL_DRAG`` event for L{AuiNotebook}.
4765
4766         :param `event`: a L{AuiNotebookEvent} event to be processed.
4767         """
4768
4769         tabs = event.GetEventObject()
4770         if not tabs.GetEnabled(event.GetSelection()):
4771             return
4772
4773         self._mgr.HideHint()
4774
4775         src_tabs = event.GetEventObject()
4776         if not src_tabs:
4777             raise Exception("no source object?")
4778
4779
4780     def IsMouseWellOutsideWindow(self):
4781         """ Returns whether the mouse is well outside the L{AuiNotebook} screen rectangle. """
4782
4783         screen_rect = self.GetScreenRect()
4784         screen_rect.Inflate(50, 50)
4785
4786         return not screen_rect.Contains(wx.GetMousePosition())
4787
4788
4789     def FloatPage(self, page_index):
4790         """
4791         Float the page in `page_index` by reparenting it to a floating frame.
4792
4793         :param `page_index`: the index of the page to be floated.
4794
4795         :warning: When the notebook is more or less full screen, tabs cannot be dragged far
4796          enough outside of the notebook to become floating pages.
4797         """
4798
4799         root_manager = framemanager.GetManager(self)
4800         page_title = self.GetPageText(page_index)
4801         page_contents = self.GetPage(page_index)
4802         page_bitmap = self.GetPageBitmap(page_index)
4803         text_colour = self.GetPageTextColour(page_index)
4804         info = self.GetPageInfo(page_index)
4805
4806         if root_manager and root_manager != self._mgr:
4807             root_manager = framemanager.GetManager(self)
4808
4809             if hasattr(page_contents, "__floating_size__"):
4810                 floating_size = wx.Size(*page_contents.__floating_size__)
4811             else:
4812                 floating_size = page_contents.GetBestSize()
4813                 if floating_size == wx.DefaultSize:
4814                     floating_size = wx.Size(300, 200)
4815
4816             page_contents.__page_index__ = page_index
4817             page_contents.__aui_notebook__ = self
4818             page_contents.__text_colour__ = text_colour
4819             page_contents.__control__ = info.control
4820
4821             if info.control:
4822                 info.control.Reparent(page_contents)
4823                 info.control.Hide()
4824                 info.control = None
4825
4826             self.RemovePage(page_index)
4827             self.RemoveEmptyTabFrames()
4828
4829             pane_info = framemanager.AuiPaneInfo().Float().FloatingPosition(wx.GetMousePosition()). \
4830                         FloatingSize(floating_size).BestSize(floating_size).Name("__floating__%s"%page_title). \
4831                         Caption(page_title).Icon(page_bitmap)
4832             root_manager.AddPane(page_contents, pane_info)
4833             root_manager.Bind(framemanager.EVT_AUI_PANE_CLOSE, self.OnCloseFloatingPage)
4834             self.GetActiveTabCtrl().DoShowHide()
4835             self.DoSizing()
4836             root_manager.Update()
4837
4838         else:
4839             frame = wx.Frame(self, title=page_title,
4840                              style=wx.DEFAULT_FRAME_STYLE|wx.FRAME_TOOL_WINDOW|
4841                                    wx.FRAME_FLOAT_ON_PARENT | wx.FRAME_NO_TASKBAR)
4842
4843             if info.control:
4844                 info.control.Reparent(frame)
4845                 info.control.Hide()
4846
4847             frame.bitmap = page_bitmap
4848             frame.page_index = page_index
4849             frame.text_colour = text_colour
4850             frame.control = info.control
4851             page_contents.Reparent(frame)
4852             frame.Bind(wx.EVT_CLOSE, self.OnCloseFloatingPage)
4853             frame.Move(wx.GetMousePosition())
4854             frame.Show()
4855             self.RemovePage(page_index)
4856
4857             self.RemoveEmptyTabFrames()
4858
4859         wx.CallAfter(self.RemoveEmptyTabFrames)
4860
4861
4862     def OnCloseFloatingPage(self, event):
4863         """
4864         Handles the ``wx.EVT_CLOSE`` event for a floating page in L{AuiNotebook}.
4865
4866         :param `event`: a `wx.CloseEvent` event to be processed.
4867         """
4868
4869         root_manager = framemanager.GetManager(self)
4870         if root_manager and root_manager != self._mgr:
4871             pane = event.pane
4872             if pane.name.startswith("__floating__"):
4873                 self.ReDockPage(pane)
4874                 return
4875
4876             event.Skip()
4877         else:
4878             event.Skip()
4879             frame = event.GetEventObject()
4880             page_title = frame.GetTitle()
4881             page_contents = list(frame.GetChildren())[-1]
4882             page_contents.Reparent(self)
4883             self.InsertPage(frame.page_index, page_contents, page_title, select=True, bitmap=frame.bitmap, control=frame.control)
4884
4885             if frame.control:
4886                 src_tabs, idx = self.FindTab(page_contents)
4887                 frame.control.Reparent(src_tabs)
4888                 frame.control.Hide()
4889                 frame.control = None
4890
4891             self.SetPageTextColour(frame.page_index, frame.text_colour)
4892
4893
4894     def ReDockPage(self, pane):
4895         """
4896         Re-docks a floating L{AuiNotebook} tab in the original position, when possible.
4897
4898         :param `pane`: an instance of L{framemanager.AuiPaneInfo}.
4899         """
4900
4901         root_manager = framemanager.GetManager(self)
4902
4903         pane.window.__floating_size__ = wx.Size(*pane.floating_size)
4904         page_index = pane.window.__page_index__
4905         text_colour = pane.window.__text_colour__
4906         control = pane.window.__control__
4907
4908         root_manager.DetachPane(pane.window)
4909         self.InsertPage(page_index, pane.window, pane.caption, True, pane.icon, control=control)
4910
4911         self.SetPageTextColour(page_index, text_colour)
4912         self.GetActiveTabCtrl().DoShowHide()
4913         self.DoSizing()
4914         if control:
4915             self.UpdateTabCtrlHeight(force=True)
4916
4917         self._mgr.Update()
4918         root_manager.Update()
4919
4920
4921     def GetTabCtrlFromPoint(self, pt):
4922         """
4923         Returns the tab control at the specified point.
4924
4925         :param `pt`: a `wx.Point` object.
4926         """
4927
4928         # if we've just removed the last tab from the source
4929         # tab set, the remove the tab control completely
4930         all_panes = self._mgr.GetAllPanes()
4931         for pane in all_panes:
4932             if pane.name == "dummy":
4933                 continue
4934
4935             tabframe = pane.window
4936             if tabframe._tab_rect.Contains(pt):
4937                 return tabframe._tabs
4938
4939         return None
4940
4941
4942     def GetTabFrameFromTabCtrl(self, tab_ctrl):
4943         """
4944         Returns the tab frame associated with a tab control.
4945
4946         :param `tab_ctrl`: an instance of L{AuiTabCtrl}.
4947         """
4948
4949         # if we've just removed the last tab from the source
4950         # tab set, the remove the tab control completely
4951         all_panes = self._mgr.GetAllPanes()
4952         for pane in all_panes:
4953             if pane.name == "dummy":
4954                 continue
4955
4956             tabframe = pane.window
4957             if tabframe._tabs == tab_ctrl:
4958                 return tabframe
4959
4960         return None
4961
4962
4963     def GetTabFrameFromWindow(self, wnd):
4964         """
4965         Returns the tab frame associated with a window.
4966
4967         :param `wnd`: an instance of `wx.Window`.
4968         """
4969
4970         all_panes = self._mgr.GetAllPanes()
4971         for pane in all_panes:
4972             if pane.name == "dummy":
4973                 continue
4974
4975             tabframe = pane.window
4976             for page in tabframe._tabs.GetPages():
4977                 if wnd == page.window:
4978                     return tabframe
4979
4980         return None
4981
4982
4983     def RemoveEmptyTabFrames(self):
4984         """ Removes all the empty tab frames. """
4985
4986         # if we've just removed the last tab from the source
4987         # tab set, the remove the tab control completely
4988         all_panes = self._mgr.GetAllPanes()
4989
4990         for indx in xrange(len(all_panes)-1, -1, -1):
4991             pane = all_panes[indx]
4992             if pane.name == "dummy":
4993                 continue
4994
4995             tab_frame = pane.window
4996             if tab_frame._tabs.GetPageCount() == 0:
4997                 self._mgr.DetachPane(tab_frame)
4998                 tab_frame._tabs.Destroy()
4999                 tab_frame._tabs = None
5000                 del tab_frame
5001
5002         # check to see if there is still a center pane
5003         # if there isn't, make a frame the center pane
5004         first_good = None
5005         center_found = False
5006
5007         all_panes = self._mgr.GetAllPanes()
5008         for pane in all_panes:
5009             if pane.name == "dummy":
5010                 continue
5011
5012             if pane.dock_direction == AUI_DOCK_CENTRE:
5013                 center_found = True
5014             if not first_good:
5015                 first_good = pane.window
5016
5017         if not center_found and first_good:
5018             self._mgr.GetPane(first_good).Centre()
5019
5020         if not self.IsBeingDeleted():
5021             self._mgr.Update()
5022
5023
5024     def OnChildFocusNotebook(self, event):
5025         """
5026         Handles the ``wx.EVT_CHILD_FOCUS`` event for L{AuiNotebook}.
5027
5028         :param `event`: a `wx.ChildFocusEvent` event to be processed.
5029         """
5030
5031         # if we're dragging a tab, don't change the current selection.
5032         # This code prevents a bug that used to happen when the hint window
5033         # was hidden.  In the bug, the focus would return to the notebook
5034         # child, which would then enter this handler and call
5035         # SetSelection, which is not desired turn tab dragging.
5036
5037         event.Skip()
5038
5039         all_panes = self._mgr.GetAllPanes()
5040         for pane in all_panes:
5041             if pane.name == "dummy":
5042                 continue
5043             tabframe = pane.window
5044             if tabframe._tabs.IsDragging():
5045                 return
5046
5047 ##        # change the tab selection to the child
5048 ##        # which was focused
5049 ##        idx = self._tabs.GetIdxFromWindow(event.GetWindow())
5050 ##        if idx != -1 and idx != self._curpage:
5051 ##            self.SetSelection(idx)
5052
5053
5054     def SetNavigatorIcon(self, bmp):
5055         """
5056         Sets the icon used by the L{TabNavigatorWindow}.
5057
5058         :param `bmp`: an instance of `wx.Bitmap`.
5059         """
5060
5061         if isinstance(bmp, wx.Bitmap) and bmp.IsOk():
5062             # Make sure image is proper size
5063             if bmp.GetSize() != (16, 16):
5064                 img = bmp.ConvertToImage()
5065                 img.Rescale(16, 16, wx.IMAGE_QUALITY_HIGH)
5066                 bmp = wx.BitmapFromImage(img)
5067             self._naviIcon = bmp
5068         else:
5069             raise TypeError, "SetNavigatorIcon requires a valid bitmap"
5070
5071
5072     def OnNavigationKeyNotebook(self, event):
5073         """
5074         Handles the ``wx.EVT_NAVIGATION_KEY`` event for L{AuiNotebook}.
5075
5076         :param `event`: a `wx.NavigationKeyEvent` event to be processed.
5077         """
5078
5079         if event.IsWindowChange():
5080             if self._agwFlags & AUI_NB_SMART_TABS:
5081                 if not self._popupWin:
5082                     self._popupWin = TabNavigatorWindow(self, self._naviIcon)
5083                     self._popupWin.SetReturnCode(wx.ID_OK)
5084                     self._popupWin.ShowModal()
5085                     idx = self._popupWin.GetSelectedPage()
5086                     self._popupWin.Destroy()
5087                     self._popupWin = None
5088                     # Need to do CallAfter so that the selection and its
5089                     # associated events get processed outside the context of
5090                     # this key event. Not doing so causes odd issues with the
5091                     # window focus under certain use cases on Windows.
5092                     wx.CallAfter(self.SetSelection, idx, True)
5093                 else:
5094                     # a dialog is already opened
5095                     self._popupWin.OnNavigationKey(event)
5096                     return
5097             else:
5098                 # change pages
5099                 # FIXME: the problem with this is that if we have a split notebook,
5100                 # we selection may go all over the place.
5101                 self.AdvanceSelection(event.GetDirection())
5102
5103         else:
5104             # we get this event in 3 cases
5105             #
5106             # a) one of our pages might have generated it because the user TABbed
5107             # out from it in which case we should propagate the event upwards and
5108             # our parent will take care of setting the focus to prev/next sibling
5109             #
5110             # or
5111             #
5112             # b) the parent panel wants to give the focus to us so that we
5113             # forward it to our selected page. We can't deal with this in
5114             # OnSetFocus() because we don't know which direction the focus came
5115             # from in this case and so can't choose between setting the focus to
5116             # first or last panel child
5117             #
5118             # or
5119             #
5120             # c) we ourselves (see MSWTranslateMessage) generated the event
5121             #
5122             parent = self.GetParent()
5123
5124             # the wxObject* casts are required to avoid MinGW GCC 2.95.3 ICE
5125             isFromParent = event.GetEventObject() == parent
5126             isFromSelf = event.GetEventObject() == self
5127
5128             if isFromParent or isFromSelf:
5129
5130                 # no, it doesn't come from child, case (b) or (c): forward to a
5131                 # page but only if direction is backwards (TAB) or from ourselves,
5132                 if self.GetSelection() != wx.NOT_FOUND and (not event.GetDirection() or isFromSelf):
5133
5134                     # so that the page knows that the event comes from it's parent
5135                     # and is being propagated downwards
5136                     event.SetEventObject(self)
5137
5138                     page = self.GetPage(self.GetSelection())
5139                     if not page.GetEventHandler().ProcessEvent(event):
5140                         page.SetFocus()
5141
5142                     #else: page manages focus inside it itself
5143
5144                 else: # otherwise set the focus to the notebook itself
5145
5146                     self.SetFocus()
5147
5148             else:
5149
5150                 # send this event back for the 'wraparound' focus.
5151                 winFocus = event.GetCurrentFocus()
5152
5153                 if winFocus:
5154                     event.SetEventObject(self)
5155                     winFocus.GetEventHandler().ProcessEvent(event)
5156
5157
5158     def OnTabButton(self, event):
5159         """
5160         Handles the ``EVT_AUINOTEBOOK_BUTTON`` event for L{AuiNotebook}.
5161
5162         :param `event`: a L{AuiNotebookEvent} event to be processed.
5163         """
5164
5165         tabs = event.GetEventObject()
5166         button_id = event.GetInt()
5167
5168         if button_id == AUI_BUTTON_CLOSE:
5169
5170             selection = event.GetSelection()
5171
5172             if selection == -1:
5173
5174                 # if the close button is to the right, use the active
5175                 # page selection to determine which page to close
5176                 selection = tabs.GetActivePage()
5177
5178             if selection == -1 or not tabs.GetEnabled(selection):
5179                 return
5180
5181             if selection != -1:
5182
5183                 close_wnd = tabs.GetWindowFromIdx(selection)
5184
5185                 if close_wnd.GetName() == "__fake__page__":
5186                     # This is a notebook preview
5187                     previous_active, page_status = close_wnd.__previousStatus
5188                     for page, status in zip(tabs.GetPages(), page_status):
5189                         page.enabled = status
5190
5191                     main_idx = self._tabs.GetIdxFromWindow(close_wnd)
5192                     self.DeletePage(main_idx)
5193
5194                     if previous_active >= 0:
5195                         tabs.SetActivePage(previous_active)
5196                         page_count = tabs.GetPageCount()
5197                         selection = -1
5198
5199                         for page in xrange(page_count):
5200                             # remove the page from the source tabs
5201                             page_info = tabs.GetPage(page)
5202                             if page_info.active:
5203                                 selection = page
5204                                 break
5205
5206                         tabs.DoShowHide()
5207                         self.DoSizing()
5208                         tabs.Refresh()
5209
5210                         if selection >= 0:
5211                             wx.CallAfter(tabs.MakeTabVisible, selection, self)
5212
5213                     # Don't fire the event
5214                     return
5215
5216                 # ask owner if it's ok to close the tab
5217                 e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE, self.GetId())
5218                 idx = self._tabs.GetIdxFromWindow(close_wnd)
5219                 e.SetSelection(idx)
5220                 e.SetOldSelection(event.GetSelection())
5221                 e.SetEventObject(self)
5222                 self.GetEventHandler().ProcessEvent(e)
5223                 if not e.IsAllowed():
5224                     return
5225
5226                 if repr(close_wnd.__class__).find("AuiMDIChildFrame") >= 0:
5227                     close_wnd.Close()
5228
5229                 else:
5230                     main_idx = self._tabs.GetIdxFromWindow(close_wnd)
5231                     self.DeletePage(main_idx)
5232
5233                 # notify owner that the tab has been closed
5234                 e2 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSED, self.GetId())
5235                 e2.SetSelection(idx)
5236                 e2.SetEventObject(self)
5237                 self.GetEventHandler().ProcessEvent(e2)
5238
5239                 if self.GetPageCount() == 0:
5240                     mgr = self.GetAuiManager()
5241                     win = mgr.GetManagedWindow()
5242                     win.SendSizeEvent()
5243
5244
5245     def OnTabMiddleDown(self, event):
5246         """
5247         Handles the ``EVT_AUINOTEBOOK_TAB_MIDDLE_DOWN`` event for L{AuiNotebook}.
5248
5249         :param `event`: a L{AuiNotebookEvent} event to be processed.
5250         """
5251
5252         tabs = event.GetEventObject()
5253         if not tabs.GetEnabled(event.GetSelection()):
5254             return
5255
5256         # patch event through to owner
5257         wnd = tabs.GetWindowFromIdx(event.GetSelection())
5258
5259         e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_DOWN, self.GetId())
5260         e.SetSelection(self._tabs.GetIdxFromWindow(wnd))
5261         e.SetEventObject(self)
5262         self.GetEventHandler().ProcessEvent(e)
5263
5264
5265     def OnTabMiddleUp(self, event):
5266         """
5267         Handles the ``EVT_AUINOTEBOOK_TAB_MIDDLE_UP`` event for L{AuiNotebook}.
5268
5269         :param `event`: a L{AuiNotebookEvent} event to be processed.
5270         """
5271
5272         tabs = event.GetEventObject()
5273         if not tabs.GetEnabled(event.GetSelection()):
5274             return
5275
5276         # if the AUI_NB_MIDDLE_CLICK_CLOSE is specified, middle
5277         # click should act like a tab close action.  However, first
5278         # give the owner an opportunity to handle the middle up event
5279         # for custom action
5280
5281         wnd = tabs.GetWindowFromIdx(event.GetSelection())
5282
5283         e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_UP, self.GetId())
5284         e.SetSelection(self._tabs.GetIdxFromWindow(wnd))
5285         e.SetEventObject(self)
5286         if self.GetEventHandler().ProcessEvent(e):
5287             return
5288         if not e.IsAllowed():
5289             return
5290
5291         # check if we are supposed to close on middle-up
5292         if self._agwFlags & AUI_NB_MIDDLE_CLICK_CLOSE == 0:
5293             return
5294
5295         # simulate the user pressing the close button on the tab
5296         event.SetInt(AUI_BUTTON_CLOSE)
5297         self.OnTabButton(event)
5298
5299
5300     def OnTabRightDown(self, event):
5301         """
5302         Handles the ``EVT_AUINOTEBOOK_TAB_RIGHT_DOWN`` event for L{AuiNotebook}.
5303
5304         :param `event`: a L{AuiNotebookEvent} event to be processed.
5305         """
5306
5307         tabs = event.GetEventObject()
5308         if not tabs.GetEnabled(event.GetSelection()):
5309             return
5310
5311         # patch event through to owner
5312         wnd = tabs.GetWindowFromIdx(event.GetSelection())
5313
5314         e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_DOWN, self.GetId())
5315         e.SetSelection(self._tabs.GetIdxFromWindow(wnd))
5316         e.SetEventObject(self)
5317         self.GetEventHandler().ProcessEvent(e)
5318
5319
5320     def OnTabRightUp(self, event):
5321         """
5322         Handles the ``EVT_AUINOTEBOOK_TAB_RIGHT_UP`` event for L{AuiNotebook}.
5323
5324         :param `event`: a L{AuiNotebookEvent} event to be processed.
5325         """
5326
5327         tabs = event.GetEventObject()
5328         if not tabs.GetEnabled(event.GetSelection()):
5329             return
5330
5331         # patch event through to owner
5332         wnd = tabs.GetWindowFromIdx(event.GetSelection())
5333
5334         e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP, self.GetId())
5335         e.SetSelection(self._tabs.GetIdxFromWindow(wnd))
5336         e.SetEventObject(self)
5337         self.GetEventHandler().ProcessEvent(e)
5338
5339
5340     def SetNormalFont(self, font):
5341         """
5342         Sets the normal font for drawing tab labels.
5343
5344         :param `font`: a `wx.Font` object.
5345         """
5346
5347         self._normal_font = font
5348         self.GetArtProvider().SetNormalFont(font)
5349
5350
5351     def SetSelectedFont(self, font):
5352         """
5353         Sets the selected tab font for drawing tab labels.
5354
5355         :param `font`: a `wx.Font` object.
5356         """
5357
5358         self._selected_font = font
5359         self.GetArtProvider().SetSelectedFont(font)
5360
5361
5362     def SetMeasuringFont(self, font):
5363         """
5364         Sets the font for calculating text measurements.
5365
5366         :param `font`: a `wx.Font` object.
5367         """
5368
5369         self.GetArtProvider().SetMeasuringFont(font)
5370
5371
5372     def SetFont(self, font):
5373         """
5374         Sets the tab font.
5375
5376         :param `font`: a `wx.Font` object.
5377
5378         :note: Overridden from `wx.PyPanel`.
5379         """
5380
5381         wx.PyPanel.SetFont(self, font)
5382
5383         selectedFont = wx.Font(font.GetPointSize(), font.GetFamily(),
5384                                font.GetStyle(), wx.BOLD, font.GetUnderlined(),
5385                                font.GetFaceName(), font.GetEncoding())
5386
5387         self.SetNormalFont(font)
5388         self.SetSelectedFont(selectedFont)
5389         self.SetMeasuringFont(selectedFont)
5390
5391         # Recalculate tab container size based on new font
5392         self.UpdateTabCtrlHeight(force=False)
5393         self.DoSizing()
5394
5395         return True
5396
5397
5398     def GetTabCtrlHeight(self):
5399         """ Returns the tab control height. """
5400
5401         return self._tab_ctrl_height
5402
5403
5404     def GetHeightForPageHeight(self, pageHeight):
5405         """
5406         Gets the height of the notebook for a given page height.
5407
5408         :param `pageHeight`: the given page height.
5409         """
5410
5411         self.UpdateTabCtrlHeight()
5412
5413         tabCtrlHeight = self.GetTabCtrlHeight()
5414         decorHeight = 2
5415         return tabCtrlHeight + pageHeight + decorHeight
5416
5417
5418     def AdvanceSelection(self, forward=True, wrap=True):
5419         """
5420         Cycles through the tabs.
5421
5422         :param `forward`: whether to advance forward or backward;
5423         :param `wrap`: ``True`` to return to the first tab if we reach the last tab.
5424
5425         :note: The call to this function generates the page changing events.
5426         """
5427
5428         tabCtrl = self.GetActiveTabCtrl()
5429         newPage = -1
5430
5431         focusWin = tabCtrl.FindFocus()
5432         activePage = tabCtrl.GetActivePage()
5433         lenPages = len(tabCtrl.GetPages())
5434
5435         if lenPages == 1:
5436             return False
5437
5438         if forward:
5439             if lenPages > 1:
5440
5441                 if activePage == -1 or activePage == lenPages - 1:
5442                     if not wrap:
5443                         return False
5444
5445                     newPage = 0
5446
5447                 elif activePage < lenPages - 1:
5448                     newPage = activePage + 1
5449
5450         else:
5451
5452             if lenPages > 1:
5453                 if activePage == -1 or activePage == 0:
5454                     if not wrap:
5455                         return False
5456
5457                     newPage = lenPages - 1
5458
5459                 elif activePage > 0:
5460                     newPage = activePage - 1
5461
5462
5463         if newPage != -1:
5464             if not self.GetEnabled(newPage):
5465                 return False
5466
5467             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, tabCtrl.GetId())
5468             e.SetSelection(newPage)
5469             e.SetOldSelection(activePage)
5470             e.SetEventObject(tabCtrl)
5471             self.GetEventHandler().ProcessEvent(e)
5472
5473 ##        if focusWin:
5474 ##            focusWin.SetFocus()
5475
5476         return True
5477
5478
5479     def ShowWindowMenu(self):
5480         """
5481         Shows the window menu for the active tab control associated with this
5482         notebook, and returns ``True`` if a selection was made.
5483         """
5484
5485         tabCtrl = self.GetActiveTabCtrl()
5486         idx = tabCtrl.GetArtProvider().ShowDropDown(tabCtrl, tabCtrl.GetPages(), tabCtrl.GetActivePage())
5487
5488         if not self.GetEnabled(idx):
5489             return False
5490
5491         if idx != -1:
5492             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, tabCtrl.GetId())
5493             e.SetSelection(idx)
5494             e.SetOldSelection(tabCtrl.GetActivePage())
5495             e.SetEventObject(tabCtrl)
5496             self.GetEventHandler().ProcessEvent(e)
5497
5498             return True
5499
5500         else:
5501
5502             return False
5503
5504
5505     def AddTabAreaButton(self, id, location, normal_bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap):
5506         """
5507         Adds a button in the tab area.
5508
5509         :param `id`: the button identifier. This can be one of the following:
5510
5511          ==============================  =================================
5512          Button Identifier               Description
5513          ==============================  =================================
5514          ``AUI_BUTTON_CLOSE``            Shows a close button on the tab area
5515          ``AUI_BUTTON_WINDOWLIST``       Shows a window list button on the tab area
5516          ``AUI_BUTTON_LEFT``             Shows a left button on the tab area
5517          ``AUI_BUTTON_RIGHT``            Shows a right button on the tab area
5518          ==============================  =================================
5519
5520         :param `location`: the button location. Can be ``wx.LEFT`` or ``wx.RIGHT``;
5521         :param `normal_bitmap`: the bitmap for an enabled tab;
5522         :param `disabled_bitmap`: the bitmap for a disabled tab.
5523         """
5524
5525         active_tabctrl = self.GetActiveTabCtrl()
5526         active_tabctrl.AddButton(id, location, normal_bitmap, disabled_bitmap)
5527
5528
5529     def RemoveTabAreaButton(self, id):
5530         """
5531         Removes a button from the tab area.
5532
5533         :param `id`: the button identifier. See L{AddTabAreaButton} for a list of button identifiers.
5534
5535         :see: L{AddTabAreaButton}
5536         """
5537
5538         active_tabctrl = self.GetActiveTabCtrl()
5539         active_tabctrl.RemoveButton(id)
5540
5541
5542     def HasMultiplePages(self):
5543         """
5544         This method should be overridden to return ``True`` if this window has multiple pages. All
5545         standard class with multiple pages such as `wx.Notebook`, `wx.Listbook` and `wx.Treebook`
5546         already override it to return ``True`` and user-defined classes with similar behaviour
5547         should do it as well to allow the library to handle such windows appropriately.
5548
5549         :note: Overridden from `wx.PyPanel`.
5550         """
5551
5552         return True
5553
5554
5555     def GetDefaultBorder(self):
5556         """ Returns the default border style for L{AuiNotebook}. """
5557
5558         return wx.BORDER_NONE
5559
5560
5561     def NotebookPreview(self, thumbnail_size=200):
5562         """
5563         Generates a preview of all the pages in the notebook (MSW and GTK only).
5564
5565         :param `thumbnail_size`: the maximum size of every page thumbnail.
5566
5567         :note: this functionality is currently unavailable on wxMac.
5568         """
5569
5570         if wx.Platform == "__WXMAC__":
5571             return False
5572
5573         tabCtrl = self.GetActiveTabCtrl()
5574         activePage = tabCtrl.GetActivePage()
5575         pages = tabCtrl.GetPages()
5576
5577         pageStatus, pageText = [], []
5578
5579         for indx, page in enumerate(pages):
5580
5581             pageStatus.append(page.enabled)
5582
5583             if not page.enabled:
5584                 continue
5585
5586             self.SetSelectionToPage(page)
5587             pageText.append(page.caption)
5588
5589             rect = page.window.GetScreenRect()
5590             bmp = RescaleScreenShot(TakeScreenShot(rect), thumbnail_size)
5591
5592             page.enabled = False
5593             if indx == 0:
5594                 il = wx.ImageList(bmp.GetWidth(), bmp.GetHeight(), True)
5595
5596             il.Add(bmp)
5597
5598         # create the list control
5599         listCtrl = wx.ListCtrl(self, style=wx.LC_ICON|wx.LC_AUTOARRANGE|wx.LC_HRULES|wx.LC_VRULES,
5600                                name="__fake__page__")
5601
5602         # assign the image list to it
5603         listCtrl.AssignImageList(il, wx.IMAGE_LIST_NORMAL)
5604         listCtrl.__previousStatus = [activePage, pageStatus]
5605
5606         # create some items for the list
5607         for indx, text in enumerate(pageText):
5608             listCtrl.InsertImageStringItem(10000, text, indx)
5609
5610         self.AddPage(listCtrl, "AuiNotebook Preview", True, bitmap=auinotebook_preview.GetBitmap(), disabled_bitmap=wx.NullBitmap)
5611         return True
5612
5613
5614     def SetRenamable(self, page_idx, renamable):
5615         """
5616         Sets whether a tab can be renamed via a left double-click or not.
5617
5618         :param `page_idx`: the page index;
5619         :param `renamable`: ``True`` if the page can be renamed.
5620         """
5621
5622         if page_idx >= self._tabs.GetPageCount():
5623             return False
5624
5625         # update our own tab catalog
5626         page_info = self._tabs.GetPage(page_idx)
5627         page_info.renamable = renamable
5628
5629         # update what's on screen
5630         ctrl, ctrl_idx = self.FindTab(page_info.window)
5631         if not ctrl:
5632             return False
5633
5634         info = ctrl.GetPage(ctrl_idx)
5635         info.renamable = page_info.renamable
5636
5637         return True
5638
5639
5640     def IsRenamable(self, page_idx):
5641         """
5642         Returns whether a tab can be renamed or not.
5643
5644         :param `page_idx`: the page index.
5645
5646         :returns: ``True`` is a page can be renamed, ``False`` otherwise.
5647         """
5648
5649         if page_idx >= self._tabs.GetPageCount():
5650             return False
5651
5652         page_info = self._tabs.GetPage(page_idx)
5653         return page_info.renamable
5654
5655
5656     def OnRenameCancelled(self, page_index):
5657         """
5658         Called by L{TabTextCtrl}, to cancel the changes and to send the
5659         `EVT_AUINOTEBOOK_END_LABEL_EDIT` event.
5660
5661         :param `page_index`: the page index in the notebook.
5662         """
5663
5664         # let owner know that the edit was cancelled
5665         evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_END_LABEL_EDIT, self.GetId())
5666
5667         evt.SetSelection(page_index)
5668         evt.SetEventObject(self)
5669         evt.SetLabel("")
5670         evt.SetEditCanceled(True)
5671         self.GetEventHandler().ProcessEvent(evt)
5672
5673
5674     def OnRenameAccept(self, page_index, value):
5675         """
5676         Called by L{TabTextCtrl}, to accept the changes and to send the
5677         `EVT_AUINOTEBOOK_END_LABEL_EDIT` event.
5678
5679         :param `page_index`: the page index in the notebook;
5680         :param `value`: the new label for the tab.
5681         """
5682
5683         evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_END_LABEL_EDIT, self.GetId())
5684         evt.SetSelection(page_index)
5685         evt.SetEventObject(self)
5686         evt.SetLabel(value)
5687         evt.SetEditCanceled(False)
5688
5689         return not self.GetEventHandler().ProcessEvent(evt) or evt.IsAllowed()
5690
5691
5692     def ResetTextControl(self):
5693         """ Called by L{TabTextCtrl} when it marks itself for deletion. """
5694
5695         if not self._textCtrl:
5696             return
5697
5698         self._textCtrl.Destroy()
5699         self._textCtrl = None
5700
5701         # tab height might have changed
5702         self.UpdateTabCtrlHeight(force=True)
5703
5704
5705     def EditTab(self, page_index):
5706         """
5707         Starts the editing of an item label, sending a `EVT_AUINOTEBOOK_BEGIN_LABEL_EDIT` event.
5708
5709         :param `page_index`: the page index we want to edit.
5710         """
5711
5712         if page_index >= self._tabs.GetPageCount():
5713             return False
5714
5715         if not self.IsRenamable(page_index):
5716             return False
5717
5718         page_info = self._tabs.GetPage(page_index)
5719         ctrl, ctrl_idx = self.FindTab(page_info.window)
5720         if not ctrl:
5721             return False
5722
5723         evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BEGIN_LABEL_EDIT, self.GetId())
5724         evt.SetSelection(page_index)
5725         evt.SetEventObject(self)
5726         if self.GetEventHandler().ProcessEvent(evt) and not evt.IsAllowed():
5727             # vetoed by user
5728             return False
5729
5730         if self._textCtrl is not None and page_info != self._textCtrl.item():
5731             self._textCtrl.StopEditing()
5732
5733         self._textCtrl = TabTextCtrl(ctrl, page_info, page_index)
5734         self._textCtrl.SetFocus()
5735
5736         return True