matrice graph classe
[iramuteq] / aui / auibook.py
1 """
2 auibook contains a notebook control which implements many features common in
3 applications with dockable panes. Specifically, L{AuiNotebook} implements functionality
4 which allows the user to rearrange tab order via drag-and-drop, split the tab window
5 into many different splitter configurations, and toggle through different themes to
6 customize the control's look and feel.
7
8 An effort has been made to try to maintain an API as similar to that of `wx.Notebook`.
9
10 The default theme that is used is L{AuiDefaultTabArt}, which provides a modern, glossy
11 look and feel. The theme can be changed by calling L{AuiNotebook.SetArtProvider}.
12 """
13
14 __author__ = "Andrea Gavana <andrea.gavana@gmail.com>"
15 __date__ = "31 March 2009"
16
17
18 import wx
19 import types
20 import datetime
21
22 from wx.lib.expando import ExpandoTextCtrl
23
24 import framemanager
25 import tabart as TA
26
27 from aui_utilities import LightColour, MakeDisabledBitmap, TabDragImage
28 from aui_utilities import TakeScreenShot, RescaleScreenShot
29
30 from aui_constants import *
31
32 # AuiNotebook events
33 wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE = wx.NewEventType()
34 wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSED = wx.NewEventType()
35 wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGED = wx.NewEventType()
36 wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING = wx.NewEventType()
37 wxEVT_COMMAND_AUINOTEBOOK_BUTTON = wx.NewEventType()
38 wxEVT_COMMAND_AUINOTEBOOK_BEGIN_DRAG = wx.NewEventType()
39 wxEVT_COMMAND_AUINOTEBOOK_END_DRAG = wx.NewEventType()
40 wxEVT_COMMAND_AUINOTEBOOK_DRAG_MOTION = wx.NewEventType()
41 wxEVT_COMMAND_AUINOTEBOOK_ALLOW_DND = wx.NewEventType()
42 wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE = wx.NewEventType()
43 wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_DOWN = wx.NewEventType()
44 wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_UP = wx.NewEventType()
45 wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_DOWN = wx.NewEventType()
46 wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP = wx.NewEventType()
47 wxEVT_COMMAND_AUINOTEBOOK_TAB_DCLICK = wx.NewEventType()
48 wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_DOWN = wx.NewEventType()
49 wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_UP = wx.NewEventType()
50 wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_DOWN = wx.NewEventType()
51 wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_UP = wx.NewEventType()
52 wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK = wx.NewEventType()
53
54 # Define a new event for a drag cancelled
55 wxEVT_COMMAND_AUINOTEBOOK_CANCEL_DRAG = wx.NewEventType()
56
57 # Define events for editing a tab label
58 wxEVT_COMMAND_AUINOTEBOOK_BEGIN_LABEL_EDIT = wx.NewEventType()
59 wxEVT_COMMAND_AUINOTEBOOK_END_LABEL_EDIT = wx.NewEventType()
60
61 # Create event binders
62 EVT_AUINOTEBOOK_PAGE_CLOSE = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE, 1)
63 """ A tab in `AuiNotebook` is being closed. Can be vetoed by calling `Veto()`. """
64 EVT_AUINOTEBOOK_PAGE_CLOSED = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSED, 1)
65 """ A tab in `AuiNotebook` has been closed. """
66 EVT_AUINOTEBOOK_PAGE_CHANGED = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGED, 1)
67 """ The page selection was changed. """
68 EVT_AUINOTEBOOK_PAGE_CHANGING = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, 1)
69 """ The page selection is being changed. """
70 EVT_AUINOTEBOOK_BUTTON = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BUTTON, 1)
71 """ The user clicked on a button in the `AuiNotebook` tab area. """
72 EVT_AUINOTEBOOK_BEGIN_DRAG = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BEGIN_DRAG, 1)
73 """ A drag-and-drop operation on a notebook tab has started. """
74 EVT_AUINOTEBOOK_END_DRAG = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_END_DRAG, 1)
75 """ A drag-and-drop operation on a notebook tab has finished. """
76 EVT_AUINOTEBOOK_DRAG_MOTION = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_DRAG_MOTION, 1)
77 """ A drag-and-drop operation on a notebook tab is ongoing. """
78 EVT_AUINOTEBOOK_ALLOW_DND = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_ALLOW_DND, 1)
79 """ Fires an event asking if it is OK to drag and drop a tab. """
80 EVT_AUINOTEBOOK_DRAG_DONE = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_DRAG_DONE, 1)
81 """ A drag-and-drop operation on a notebook tab has finished. """
82 EVT_AUINOTEBOOK_TAB_MIDDLE_DOWN = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_DOWN, 1)
83 """ The user clicked with the middle mouse button on a tab. """
84 EVT_AUINOTEBOOK_TAB_MIDDLE_UP = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_UP, 1)
85 """ The user clicked with the middle mouse button on a tab. """
86 EVT_AUINOTEBOOK_TAB_RIGHT_DOWN = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_DOWN, 1)
87 """ The user clicked with the right mouse button on a tab. """
88 EVT_AUINOTEBOOK_TAB_RIGHT_UP = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP, 1)
89 """ The user clicked with the right mouse button on a tab. """
90 EVT_AUINOTEBOOK_BG_MIDDLE_DOWN = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_DOWN, 1)
91 """ The user middle-clicked in the tab area but not over a tab or a button. """
92 EVT_AUINOTEBOOK_BG_MIDDLE_UP = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_UP, 1)
93 """ The user middle-clicked in the tab area but not over a tab or a button. """
94 EVT_AUINOTEBOOK_BG_RIGHT_DOWN = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_DOWN, 1)
95 """ The user right-clicked in the tab area but not over a tab or a button. """
96 EVT_AUINOTEBOOK_BG_RIGHT_UP = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_UP, 1)
97 """ The user right-clicked in the tab area but not over a tab or a button. """
98 EVT_AUINOTEBOOK_BG_DCLICK = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK, 1)
99 """ The user left-clicked on the tab area not occupied by `AuiNotebook` tabs. """
100 EVT_AUINOTEBOOK_CANCEL_DRAG = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_CANCEL_DRAG, 1)
101 """ A drag and drop operation has been cancelled. """
102 EVT_AUINOTEBOOK_TAB_DCLICK = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_TAB_DCLICK, 1)
103 """ The user double-clicked with the left mouse button on a tab. """
104 EVT_AUINOTEBOOK_BEGIN_LABEL_EDIT = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_BEGIN_LABEL_EDIT, 1)
105 """ The user double-clicked with the left mouse button on a tab which text is editable. """
106 EVT_AUINOTEBOOK_END_LABEL_EDIT = wx.PyEventBinder(wxEVT_COMMAND_AUINOTEBOOK_END_LABEL_EDIT, 1)
107 """ The user finished editing a tab label. """
108
109
110 # -----------------------------------------------------------------------------
111 # Auxiliary class: TabTextCtrl
112 # This is the temporary ExpandoTextCtrl created when you edit the text of a tab
113 # -----------------------------------------------------------------------------
114
115 class TabTextCtrl(ExpandoTextCtrl):
116     """ Control used for in-place edit. """
117
118     def __init__(self, owner, tab, page_index):
119         """
120         Default class constructor.
121         For internal use: do not call it in your code!
122
123         :param `owner`: the L{AuiTabCtrl} owning the tab;
124         :param `tab`: the actual L{AuiNotebookPage} tab;
125         :param `page_index`: the L{AuiNotebook} page index for the tab.
126         """
127
128         self._owner = owner
129         self._tabEdited = tab
130         self._pageIndex = page_index
131         self._startValue = tab.caption
132         self._finished = False
133         self._aboutToFinish = False
134         self._currentValue = self._startValue
135
136         x, y, w, h = self._tabEdited.rect
137
138         wnd = self._tabEdited.control
139         if wnd:
140             x += wnd.GetSize()[0] + 2
141             h = 0
142
143         image_h = 0
144         image_w = 0
145
146         image = tab.bitmap
147
148         if image.IsOk():
149             image_w, image_h = image.GetWidth(), image.GetHeight()
150             image_w += 6
151
152         dc = wx.ClientDC(self._owner)
153         h = max(image_h, dc.GetMultiLineTextExtent(tab.caption)[1])
154         h = h + 2
155
156         # FIXME: what are all these hardcoded 4, 8 and 11s really?
157         x += image_w
158         w -= image_w + 4
159
160         y = (self._tabEdited.rect.height - h)/2 + 1
161
162         expandoStyle = wx.WANTS_CHARS
163         if wx.Platform in ["__WXGTK__", "__WXMAC__"]:
164             expandoStyle |= wx.SIMPLE_BORDER
165             xSize, ySize = w + 2, h
166         else:
167             expandoStyle |= wx.SUNKEN_BORDER
168             xSize, ySize = w + 2, h+2
169
170         ExpandoTextCtrl.__init__(self, self._owner, wx.ID_ANY, self._startValue,
171                                  wx.Point(x, y), wx.Size(xSize, ySize),
172                                  expandoStyle)
173
174         if wx.Platform == "__WXMAC__":
175             self.SetFont(owner.GetFont())
176             bs = self.GetBestSize()
177             self.SetSize((-1, bs.height))
178
179         self.Bind(wx.EVT_CHAR, self.OnChar)
180         self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
181         self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
182
183
184     def AcceptChanges(self):
185         """ Accepts/refuses the changes made by the user. """
186
187         value = self.GetValue()
188         notebook = self._owner.GetParent()
189
190         if value == self._startValue:
191             # nothing changed, always accept
192             # when an item remains unchanged, the owner
193             # needs to be notified that the user decided
194             # not to change the tree item label, and that
195             # the edit has been cancelled
196             notebook.OnRenameCancelled(self._pageIndex)
197             return True
198
199         if not notebook.OnRenameAccept(self._pageIndex, value):
200             # vetoed by the user
201             return False
202
203         # accepted, do rename the item
204         notebook.SetPageText(self._pageIndex, value)
205
206         return True
207
208
209     def Finish(self):
210         """ Finish editing. """
211
212         if not self._finished:
213
214             notebook = self._owner.GetParent()
215
216             self._finished = True
217             self._owner.SetFocus()
218             notebook.ResetTextControl()
219
220
221     def OnChar(self, event):
222         """
223         Handles the ``wx.EVT_CHAR`` event for L{TabTextCtrl}.
224
225         :param `event`: a `wx.KeyEvent` event to be processed.
226         """
227
228         keycode = event.GetKeyCode()
229         shiftDown = event.ShiftDown()
230
231         if keycode == wx.WXK_RETURN:
232             if shiftDown and self._tabEdited.IsMultiline():
233                 event.Skip()
234             else:
235                 self._aboutToFinish = True
236                 self.SetValue(self._currentValue)
237                 # Notify the owner about the changes
238                 self.AcceptChanges()
239                 # Even if vetoed, close the control (consistent with MSW)
240                 wx.CallAfter(self.Finish)
241
242         elif keycode == wx.WXK_ESCAPE:
243             self.StopEditing()
244
245         else:
246             event.Skip()
247
248
249     def OnKeyUp(self, event):
250         """
251         Handles the ``wx.EVT_KEY_UP`` event for L{TabTextCtrl}.
252
253         :param `event`: a `wx.KeyEvent` event to be processed.
254         """
255
256         if not self._finished:
257
258             # auto-grow the textctrl:
259             mySize = self.GetSize()
260
261             dc = wx.ClientDC(self)
262             sx, sy, dummy = dc.GetMultiLineTextExtent(self.GetValue() + "M")
263
264             self.SetSize((sx, -1))
265             self._currentValue = self.GetValue()
266
267         event.Skip()
268
269
270     def OnKillFocus(self, event):
271         """
272         Handles the ``wx.EVT_KILL_FOCUS`` event for L{TabTextCtrl}.
273
274         :param `event`: a `wx.FocusEvent` event to be processed.
275         """
276
277         if not self._finished and not self._aboutToFinish:
278
279             # We must finish regardless of success, otherwise we'll get
280             # focus problems:
281             if not self.AcceptChanges():
282                 self._owner.GetParent().OnRenameCancelled(self._pageIndex)
283
284         # We must let the native text control handle focus, too, otherwise
285         # it could have problems with the cursor (e.g., in wxGTK).
286         event.Skip()
287         wx.CallAfter(self._owner.GetParent().ResetTextControl)
288
289
290     def StopEditing(self):
291         """ Suddenly stops the editing. """
292
293         self._owner.GetParent().OnRenameCancelled(self._pageIndex)
294         self.Finish()
295
296
297     def item(self):
298         """ Returns the item currently edited. """
299
300         return self._tabEdited
301
302
303 # ----------------------------------------------------------------------
304
305 class AuiNotebookPage(object):
306     """
307     A simple class which holds information about tab captions, bitmaps and
308     colours.
309     """
310
311     def __init__(self):
312         """
313         Default class constructor.
314         Used internally, do not call it in your code!
315         """
316
317         self.window = None              # page's associated window
318         self.caption = ""               # caption displayed on the tab
319         self.bitmap = wx.NullBitmap     # tab's bitmap
320         self.dis_bitmap = wx.NullBitmap # tab's disabled bitmap
321         self.rect = wx.Rect()           # tab's hit rectangle
322         self.active = False             # True if the page is currently active
323         self.enabled = True             # True if the page is currently enabled
324         self.hasCloseButton = True      # True if the page has a close button using the style
325                                         # AUI_NB_CLOSE_ON_ALL_TABS
326         self.control = None             # A control can now be inside a tab
327         self.renamable = False          # If True, a tab can be renamed by a left double-click
328
329         self.text_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNTEXT)
330
331         self.access_time = datetime.datetime.now() # Last time this page was selected
332
333
334     def IsMultiline(self):
335         """ Returns whether the tab contains multiline text. """
336
337         return "\n" in self.caption
338
339
340 # ----------------------------------------------------------------------
341
342 class AuiTabContainerButton(object):
343     """
344     A simple class which holds information about tab buttons and their state.
345     """
346
347     def __init__(self):
348         """
349         Default class constructor.
350         Used internally, do not call it in your code!
351         """
352
353         self.id = -1                                      # button's id
354         self.cur_state = AUI_BUTTON_STATE_NORMAL          # current state (normal, hover, pressed, etc.)
355         self.location = wx.LEFT                           # buttons location (wxLEFT, wxRIGHT, or wxCENTER)
356         self.bitmap = wx.NullBitmap                       # button's hover bitmap
357         self.dis_bitmap = wx.NullBitmap                   # button's disabled bitmap
358         self.rect = wx.Rect()                             # button's hit rectangle
359
360
361 # ----------------------------------------------------------------------
362
363 class CommandNotebookEvent(wx.PyCommandEvent):
364     """ A specialized command event class for events sent by L{AuiNotebook} . """
365
366     def __init__(self, command_type=None, win_id=0):
367         """
368         Default class constructor.
369
370         :param `command_type`: the event kind or an instance of `wx.PyCommandEvent`.
371         :param `win_id`: the window identification number.
372         """
373
374         if type(command_type) == types.IntType:
375             wx.PyCommandEvent.__init__(self, command_type, win_id)
376         else:
377             wx.PyCommandEvent.__init__(self, command_type.GetEventType(), command_type.GetId())
378
379         self.old_selection = -1
380         self.selection = -1
381         self.drag_source = None
382         self.dispatched = 0
383         self.label = ""
384         self.editCancelled = False
385
386
387     def SetSelection(self, s):
388         """
389         Sets the selection member variable.
390
391         :param `s`: the new selection.
392         """
393
394         self.selection = s
395         self._commandInt = s
396
397
398     def GetSelection(self):
399         """ Returns the currently selected page, or -1 if none was selected. """
400
401         return self.selection
402
403
404     def SetOldSelection(self, s):
405         """
406         Sets the id of the page selected before the change.
407
408         :param `s`: the old selection.
409         """
410
411         self.old_selection = s
412
413
414     def GetOldSelection(self):
415         """
416         Returns the page that was selected before the change, or -1 if none was
417         selected.
418         """
419
420         return self.old_selection
421
422
423     def SetDragSource(self, s):
424         """
425         Sets the drag and drop source.
426
427         :param `s`: the drag source.
428         """
429
430         self.drag_source = s
431
432
433     def GetDragSource(self):
434         """ Returns the drag and drop source. """
435
436         return self.drag_source
437
438
439     def SetDispatched(self, b):
440         """
441         Sets the event as dispatched (used for automatic L{AuiNotebook} ).
442
443         :param `b`: whether the event was dispatched or not.
444         """
445
446         self.dispatched = b
447
448
449     def GetDispatched(self):
450         """ Returns whether the event was dispatched (used for automatic L{AuiNotebook} ). """
451
452         return self.dispatched
453
454
455     def IsEditCancelled(self):
456         """ Returns the edit cancel flag (for ``EVT_AUINOTEBOOK_BEGIN`` | ``END_LABEL_EDIT`` only)."""
457
458         return self.editCancelled
459
460
461     def SetEditCanceled(self, editCancelled):
462         """
463         Sets the edit cancel flag (for ``EVT_AUINOTEBOOK_BEGIN`` | ``END_LABEL_EDIT`` only).
464
465         :param `editCancelled`: whether the editing action has been cancelled or not.
466         """
467
468         self.editCancelled = editCancelled
469
470
471     def GetLabel(self):
472         """Returns the label-itemtext (for ``EVT_AUINOTEBOOK_BEGIN`` | ``END_LABEL_EDIT`` only)."""
473
474         return self.label
475
476
477     def SetLabel(self, label):
478         """
479         Sets the label. Useful only for ``EVT_AUINOTEBOOK_END_LABEL_EDIT``.
480
481         :param `label`: the new label.
482         """
483
484         self.label = label
485
486
487 # ----------------------------------------------------------------------
488
489 class AuiNotebookEvent(CommandNotebookEvent):
490     """ A specialized command event class for events sent by L{AuiNotebook}. """
491
492     def __init__(self, command_type=None, win_id=0):
493         """
494         Default class constructor.
495
496         :param `command_type`: the event kind or an instance of `wx.PyCommandEvent`.
497         :param `win_id`: the window identification number.
498         """
499
500         CommandNotebookEvent.__init__(self, command_type, win_id)
501
502         if type(command_type) == types.IntType:
503             self.notify = wx.NotifyEvent(command_type, win_id)
504         else:
505             self.notify = wx.NotifyEvent(command_type.GetEventType(), command_type.GetId())
506
507
508     def GetNotifyEvent(self):
509         """ Returns the actual `wx.NotifyEvent`. """
510
511         return self.notify
512
513
514     def IsAllowed(self):
515         """ Returns whether the event is allowed or not. """
516
517         return self.notify.IsAllowed()
518
519
520     def Veto(self):
521         """
522         Prevents the change announced by this event from happening.
523
524         It is in general a good idea to notify the user about the reasons for
525         vetoing the change because otherwise the applications behaviour (which
526         just refuses to do what the user wants) might be quite surprising.
527         """
528
529         self.notify.Veto()
530
531
532     def Allow(self):
533         """
534         This is the opposite of L{Veto}: it explicitly allows the event to be
535         processed. For most events it is not necessary to call this method as the
536         events are allowed anyhow but some are forbidden by default (this will
537         be mentioned in the corresponding event description).
538         """
539
540         self.notify.Allow()
541
542
543 # ---------------------------------------------------------------------------- #
544 # Class TabNavigatorWindow
545 # ---------------------------------------------------------------------------- #
546
547 class TabNavigatorWindow(wx.Dialog):
548     """
549     This class is used to create a modal dialog that enables "Smart Tabbing",
550     similar to what you would get by hitting ``Alt`` + ``Tab`` on Windows.
551     """
552
553     def __init__(self, parent=None, icon=None):
554         """
555         Default class constructor. Used internally.
556
557         :param `parent`: the L{TabNavigatorWindow} parent;
558         :param `icon`: the L{TabNavigatorWindow} icon.
559         """
560
561         wx.Dialog.__init__(self, parent, wx.ID_ANY, "", style=0)
562
563         self._selectedItem = -1
564         self._indexMap = []
565
566         if icon is None:
567             self._bmp = Mondrian.GetBitmap()
568         else:
569             self._bmp = icon
570
571         if self._bmp.GetSize() != (16, 16):
572             img = self._bmp.ConvertToImage()
573             img.Rescale(16, 16, wx.IMAGE_QUALITY_HIGH)
574             self._bmp = wx.BitmapFromImage(img)
575
576         sz = wx.BoxSizer(wx.VERTICAL)
577
578         self._listBox = wx.ListBox(self, wx.ID_ANY, wx.DefaultPosition, wx.Size(200, 150), [], wx.LB_SINGLE | wx.NO_BORDER)
579
580         mem_dc = wx.MemoryDC()
581         mem_dc.SelectObject(wx.EmptyBitmap(1,1))
582         font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
583         font.SetWeight(wx.BOLD)
584         mem_dc.SetFont(font)
585
586         panelHeight = mem_dc.GetCharHeight()
587         panelHeight += 4 # Place a spacer of 2 pixels
588
589         # Out signpost bitmap is 24 pixels
590         if panelHeight < 24:
591             panelHeight = 24
592
593         self._panel = wx.Panel(self, wx.ID_ANY, wx.DefaultPosition, wx.Size(200, panelHeight))
594
595         sz.Add(self._panel)
596         sz.Add(self._listBox, 1, wx.EXPAND)
597
598         self.SetSizer(sz)
599
600         # Connect events to the list box
601         self._listBox.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
602         self._listBox.Bind(wx.EVT_NAVIGATION_KEY, self.OnNavigationKey)
603         self._listBox.Bind(wx.EVT_LISTBOX_DCLICK, self.OnItemSelected)
604
605         # Connect paint event to the panel
606         self._panel.Bind(wx.EVT_PAINT, self.OnPanelPaint)
607         self._panel.Bind(wx.EVT_ERASE_BACKGROUND, self.OnPanelEraseBg)
608
609         self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE))
610         self._listBox.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE))
611         self.PopulateListControl(parent)
612
613         self.GetSizer().Fit(self)
614         self.GetSizer().SetSizeHints(self)
615         self.GetSizer().Layout()
616         self.Centre()
617
618         # Set focus on the list box to avoid having to click on it to change
619         # the tab selection under GTK.
620         self._listBox.SetFocus()
621
622
623     def OnKeyUp(self, event):
624         """
625         Handles the ``wx.EVT_KEY_UP`` for the L{TabNavigatorWindow}.
626
627         :param `event`: a `wx.KeyEvent` event to be processed.
628         """
629
630         if event.GetKeyCode() == wx.WXK_CONTROL:
631             self.CloseDialog()
632
633
634     def OnNavigationKey(self, event):
635         """
636         Handles the ``wx.EVT_NAVIGATION_KEY`` for the L{TabNavigatorWindow}.
637
638         :param `event`: a `wx.NavigationKeyEvent` event to be processed.
639         """
640
641         selected = self._listBox.GetSelection()
642         bk = self.GetParent()
643         maxItems = bk.GetPageCount()
644
645         if event.GetDirection():
646
647             # Select next page
648             if selected == maxItems - 1:
649                 itemToSelect = 0
650             else:
651                 itemToSelect = selected + 1
652
653         else:
654
655             # Previous page
656             if selected == 0:
657                 itemToSelect = maxItems - 1
658             else:
659                 itemToSelect = selected - 1
660
661         self._listBox.SetSelection(itemToSelect)
662
663
664     def PopulateListControl(self, book):
665         """
666         Populates the L{TabNavigatorWindow} listbox with a list of tabs.
667
668         :param `book`: the actual L{AuiNotebook}.
669         """
670         # Index of currently selected page
671         selection = book.GetSelection()
672         # Total number of pages
673         count = book.GetPageCount()
674         # List of (index, AuiNotebookPage)
675         pages = list(enumerate(book.GetTabContainer().GetPages()))
676         if book.GetAGWWindowStyleFlag() & AUI_NB_ORDER_BY_ACCESS:
677             # Sort pages using last access time. Most recently used is the
678             # first in line
679             pages.sort(
680                 key = lambda element: element[1].access_time,
681                 reverse = True
682             )
683         else:
684             # Manually add the current selection as first item
685             # Remaining ones are added in the next loop
686             del pages[selection]
687             self._listBox.Append(book.GetPageText(selection))
688             self._indexMap.append(selection)
689
690         for (index, page) in pages:
691             self._listBox.Append(book.GetPageText(index))
692             self._indexMap.append(index)
693
694         # Select the next entry after the current selection
695         self._listBox.SetSelection(0)
696         dummy = wx.NavigationKeyEvent()
697         dummy.SetDirection(True)
698         self.OnNavigationKey(dummy)
699
700
701     def OnItemSelected(self, event):
702         """
703         Handles the ``wx.EVT_LISTBOX_DCLICK`` event for the wx.ListBox inside L{TabNavigatorWindow}.
704
705         :param `event`: a `wx.ListEvent` event to be processed.
706         """
707
708         self.CloseDialog()
709
710
711     def CloseDialog(self):
712         """ Closes the L{TabNavigatorWindow} dialog, setting selection in L{AuiNotebook}. """
713
714         bk = self.GetParent()
715         self._selectedItem = self._listBox.GetSelection()
716         self.EndModal(wx.ID_OK)
717
718
719     def GetSelectedPage(self):
720         """ Gets the page index that was selected when the dialog was closed. """
721
722         return self._indexMap[self._selectedItem]
723
724
725     def OnPanelPaint(self, event):
726         """
727         Handles the ``wx.EVT_PAINT`` event for L{TabNavigatorWindow} top panel.
728
729         :param `event`: a `wx.PaintEvent` event to be processed.
730         """
731
732         dc = wx.PaintDC(self._panel)
733         rect = self._panel.GetClientRect()
734
735         bmp = wx.EmptyBitmap(rect.width, rect.height)
736
737         mem_dc = wx.MemoryDC()
738         mem_dc.SelectObject(bmp)
739
740         endColour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)
741         startColour = LightColour(endColour, 50)
742         mem_dc.GradientFillLinear(rect, startColour, endColour, wx.SOUTH)
743
744         # Draw the caption title and place the bitmap
745         # get the bitmap optimal position, and draw it
746         bmpPt, txtPt = wx.Point(), wx.Point()
747         bmpPt.y = (rect.height - self._bmp.GetHeight())/2
748         bmpPt.x = 3
749         mem_dc.DrawBitmap(self._bmp, bmpPt.x, bmpPt.y, True)
750
751         # get the text position, and draw it
752         font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
753         font.SetWeight(wx.BOLD)
754         mem_dc.SetFont(font)
755         fontHeight = mem_dc.GetCharHeight()
756
757         txtPt.x = bmpPt.x + self._bmp.GetWidth() + 4
758         txtPt.y = (rect.height - fontHeight)/2
759         mem_dc.SetTextForeground(wx.WHITE)
760         mem_dc.DrawText("Opened tabs:", txtPt.x, txtPt.y)
761         mem_dc.SelectObject(wx.NullBitmap)
762
763         dc.DrawBitmap(bmp, 0, 0)
764
765
766     def OnPanelEraseBg(self, event):
767         """
768         Handles the ``wx.EVT_ERASE_BACKGROUND`` event for L{TabNavigatorWindow} top panel.
769
770         :param `event`: a `wx.EraseEvent` event to be processed.
771
772         :note: This is intentionally empty, to reduce flicker.
773         """
774
775         pass
776
777
778 # ----------------------------------------------------------------------
779 # -- AuiTabContainer class implementation --
780
781 class AuiTabContainer(object):
782     """
783     AuiTabContainer is a class which contains information about each
784     tab. It also can render an entire tab control to a specified DC.
785     It's not a window class itself, because this code will be used by
786     the L{AuiManager}, where it is disadvantageous to have separate
787     windows for each tab control in the case of "docked tabs".
788
789     A derived class, L{AuiTabCtrl}, is an actual `wx.Window`-derived window
790     which can be used as a tab control in the normal sense.
791     """
792
793     def __init__(self, auiNotebook):
794         """
795         Default class constructor.
796         Used internally, do not call it in your code!
797
798         :param `auiNotebook`: the parent L{AuiNotebook} window.        
799         """
800
801         self._tab_offset = 0
802         self._agwFlags = 0
803         self._art = TA.AuiDefaultTabArt()
804
805         self._buttons = []
806         self._pages = []
807         self._tab_close_buttons = []
808
809         self._rect = wx.Rect()
810         self._auiNotebook = auiNotebook
811
812         self.AddButton(AUI_BUTTON_LEFT, wx.LEFT)
813         self.AddButton(AUI_BUTTON_RIGHT, wx.RIGHT)
814         self.AddButton(AUI_BUTTON_WINDOWLIST, wx.RIGHT)
815         self.AddButton(AUI_BUTTON_CLOSE, wx.RIGHT)
816
817
818     def SetArtProvider(self, art):
819         """
820         Instructs L{AuiTabContainer} to use art provider specified by parameter `art`
821         for all drawing calls. This allows plugable look-and-feel features.
822
823         :param `art`: an art provider.
824
825         :note: The previous art provider object, if any, will be deleted by L{AuiTabContainer}.
826         """
827
828         del self._art
829         self._art = art
830
831         if self._art:
832             self._art.SetAGWFlags(self._agwFlags)
833
834
835     def GetArtProvider(self):
836         """ Returns the current art provider being used. """
837
838         return self._art
839
840
841     def SetAGWFlags(self, agwFlags):
842         """
843         Sets the tab art flags.
844
845         :param `agwFlags`: a combination of the following values:
846
847          ==================================== ==================================
848          Flag name                            Description
849          ==================================== ==================================
850          ``AUI_NB_TOP``                       With this style, tabs are drawn along the top of the notebook
851          ``AUI_NB_LEFT``                      With this style, tabs are drawn along the left of the notebook. Not implemented yet
852          ``AUI_NB_RIGHT``                     With this style, tabs are drawn along the right of the notebook. Not implemented yet
853          ``AUI_NB_BOTTOM``                    With this style, tabs are drawn along the bottom of the notebook
854          ``AUI_NB_TAB_SPLIT``                 Allows the tab control to be split by dragging a tab
855          ``AUI_NB_TAB_MOVE``                  Allows a tab to be moved horizontally by dragging
856          ``AUI_NB_TAB_EXTERNAL_MOVE``         Allows a tab to be moved to another tab control
857          ``AUI_NB_TAB_FIXED_WIDTH``           With this style, all tabs have the same width
858          ``AUI_NB_SCROLL_BUTTONS``            With this style, left and right scroll buttons are displayed
859          ``AUI_NB_WINDOWLIST_BUTTON``         With this style, a drop-down list of windows is available
860          ``AUI_NB_CLOSE_BUTTON``              With this style, a close button is available on the tab bar
861          ``AUI_NB_CLOSE_ON_ACTIVE_TAB``       With this style, a close button is available on the active tab
862          ``AUI_NB_CLOSE_ON_ALL_TABS``         With this style, a close button is available on all tabs
863          ``AUI_NB_MIDDLE_CLICK_CLOSE``        Allows to close L{AuiNotebook} tabs by mouse middle button click
864          ``AUI_NB_SUB_NOTEBOOK``              This style is used by L{AuiManager} to create automatic AuiNotebooks
865          ``AUI_NB_HIDE_ON_SINGLE_TAB``        Hides the tab window if only one tab is present
866          ``AUI_NB_SMART_TABS``                Use Smart Tabbing, like ``Alt`` + ``Tab`` on Windows
867          ``AUI_NB_USE_IMAGES_DROPDOWN``       Uses images on dropdown window list menu instead of check items
868          ``AUI_NB_CLOSE_ON_TAB_LEFT``         Draws the tab close button on the left instead of on the right (a la Camino browser)
869          ``AUI_NB_TAB_FLOAT``                 Allows the floating of single tabs. Known limitation: when the notebook is more or less full screen, tabs cannot be dragged far enough outside of the notebook to become floating pages
870          ``AUI_NB_DRAW_DND_TAB``              Draws an image representation of a tab while dragging (on by default)
871          ``AUI_NB_ORDER_BY_ACCESS``           Tab navigation order by last access time for the tabs
872          ``AUI_NB_NO_TAB_FOCUS``              Don't draw tab focus rectangle
873          ==================================== ==================================
874
875         :todo: Implementation of flags ``AUI_NB_RIGHT`` and ``AUI_NB_LEFT``.
876
877         """
878
879         self._agwFlags = agwFlags
880
881         # check for new close button settings
882         self.RemoveButton(AUI_BUTTON_LEFT)
883         self.RemoveButton(AUI_BUTTON_RIGHT)
884         self.RemoveButton(AUI_BUTTON_WINDOWLIST)
885         self.RemoveButton(AUI_BUTTON_CLOSE)
886
887         if agwFlags & AUI_NB_SCROLL_BUTTONS:
888             self.AddButton(AUI_BUTTON_LEFT, wx.LEFT)
889             self.AddButton(AUI_BUTTON_RIGHT, wx.RIGHT)
890
891         if agwFlags & AUI_NB_WINDOWLIST_BUTTON:
892             self.AddButton(AUI_BUTTON_WINDOWLIST, wx.RIGHT)
893
894         if agwFlags & AUI_NB_CLOSE_BUTTON:
895             self.AddButton(AUI_BUTTON_CLOSE, wx.RIGHT)
896
897         if self._art:
898             self._art.SetAGWFlags(self._agwFlags)
899
900
901     def GetAGWFlags(self):
902         """
903         Returns the tab art flags.
904
905         See L{SetAGWFlags} for a list of possible return values.
906
907         :see: L{SetAGWFlags}
908         """
909
910         return self._agwFlags
911
912
913     def SetNormalFont(self, font):
914         """
915         Sets the normal font for drawing tab labels.
916
917         :param `font`: a `wx.Font` object.
918         """
919
920         self._art.SetNormalFont(font)
921
922
923     def SetSelectedFont(self, font):
924         """
925         Sets the selected tab font for drawing tab labels.
926
927         :param `font`: a `wx.Font` object.
928         """
929
930         self._art.SetSelectedFont(font)
931
932
933     def SetMeasuringFont(self, font):
934         """
935         Sets the font for calculating text measurements.
936
937         :param `font`: a `wx.Font` object.
938         """
939
940         self._art.SetMeasuringFont(font)
941
942
943     def SetTabRect(self, rect):
944         """
945         Sets the tab area rectangle.
946
947         :param `rect`: an instance of `wx.Rect`, specifying the available area for L{AuiTabContainer}.
948         """
949
950         self._rect = rect
951
952         if self._art:
953             minMaxTabWidth = self._auiNotebook.GetMinMaxTabWidth()
954             self._art.SetSizingInfo(rect.GetSize(), len(self._pages), minMaxTabWidth)
955
956
957     def AddPage(self, page, info):
958         """
959         Adds a page to the tab control.
960
961         :param `page`: the window associated with this tab;
962         :param `info`: an instance of L{AuiNotebookPage}.
963         """
964
965         page_info = info
966         page_info.window = page
967
968         self._pages.append(page_info)
969
970         # let the art provider know how many pages we have
971         if self._art:
972             minMaxTabWidth = self._auiNotebook.GetMinMaxTabWidth()
973             self._art.SetSizingInfo(self._rect.GetSize(), len(self._pages), minMaxTabWidth)
974
975         return True
976
977
978     def InsertPage(self, page, info, idx):
979         """
980         Inserts a page in the tab control in the position specified by `idx`.
981
982         :param `page`: the window associated with this tab;
983         :param `info`: an instance of L{AuiNotebookPage};
984         :param `idx`: the page insertion index.
985         """
986
987         page_info = info
988         page_info.window = page
989
990         if idx >= len(self._pages):
991             self._pages.append(page_info)
992         else:
993             self._pages.insert(idx, page_info)
994
995         # let the art provider know how many pages we have
996         if self._art:
997             minMaxTabWidth = self._auiNotebook.GetMinMaxTabWidth()
998             self._art.SetSizingInfo(self._rect.GetSize(), len(self._pages), minMaxTabWidth)
999
1000         return True
1001
1002
1003     def MovePage(self, page, new_idx):
1004         """
1005         Moves a page in a new position specified by `new_idx`.
1006
1007         :param `page`: the window associated with this tab;
1008         :param `new_idx`: the new page position.
1009         """
1010
1011         idx = self.GetIdxFromWindow(page)
1012         if idx == -1:
1013             return False
1014
1015         # get page entry, make a copy of it
1016         p = self.GetPage(idx)
1017
1018         # remove old page entry
1019         self.RemovePage(page)
1020
1021         # insert page where it should be
1022         self.InsertPage(page, p, new_idx)
1023
1024         return True
1025
1026
1027     def RemovePage(self, wnd):
1028         """
1029         Removes a page from the tab control.
1030
1031         :param `wnd`: an instance of `wx.Window`, a window associated with this tab.
1032         """
1033
1034         minMaxTabWidth = self._auiNotebook.GetMinMaxTabWidth()
1035
1036         for page in self._pages:
1037             if page.window == wnd:
1038                 self._pages.remove(page)
1039                 self._tab_offset = min(self._tab_offset, len(self._pages) - 1)
1040
1041                 # let the art provider know how many pages we have
1042                 if self._art:
1043                     self._art.SetSizingInfo(self._rect.GetSize(), len(self._pages), minMaxTabWidth)
1044
1045                 return True
1046
1047         return False
1048
1049
1050     def SetActivePage(self, wndOrInt):
1051         """
1052         Sets the L{AuiTabContainer} active page.
1053
1054         :param `wndOrInt`: an instance of `wx.Window` or an integer specifying a tab index.
1055         """
1056
1057         if type(wndOrInt) == types.IntType:
1058
1059             if wndOrInt >= len(self._pages):
1060                 return False
1061
1062             wnd = self._pages[wndOrInt].window
1063
1064         else:
1065             wnd = wndOrInt
1066
1067         found = False
1068
1069         for indx, page in enumerate(self._pages):
1070             if page.window == wnd:
1071                 page.active = True
1072                 found = True
1073             else:
1074                 page.active = False
1075
1076         return found
1077
1078
1079     def SetNoneActive(self):
1080         """ Sets all the tabs as inactive (non-selected). """
1081
1082         for page in self._pages:
1083             page.active = False
1084
1085
1086     def GetActivePage(self):
1087         """ Returns the current selected tab or ``wx.NOT_FOUND`` if none is selected. """
1088
1089         for indx, page in enumerate(self._pages):
1090             if page.active:
1091                 return indx
1092
1093         return wx.NOT_FOUND
1094
1095
1096     def GetWindowFromIdx(self, idx):
1097         """
1098         Returns the window associated with the tab with index `idx`.
1099
1100         :param `idx`: the tab index.
1101         """
1102
1103         if idx >= len(self._pages):
1104             return None
1105
1106         return self._pages[idx].window
1107
1108
1109     def GetIdxFromWindow(self, wnd):
1110         """
1111         Returns the tab index based on the window `wnd` associated with it.
1112
1113         :param `wnd`: an instance of `wx.Window`.
1114         """
1115
1116         for indx, page in enumerate(self._pages):
1117             if page.window == wnd:
1118                 return indx
1119
1120         return wx.NOT_FOUND
1121
1122
1123     def GetPage(self, idx):
1124         """
1125         Returns the page specified by the given index.
1126
1127         :param `idx`: the tab index.
1128         """
1129
1130         if idx < 0 or idx >= len(self._pages):
1131             raise Exception("Invalid Page index")
1132
1133         return self._pages[idx]
1134
1135
1136     def GetPages(self):
1137         """ Returns a list of all the pages in this L{AuiTabContainer}. """
1138
1139         return self._pages
1140
1141
1142     def GetPageCount(self):
1143         """ Returns the number of pages in the L{AuiTabContainer}. """
1144
1145         return len(self._pages)
1146
1147
1148     def GetEnabled(self, idx):
1149         """
1150         Returns whether a tab is enabled or not.
1151
1152         :param `idx`: the tab index.
1153         """
1154
1155         if idx < 0 or idx >= len(self._pages):
1156             return False
1157
1158         return self._pages[idx].enabled
1159
1160
1161     def EnableTab(self, idx, enable=True):
1162         """
1163         Enables/disables a tab in the L{AuiTabContainer}.
1164
1165         :param `idx`: the tab index;
1166         :param `enable`: ``True`` to enable a tab, ``False`` to disable it.
1167         """
1168
1169         if idx < 0 or idx >= len(self._pages):
1170             raise Exception("Invalid Page index")
1171
1172         self._pages[idx].enabled = enable
1173         wnd = self.GetWindowFromIdx(idx)
1174         wnd.Enable(enable)
1175
1176
1177     def AddButton(self, id, location, normal_bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap):
1178         """
1179         Adds a button in the tab area.
1180
1181         :param `id`: the button identifier. This can be one of the following:
1182
1183          ==============================  =================================
1184          Button Identifier               Description
1185          ==============================  =================================
1186          ``AUI_BUTTON_CLOSE``            Shows a close button on the tab area
1187          ``AUI_BUTTON_WINDOWLIST``       Shows a window list button on the tab area
1188          ``AUI_BUTTON_LEFT``             Shows a left button on the tab area
1189          ``AUI_BUTTON_RIGHT``            Shows a right button on the tab area
1190          ==============================  =================================
1191
1192         :param `location`: the button location. Can be ``wx.LEFT`` or ``wx.RIGHT``;
1193         :param `normal_bitmap`: the bitmap for an enabled tab;
1194         :param `disabled_bitmap`: the bitmap for a disabled tab.
1195         """
1196
1197         button = AuiTabContainerButton()
1198         button.id = id
1199         button.bitmap = normal_bitmap
1200         button.dis_bitmap = disabled_bitmap
1201         button.location = location
1202         button.cur_state = AUI_BUTTON_STATE_NORMAL
1203
1204         self._buttons.append(button)
1205
1206
1207     def RemoveButton(self, id):
1208         """
1209         Removes a button from the tab area.
1210
1211         :param `id`: the button identifier. See L{AddButton} for a list of button identifiers.
1212
1213         :see: L{AddButton}
1214         """
1215
1216         for button in self._buttons:
1217             if button.id == id:
1218                 self._buttons.remove(button)
1219                 return
1220
1221
1222     def GetTabOffset(self):
1223         """ Returns the tab offset. """
1224
1225         return self._tab_offset
1226
1227
1228     def SetTabOffset(self, offset):
1229         """
1230         Sets the tab offset.
1231
1232         :param `offset`: the tab offset.
1233         """
1234
1235         self._tab_offset = offset
1236
1237
1238     def MinimizeTabOffset(self, dc, wnd, max_width):
1239         """
1240         Minimize `self._tab_offset` to fit as many tabs as possible in the available space.
1241
1242         :param `dc`: a `wx.DC` device context;
1243         :param `wnd`: an instance of `wx.Window`;
1244         :param `max_width`: the maximum available width for the tabs.
1245         """
1246
1247         total_width = 0
1248
1249         for i, page in reversed(list(enumerate(self._pages))):
1250
1251             tab_button = self._tab_close_buttons[i]
1252             (tab_width, tab_height), x_extent = self._art.GetTabSize(dc, wnd, page.caption, page.bitmap, page.active, tab_button.cur_state, page.control)
1253             total_width += tab_width
1254
1255             if total_width > max_width:
1256
1257                 tab_offset = i + 1
1258
1259                 if tab_offset < self._tab_offset and tab_offset < len(self._pages):
1260                     self._tab_offset = tab_offset
1261
1262                 break
1263
1264         if i == 0:
1265             self._tab_offset = 0
1266
1267
1268     def Render(self, raw_dc, wnd):
1269         """
1270         Renders the tab catalog to the specified `wx.DC`.
1271
1272         It is a virtual function and can be overridden to provide custom drawing
1273         capabilities.
1274
1275         :param `raw_dc`: a `wx.DC` device context;
1276         :param `wnd`: an instance of `wx.Window`.
1277         """
1278
1279         if not raw_dc or not raw_dc.IsOk():
1280             return
1281
1282         dc = wx.MemoryDC()
1283
1284         # use the same layout direction as the window DC uses to ensure that the
1285         # text is rendered correctly
1286         dc.SetLayoutDirection(raw_dc.GetLayoutDirection())
1287
1288         page_count = len(self._pages)
1289         button_count = len(self._buttons)
1290
1291         # create off-screen bitmap
1292         bmp = wx.EmptyBitmap(self._rect.GetWidth(), self._rect.GetHeight())
1293         dc.SelectObject(bmp)
1294
1295         if not dc.IsOk():
1296             return
1297
1298         # find out if size of tabs is larger than can be
1299         # afforded on screen
1300         total_width = visible_width = 0
1301
1302         for i in xrange(page_count):
1303             page = self._pages[i]
1304
1305             # determine if a close button is on this tab
1306             close_button = False
1307             if (self._agwFlags & AUI_NB_CLOSE_ON_ALL_TABS and page.hasCloseButton) or \
1308                (self._agwFlags & AUI_NB_CLOSE_ON_ACTIVE_TAB and page.active and page.hasCloseButton):
1309
1310                 close_button = True
1311
1312             control = page.control
1313             if control:
1314                 try:
1315                     control.GetSize()
1316                 except wx.PyDeadObjectError:
1317                     page.control = None
1318
1319             size, x_extent = self._art.GetTabSize(dc, wnd, page.caption, page.bitmap, page.active,
1320                                                   (close_button and [AUI_BUTTON_STATE_NORMAL] or \
1321                                                    [AUI_BUTTON_STATE_HIDDEN])[0], page.control)
1322
1323             if i+1 < page_count:
1324                 total_width += x_extent
1325             else:
1326                 total_width += size[0]
1327
1328             if i >= self._tab_offset:
1329                 if i+1 < page_count:
1330                     visible_width += x_extent
1331                 else:
1332                     visible_width += size[0]
1333
1334         if total_width > self._rect.GetWidth() or self._tab_offset != 0:
1335
1336             # show left/right buttons
1337             for button in self._buttons:
1338                 if button.id == AUI_BUTTON_LEFT or \
1339                    button.id == AUI_BUTTON_RIGHT:
1340
1341                     button.cur_state &= ~AUI_BUTTON_STATE_HIDDEN
1342
1343         else:
1344
1345             # hide left/right buttons
1346             for button in self._buttons:
1347                 if button.id == AUI_BUTTON_LEFT or \
1348                    button.id == AUI_BUTTON_RIGHT:
1349
1350                     button.cur_state |= AUI_BUTTON_STATE_HIDDEN
1351
1352         # determine whether left button should be enabled
1353         for button in self._buttons:
1354             if button.id == AUI_BUTTON_LEFT:
1355                 if self._tab_offset == 0:
1356                     button.cur_state |= AUI_BUTTON_STATE_DISABLED
1357                 else:
1358                     button.cur_state &= ~AUI_BUTTON_STATE_DISABLED
1359
1360             if button.id == AUI_BUTTON_RIGHT:
1361                 if visible_width < self._rect.GetWidth() - 16*button_count:
1362                     button.cur_state |= AUI_BUTTON_STATE_DISABLED
1363                 else:
1364                     button.cur_state &= ~AUI_BUTTON_STATE_DISABLED
1365
1366         # draw background
1367         self._art.DrawBackground(dc, wnd, self._rect)
1368
1369         # draw buttons
1370         left_buttons_width = 0
1371         right_buttons_width = 0
1372
1373         # draw the buttons on the right side
1374         offset = self._rect.x + self._rect.width
1375
1376         for i in xrange(button_count):
1377             button = self._buttons[button_count - i - 1]
1378
1379             if button.location != wx.RIGHT:
1380                 continue
1381             if button.cur_state & AUI_BUTTON_STATE_HIDDEN:
1382                 continue
1383
1384             button_rect = wx.Rect(*self._rect)
1385             button_rect.SetY(1)
1386             button_rect.SetWidth(offset)
1387
1388             button.rect = self._art.DrawButton(dc, wnd, button_rect, button, wx.RIGHT)
1389
1390             offset -= button.rect.GetWidth()
1391             right_buttons_width += button.rect.GetWidth()
1392
1393         offset = 0
1394
1395         # draw the buttons on the left side
1396         for i in xrange(button_count):
1397             button = self._buttons[button_count - i - 1]
1398
1399             if button.location != wx.LEFT:
1400                 continue
1401             if button.cur_state & AUI_BUTTON_STATE_HIDDEN:
1402                 continue
1403
1404             button_rect = wx.Rect(offset, 1, 1000, self._rect.height)
1405
1406             button.rect = self._art.DrawButton(dc, wnd, button_rect, button, wx.LEFT)
1407
1408             offset += button.rect.GetWidth()
1409             left_buttons_width += button.rect.GetWidth()
1410
1411         offset = left_buttons_width
1412
1413         if offset == 0:
1414             offset += self._art.GetIndentSize()
1415
1416         # prepare the tab-close-button array
1417         # make sure tab button entries which aren't used are marked as hidden
1418         for i in xrange(page_count, len(self._tab_close_buttons)):
1419             self._tab_close_buttons[i].cur_state = AUI_BUTTON_STATE_HIDDEN
1420
1421         # make sure there are enough tab button entries to accommodate all tabs
1422         while len(self._tab_close_buttons) < page_count:
1423             tempbtn = AuiTabContainerButton()
1424             tempbtn.id = AUI_BUTTON_CLOSE
1425             tempbtn.location = wx.CENTER
1426             tempbtn.cur_state = AUI_BUTTON_STATE_HIDDEN
1427             self._tab_close_buttons.append(tempbtn)
1428
1429         # buttons before the tab offset must be set to hidden
1430         for i in xrange(self._tab_offset):
1431             self._tab_close_buttons[i].cur_state = AUI_BUTTON_STATE_HIDDEN
1432             if self._pages[i].control:
1433                 if self._pages[i].control.IsShown():
1434                     self._pages[i].control.Hide()
1435
1436         self.MinimizeTabOffset(dc, wnd, self._rect.GetWidth() - right_buttons_width - offset - 2)
1437
1438         # draw the tabs
1439         active = 999
1440         active_offset = 0
1441
1442         rect = wx.Rect(*self._rect)
1443         rect.y = 0
1444         rect.height = self._rect.height
1445
1446         for i in xrange(self._tab_offset, page_count):
1447
1448             page = self._pages[i]
1449             tab_button = self._tab_close_buttons[i]
1450
1451             # determine if a close button is on this tab
1452             if (self._agwFlags & AUI_NB_CLOSE_ON_ALL_TABS and page.hasCloseButton) or \
1453                (self._agwFlags & AUI_NB_CLOSE_ON_ACTIVE_TAB and page.active and page.hasCloseButton):
1454
1455                 if tab_button.cur_state == AUI_BUTTON_STATE_HIDDEN:
1456
1457                     tab_button.id = AUI_BUTTON_CLOSE
1458                     tab_button.cur_state = AUI_BUTTON_STATE_NORMAL
1459                     tab_button.location = wx.CENTER
1460
1461             else:
1462
1463                 tab_button.cur_state = AUI_BUTTON_STATE_HIDDEN
1464
1465             rect.x = offset
1466             rect.width = self._rect.width - right_buttons_width - offset - 2
1467
1468             if rect.width <= 0:
1469                 break
1470
1471             page.rect, tab_button.rect, x_extent = self._art.DrawTab(dc, wnd, page, rect, tab_button.cur_state)
1472
1473             if page.active:
1474                 active = i
1475                 active_offset = offset
1476                 active_rect = wx.Rect(*rect)
1477
1478             offset += x_extent
1479
1480         lenPages = len(self._pages)
1481         # make sure to deactivate buttons which are off the screen to the right
1482         for j in xrange(i+1, len(self._tab_close_buttons)):
1483             self._tab_close_buttons[j].cur_state = AUI_BUTTON_STATE_HIDDEN
1484             if j > 0 and j <= lenPages:
1485                 if self._pages[j-1].control:
1486                     if self._pages[j-1].control.IsShown():
1487                         self._pages[j-1].control.Hide()
1488
1489         # draw the active tab again so it stands in the foreground
1490         if active >= self._tab_offset and active < len(self._pages):
1491
1492             page = self._pages[active]
1493             tab_button = self._tab_close_buttons[active]
1494
1495             rect.x = active_offset
1496             dummy = self._art.DrawTab(dc, wnd, page, active_rect, tab_button.cur_state)
1497
1498         raw_dc.Blit(self._rect.x, self._rect.y, self._rect.GetWidth(), self._rect.GetHeight(), dc, 0, 0)
1499
1500
1501     def IsTabVisible(self, tabPage, tabOffset, dc, wnd):
1502         """
1503         Returns whether a tab is visible or not.
1504
1505         :param `tabPage`: the tab index;
1506         :param `tabOffset`: the tab offset;
1507         :param `dc`: a `wx.DC` device context;
1508         :param `wnd`: an instance of `wx.Window` derived window.
1509         """
1510
1511         if not dc or not dc.IsOk():
1512             return False
1513
1514         page_count = len(self._pages)
1515         button_count = len(self._buttons)
1516         self.Render(dc, wnd)
1517
1518         # Hasn't been rendered yet assume it's visible
1519         if len(self._tab_close_buttons) < page_count:
1520             return True
1521
1522         if self._agwFlags & AUI_NB_SCROLL_BUTTONS:
1523             # First check if both buttons are disabled - if so, there's no need to
1524             # check further for visibility.
1525             arrowButtonVisibleCount = 0
1526             for i in xrange(button_count):
1527
1528                 button = self._buttons[i]
1529                 if button.id == AUI_BUTTON_LEFT or \
1530                    button.id == AUI_BUTTON_RIGHT:
1531
1532                     if button.cur_state & AUI_BUTTON_STATE_HIDDEN == 0:
1533                         arrowButtonVisibleCount += 1
1534
1535             # Tab must be visible
1536             if arrowButtonVisibleCount == 0:
1537                 return True
1538
1539         # If tab is less than the given offset, it must be invisible by definition
1540         if tabPage < tabOffset:
1541             return False
1542
1543         # draw buttons
1544         left_buttons_width = 0
1545         right_buttons_width = 0
1546
1547         offset = 0
1548
1549         # calculate size of the buttons on the right side
1550         offset = self._rect.x + self._rect.width
1551
1552         for i in xrange(button_count):
1553             button = self._buttons[button_count - i - 1]
1554
1555             if button.location != wx.RIGHT:
1556                 continue
1557             if button.cur_state & AUI_BUTTON_STATE_HIDDEN:
1558                 continue
1559
1560             offset -= button.rect.GetWidth()
1561             right_buttons_width += button.rect.GetWidth()
1562
1563         offset = 0
1564
1565         # calculate size of the buttons on the left side
1566         for i in xrange(button_count):
1567             button = self._buttons[button_count - i - 1]
1568
1569             if button.location != wx.LEFT:
1570                 continue
1571             if button.cur_state & AUI_BUTTON_STATE_HIDDEN:
1572                 continue
1573
1574             offset += button.rect.GetWidth()
1575             left_buttons_width += button.rect.GetWidth()
1576
1577         offset = left_buttons_width
1578
1579         if offset == 0:
1580             offset += self._art.GetIndentSize()
1581
1582         rect = wx.Rect(*self._rect)
1583         rect.y = 0
1584         rect.height = self._rect.height
1585
1586         # See if the given page is visible at the given tab offset (effectively scroll position)
1587         for i in xrange(tabOffset, page_count):
1588
1589             page = self._pages[i]
1590             tab_button = self._tab_close_buttons[i]
1591
1592             rect.x = offset
1593             rect.width = self._rect.width - right_buttons_width - offset - 2
1594
1595             if rect.width <= 0:
1596                 return False # haven't found the tab, and we've run out of space, so return False
1597
1598             size, x_extent = self._art.GetTabSize(dc, wnd, page.caption, page.bitmap, page.active, tab_button.cur_state, page.control)
1599             offset += x_extent
1600
1601             if i == tabPage:
1602
1603                 # If not all of the tab is visible, and supposing there's space to display it all,
1604                 # we could do better so we return False.
1605                 if (self._rect.width - right_buttons_width - offset - 2) <= 0 and (self._rect.width - right_buttons_width - left_buttons_width) > x_extent:
1606                     return False
1607                 else:
1608                     return True
1609
1610         # Shouldn't really get here, but if it does, assume the tab is visible to prevent
1611         # further looping in calling code.
1612         return True
1613
1614
1615     def MakeTabVisible(self, tabPage, win):
1616         """
1617         Make the tab visible if it wasn't already.
1618
1619         :param `tabPage`: the tab index;
1620         :param `win`: an instance of `wx.Window` derived window.
1621         """
1622
1623         dc = wx.ClientDC(win)
1624
1625         if not self.IsTabVisible(tabPage, self.GetTabOffset(), dc, win):
1626             for i in xrange(len(self._pages)):
1627                 if self.IsTabVisible(tabPage, i, dc, win):
1628                     self.SetTabOffset(i)
1629                     win.Refresh()
1630                     return
1631
1632
1633     def TabHitTest(self, x, y):
1634         """
1635         TabHitTest() tests if a tab was hit, passing the window pointer
1636         back if that condition was fulfilled.
1637
1638         :param `x`: the mouse `x` position;
1639         :param `y`: the mouse `y` position.
1640         """
1641
1642         if not self._rect.Contains((x,y)):
1643             return None
1644
1645         btn = self.ButtonHitTest(x, y)
1646         if btn:
1647             if btn in self._buttons:
1648                 return None
1649
1650         for i in xrange(self._tab_offset, len(self._pages)):
1651             page = self._pages[i]
1652             if page.rect.Contains((x,y)):
1653                 return page.window
1654
1655         return None
1656
1657
1658     def ButtonHitTest(self, x, y):
1659         """
1660         Tests if a button was hit.
1661
1662         :param `x`: the mouse `x` position;
1663         :param `y`: the mouse `y` position.
1664
1665         :returns: and instance of L{AuiTabContainerButton} if a button was hit, ``None`` otherwise.
1666         """
1667
1668         if not self._rect.Contains((x,y)):
1669             return None
1670
1671         for button in self._buttons:
1672             if button.rect.Contains((x,y)) and \
1673                (button.cur_state & (AUI_BUTTON_STATE_HIDDEN|AUI_BUTTON_STATE_DISABLED)) == 0:
1674                 return button
1675
1676         for button in self._tab_close_buttons:
1677             if button.rect.Contains((x,y)) and \
1678                (button.cur_state & (AUI_BUTTON_STATE_HIDDEN|AUI_BUTTON_STATE_DISABLED)) == 0:
1679                 return button
1680
1681         return None
1682
1683
1684     def DoShowHide(self):
1685         """
1686         This function shows the active window, then hides all of the other windows
1687         (in that order).
1688         """
1689
1690         pages = self.GetPages()
1691
1692         # show new active page first
1693         for page in pages:
1694             if page.active:
1695                 page.window.Show(True)
1696                 break
1697
1698         # hide all other pages
1699         for page in pages:
1700             if not page.active:
1701                 page.window.Show(False)
1702
1703
1704 # ----------------------------------------------------------------------
1705 # -- AuiTabCtrl class implementation --
1706
1707 class AuiTabCtrl(wx.PyControl, AuiTabContainer):
1708     """
1709     This is an actual `wx.Window`-derived window which can be used as a tab
1710     control in the normal sense.
1711     """
1712
1713     def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
1714                  style=wx.NO_BORDER|wx.WANTS_CHARS|wx.TAB_TRAVERSAL):
1715         """
1716         Default class constructor.
1717         Used internally, do not call it in your code!
1718
1719         :param `parent`: the L{AuiTabCtrl} parent;
1720         :param `id`: an identifier for the control: a value of -1 is taken to mean a default;
1721         :param `pos`: the control position. A value of (-1, -1) indicates a default position,
1722          chosen by either the windowing system or wxPython, depending on platform;
1723         :param `size`: the control size. A value of (-1, -1) indicates a default size,
1724          chosen by either the windowing system or wxPython, depending on platform;
1725         :param `style`: the window style.
1726         """
1727
1728         wx.PyControl.__init__(self, parent, id, pos, size, style, name="AuiTabCtrl")
1729         AuiTabContainer.__init__(self, parent)
1730
1731         self._click_pt = wx.Point(-1, -1)
1732         self._is_dragging = False
1733         self._hover_button = None
1734         self._pressed_button = None
1735         self._drag_image = None
1736         self._drag_img_offset = (0, 0)
1737         self._on_button = False
1738
1739         self.Bind(wx.EVT_PAINT, self.OnPaint)
1740         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
1741         self.Bind(wx.EVT_SIZE, self.OnSize)
1742         self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
1743         self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
1744         self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
1745         self.Bind(wx.EVT_MIDDLE_DOWN, self.OnMiddleDown)
1746         self.Bind(wx.EVT_MIDDLE_UP, self.OnMiddleUp)
1747         self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
1748         self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp)
1749         self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
1750         self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
1751         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
1752         self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.OnCaptureLost)
1753         self.Bind(wx.EVT_MOTION, self.OnMotion)
1754         self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)
1755         self.Bind(EVT_AUINOTEBOOK_BUTTON, self.OnButton)
1756
1757
1758     def IsDragging(self):
1759         """ Returns whether the user is dragging a tab with the mouse or not. """
1760
1761         return self._is_dragging
1762
1763
1764     def GetDefaultBorder(self):
1765         """ Returns the default border style for L{AuiTabCtrl}. """
1766
1767         return wx.BORDER_NONE
1768
1769
1770     def OnPaint(self, event):
1771         """
1772         Handles the ``wx.EVT_PAINT`` event for L{AuiTabCtrl}.
1773
1774         :param `event`: a `wx.PaintEvent` event to be processed.
1775         """
1776
1777         dc = wx.PaintDC(self)
1778         dc.SetFont(self.GetFont())
1779
1780         if self.GetPageCount() > 0:
1781             self.Render(dc, self)
1782
1783
1784     def OnEraseBackground(self, event):
1785         """
1786         Handles the ``wx.EVT_ERASE_BACKGROUND`` event for L{AuiTabCtrl}.
1787
1788         :param `event`: a `wx.EraseEvent` event to be processed.
1789
1790         :note: This is intentionally empty, to reduce flicker.
1791         """
1792
1793         pass
1794
1795
1796     def DoGetBestSize(self):
1797         """
1798         Gets the size which best suits the window: for a control, it would be the
1799         minimal size which doesn't truncate the control, for a panel - the same
1800         size as it would have after a call to `Fit()`.
1801
1802         :note: Overridden from `wx.PyControl`.
1803         """
1804
1805         return wx.Size(self._rect.width, self._rect.height)
1806
1807
1808     def OnSize(self, event):
1809         """
1810         Handles the ``wx.EVT_SIZE`` event for L{AuiTabCtrl}.
1811
1812         :param `event`: a `wx.SizeEvent` event to be processed.
1813         """
1814
1815         s = event.GetSize()
1816         self.SetTabRect(wx.Rect(0, 0, s.GetWidth(), s.GetHeight()))
1817
1818
1819     def OnLeftDown(self, event):
1820         """
1821         Handles the ``wx.EVT_LEFT_DOWN`` event for L{AuiTabCtrl}.
1822
1823         :param `event`: a `wx.MouseEvent` event to be processed.
1824         """
1825
1826         self.CaptureMouse()
1827         self._click_pt = wx.Point(-1, -1)
1828         self._is_dragging = False
1829         self._click_tab = None
1830         self._pressed_button = None
1831
1832         wnd = self.TabHitTest(event.GetX(), event.GetY())
1833
1834         if wnd is not None:
1835             new_selection = self.GetIdxFromWindow(wnd)
1836
1837             # AuiNotebooks always want to receive this event
1838             # even if the tab is already active, because they may
1839             # have multiple tab controls
1840             if new_selection != self.GetActivePage() or isinstance(self.GetParent(), AuiNotebook):
1841
1842                 e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, self.GetId())
1843                 e.SetSelection(new_selection)
1844                 e.SetOldSelection(self.GetActivePage())
1845                 e.SetEventObject(self)
1846                 self.GetEventHandler().ProcessEvent(e)
1847
1848             self._click_pt.x = event.GetX()
1849             self._click_pt.y = event.GetY()
1850             self._click_tab = wnd
1851         else:
1852             page_index = self.GetActivePage()
1853             if page_index != wx.NOT_FOUND:
1854                 self.GetWindowFromIdx(page_index).SetFocus()
1855
1856         if self._hover_button:
1857             self._pressed_button = self._hover_button
1858             self._pressed_button.cur_state = AUI_BUTTON_STATE_PRESSED
1859             self._on_button = True
1860             self.Refresh()
1861             self.Update()
1862
1863
1864     def OnCaptureLost(self, event):
1865         """
1866         Handles the ``wx.EVT_MOUSE_CAPTURE_LOST`` event for L{AuiTabCtrl}.
1867
1868         :param `event`: a `wx.MouseCaptureLostEvent` event to be processed.
1869         """
1870
1871         if self._is_dragging:
1872             self._is_dragging = False
1873             self._on_button = False
1874
1875             if self._drag_image:
1876                 self._drag_image.EndDrag()
1877                 del self._drag_image
1878                 self._drag_image = None
1879
1880             event = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_CANCEL_DRAG, self.GetId())
1881             event.SetSelection(self.GetIdxFromWindow(self._click_tab))
1882             event.SetOldSelection(event.GetSelection())
1883             event.SetEventObject(self)
1884             self.GetEventHandler().ProcessEvent(event)
1885
1886
1887     def OnLeftUp(self, event):
1888         """
1889         Handles the ``wx.EVT_LEFT_UP`` event for L{AuiTabCtrl}.
1890
1891         :param `event`: a `wx.MouseEvent` event to be processed.
1892         """
1893
1894         self._on_button = False
1895
1896         if self._is_dragging:
1897
1898             if self.HasCapture():
1899                 self.ReleaseMouse()
1900
1901             self._is_dragging = False
1902             if self._drag_image:
1903                 self._drag_image.EndDrag()
1904                 del self._drag_image
1905                 self._drag_image = None
1906                 self.GetParent().Refresh()
1907
1908             evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_END_DRAG, self.GetId())
1909             evt.SetSelection(self.GetIdxFromWindow(self._click_tab))
1910             evt.SetOldSelection(evt.GetSelection())
1911             evt.SetEventObject(self)
1912             self.GetEventHandler().ProcessEvent(evt)
1913
1914             return
1915
1916         self.GetParent()._mgr.HideHint()
1917
1918         if self.HasCapture():
1919             self.ReleaseMouse()
1920
1921         if self._hover_button:
1922             self._pressed_button = self._hover_button
1923
1924         if self._pressed_button:
1925
1926             # make sure we're still clicking the button
1927             button = self.ButtonHitTest(event.GetX(), event.GetY())
1928
1929             if button is None:
1930                 return
1931
1932             if button != self._pressed_button:
1933                 self._pressed_button = None
1934                 return
1935
1936             self.Refresh()
1937             self.Update()
1938
1939             if self._pressed_button.cur_state & AUI_BUTTON_STATE_DISABLED == 0:
1940
1941                 evt = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BUTTON, self.GetId())
1942                 evt.SetSelection(self.GetIdxFromWindow(self._click_tab))
1943                 evt.SetInt(self._pressed_button.id)
1944                 evt.SetEventObject(self)
1945                 eventHandler = self.GetEventHandler()
1946
1947                 if eventHandler is not None:
1948                     eventHandler.ProcessEvent(evt)
1949
1950             self._pressed_button = None
1951
1952         self._click_pt = wx.Point(-1, -1)
1953         self._is_dragging = False
1954         self._click_tab = None
1955
1956
1957     def OnMiddleUp(self, event):
1958         """
1959         Handles the ``wx.EVT_MIDDLE_UP`` event for L{AuiTabCtrl}.
1960
1961         :param `event`: a `wx.MouseEvent` event to be processed.
1962         """
1963
1964         eventHandler = self.GetEventHandler()
1965         if not isinstance(eventHandler, AuiTabCtrl):
1966             event.Skip()
1967             return
1968
1969         x, y = event.GetX(), event.GetY()
1970         wnd = self.TabHitTest(x, y)
1971
1972         if wnd:
1973             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_UP, self.GetId())
1974             e.SetEventObject(self)
1975             e.SetSelection(self.GetIdxFromWindow(wnd))
1976             self.GetEventHandler().ProcessEvent(e)
1977         elif not self.ButtonHitTest(x, y):
1978             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_UP, self.GetId())
1979             e.SetEventObject(self)
1980             self.GetEventHandler().ProcessEvent(e)
1981
1982
1983     def OnMiddleDown(self, event):
1984         """
1985         Handles the ``wx.EVT_MIDDLE_DOWN`` event for L{AuiTabCtrl}.
1986
1987         :param `event`: a `wx.MouseEvent` event to be processed.
1988         """
1989
1990         eventHandler = self.GetEventHandler()
1991         if not isinstance(eventHandler, AuiTabCtrl):
1992             event.Skip()
1993             return
1994         
1995         x, y = event.GetX(), event.GetY()
1996         wnd = self.TabHitTest(x, y)
1997
1998         if wnd:
1999             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_MIDDLE_DOWN, self.GetId())
2000             e.SetEventObject(self)
2001             e.SetSelection(self.GetIdxFromWindow(wnd))
2002             self.GetEventHandler().ProcessEvent(e)
2003         elif not self.ButtonHitTest(x, y):
2004             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_MIDDLE_DOWN, self.GetId())
2005             e.SetEventObject(self)
2006             self.GetEventHandler().ProcessEvent(e)
2007
2008
2009     def OnRightUp(self, event):
2010         """
2011         Handles the ``wx.EVT_RIGHT_UP`` event for L{AuiTabCtrl}.
2012
2013         :param `event`: a `wx.MouseEvent` event to be processed.
2014         """
2015
2016         x, y = event.GetX(), event.GetY()
2017         wnd = self.TabHitTest(x, y)
2018
2019         if wnd:
2020             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP, self.GetId())
2021             e.SetEventObject(self)
2022             e.SetSelection(self.GetIdxFromWindow(wnd))
2023             self.GetEventHandler().ProcessEvent(e)
2024         elif not self.ButtonHitTest(x, y):
2025             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_UP, self.GetId())
2026             e.SetEventObject(self)
2027             self.GetEventHandler().ProcessEvent(e)
2028
2029
2030     def OnRightDown(self, event):
2031         """
2032         Handles the ``wx.EVT_RIGHT_DOWN`` event for L{AuiTabCtrl}.
2033
2034         :param `event`: a `wx.MouseEvent` event to be processed.
2035         """
2036
2037         x, y = event.GetX(), event.GetY()
2038         wnd = self.TabHitTest(x, y)
2039
2040         if wnd:
2041             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_DOWN, self.GetId())
2042             e.SetEventObject(self)
2043             e.SetSelection(self.GetIdxFromWindow(wnd))
2044             self.GetEventHandler().ProcessEvent(e)
2045         elif not self.ButtonHitTest(x, y):
2046             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_RIGHT_DOWN, self.GetId())
2047             e.SetEventObject(self)
2048             self.GetEventHandler().ProcessEvent(e)
2049
2050
2051     def OnLeftDClick(self, event):
2052         """
2053         Handles the ``wx.EVT_LEFT_DCLICK`` event for L{AuiTabCtrl}.
2054
2055         :param `event`: a `wx.MouseEvent` event to be processed.
2056         """
2057
2058         x, y = event.GetX(), event.GetY()
2059         wnd = self.TabHitTest(x, y)
2060
2061         if wnd:
2062             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_TAB_DCLICK, self.GetId())
2063             e.SetEventObject(self)
2064             e.SetSelection(self.GetIdxFromWindow(wnd))
2065             self.GetEventHandler().ProcessEvent(e)
2066         elif not self.ButtonHitTest(x, y):
2067             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK, self.GetId())
2068             e.SetEventObject(self)
2069             self.GetEventHandler().ProcessEvent(e)
2070
2071
2072     def OnMotion(self, event):
2073         """
2074         Handles the ``wx.EVT_MOTION`` event for L{AuiTabCtrl}.
2075
2076         :param `event`: a `wx.MouseEvent` event to be processed.
2077         """
2078
2079         pos = event.GetPosition()
2080
2081         # check if the mouse is hovering above a button
2082
2083         button = self.ButtonHitTest(pos.x, pos.y)
2084         wnd = self.TabHitTest(pos.x, pos.y)
2085
2086         if wnd is not None:
2087             mouse_tab = self.GetIdxFromWindow(wnd)
2088             if not self._pages[mouse_tab].enabled:
2089                 self._hover_button = None
2090                 return
2091
2092         if self._on_button:
2093             return
2094
2095         if button:
2096
2097             if self._hover_button and button != self._hover_button:
2098                 self._hover_button.cur_state = AUI_BUTTON_STATE_NORMAL
2099                 self._hover_button = None
2100                 self.Refresh()
2101                 self.Update()
2102
2103             if button.cur_state != AUI_BUTTON_STATE_HOVER:
2104                 button.cur_state = AUI_BUTTON_STATE_HOVER
2105                 self.Refresh()
2106                 self.Update()
2107                 self._hover_button = button
2108                 return
2109
2110         else:
2111
2112             if self._hover_button:
2113                 self._hover_button.cur_state = AUI_BUTTON_STATE_NORMAL
2114                 self._hover_button = None
2115                 self.Refresh()
2116                 self.Update()
2117
2118         if not event.LeftIsDown() or self._click_pt == wx.Point(-1, -1):
2119             return
2120
2121         if not self.HasCapture():
2122             return
2123
2124         wnd = self.TabHitTest(pos.x, pos.y)
2125
2126         if not self._is_dragging:
2127
2128             drag_x_threshold = wx.SystemSettings.GetMetric(wx.SYS_DRAG_X)
2129             drag_y_threshold = wx.SystemSettings.GetMetric(wx.SYS_DRAG_Y)
2130
2131             if abs(pos.x - self._click_pt.x) > drag_x_threshold or \
2132                abs(pos.y - self._click_pt.y) > drag_y_threshold:
2133
2134                 self._is_dragging = True
2135
2136                 if self._drag_image:
2137                     self._drag_image.EndDrag()
2138                     del self._drag_image
2139                     self._drag_image = None
2140
2141                 if self._agwFlags & AUI_NB_DRAW_DND_TAB:
2142                     # Create the custom draw image from the icons and the text of the item
2143                     mouse_tab = self.GetIdxFromWindow(wnd)
2144                     page = self._pages[mouse_tab]
2145                     tab_button = self._tab_close_buttons[mouse_tab]
2146                     self._drag_image = TabDragImage(self, page, tab_button.cur_state, self._art)
2147
2148                     if self._agwFlags & AUI_NB_TAB_FLOAT:
2149                         self._drag_image.BeginDrag(wx.Point(0,0), self, fullScreen=True)
2150                     else:
2151                         self._drag_image.BeginDragBounded(wx.Point(0,0), self, self.GetParent())
2152
2153                     # Capture the mouse cursor position offset relative to
2154                     # The tab image location
2155                     self._drag_img_offset = (pos[0] - page.rect.x,
2156                                              pos[1] - page.rect.y)
2157
2158                     self._drag_image.Show()
2159
2160         if not wnd:
2161             evt2 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_BEGIN_DRAG, self.GetId())
2162             evt2.SetSelection(self.GetIdxFromWindow(self._click_tab))
2163             evt2.SetOldSelection(evt2.GetSelection())
2164             evt2.SetEventObject(self)
2165             self.GetEventHandler().ProcessEvent(evt2)
2166             if evt2.GetDispatched():
2167                 return
2168
2169         evt3 = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_DRAG_MOTION, self.GetId())
2170         evt3.SetSelection(self.GetIdxFromWindow(self._click_tab))
2171         evt3.SetOldSelection(evt3.GetSelection())
2172         evt3.SetEventObject(self)
2173         self.GetEventHandler().ProcessEvent(evt3)
2174
2175         if self._drag_image:
2176             # Apply the drag images offset
2177             pos -= self._drag_img_offset
2178             self._drag_image.Move(pos)
2179
2180
2181     def OnLeaveWindow(self, event):
2182         """
2183         Handles the ``wx.EVT_LEAVE_WINDOW`` event for L{AuiTabCtrl}.
2184
2185         :param `event`: a `wx.MouseEvent` event to be processed.
2186         """
2187
2188         if self._hover_button:
2189             self._hover_button.cur_state = AUI_BUTTON_STATE_NORMAL
2190             self._hover_button = None
2191             self.Refresh()
2192             self.Update()
2193
2194
2195     def OnButton(self, event):
2196         """
2197         Handles the ``EVT_AUINOTEBOOK_BUTTON`` event for L{AuiTabCtrl}.
2198
2199         :param `event`: a L{AuiNotebookEvent} event to be processed.
2200         """
2201
2202         button = event.GetInt()
2203
2204         if button == AUI_BUTTON_LEFT or button == AUI_BUTTON_RIGHT:
2205             if button == AUI_BUTTON_LEFT:
2206                 if self.GetTabOffset() > 0:
2207
2208                     self.SetTabOffset(self.GetTabOffset()-1)
2209                     self.Refresh()
2210                     self.Update()
2211             else:
2212                 self.SetTabOffset(self.GetTabOffset()+1)
2213                 self.Refresh()
2214                 self.Update()
2215
2216         elif button == AUI_BUTTON_WINDOWLIST:
2217             idx = self.GetArtProvider().ShowDropDown(self, self._pages, self.GetActivePage())
2218
2219             if idx != -1:
2220
2221                 e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, self.GetId())
2222                 e.SetSelection(idx)
2223                 e.SetOldSelection(self.GetActivePage())
2224                 e.SetEventObject(self)
2225                 self.GetEventHandler().ProcessEvent(e)
2226
2227         else:
2228             event.Skip()
2229
2230
2231     def OnSetFocus(self, event):
2232         """
2233         Handles the ``wx.EVT_SET_FOCUS`` event for L{AuiTabCtrl}.
2234
2235         :param `event`: a `wx.FocusEvent` event to be processed.
2236         """
2237
2238         self.Refresh()
2239
2240
2241     def OnKillFocus(self, event):
2242         """
2243         Handles the ``wx.EVT_KILL_FOCUS`` event for L{AuiTabCtrl}.
2244
2245         :param `event`: a `wx.FocusEvent` event to be processed.
2246         """
2247
2248         self.Refresh()
2249
2250
2251     def OnKeyDown(self, event):
2252         """
2253         Handles the ``wx.EVT_KEY_DOWN`` event for L{AuiTabCtrl}.
2254
2255         :param `event`: a `wx.KeyEvent` event to be processed.
2256         """
2257
2258         key = event.GetKeyCode()
2259         nb = self.GetParent()
2260
2261         if key == wx.WXK_LEFT:
2262             nb.AdvanceSelection(False)
2263             self.SetFocus()
2264
2265         elif key == wx.WXK_RIGHT:
2266             nb.AdvanceSelection(True)
2267             self.SetFocus()
2268
2269         elif key == wx.WXK_HOME:
2270             newPage = 0
2271             nb.SetSelection(newPage)
2272             self.SetFocus()
2273
2274         elif key == wx.WXK_END:
2275             newPage = nb.GetPageCount() - 1
2276             nb.SetSelection(newPage)
2277             self.SetFocus()
2278
2279         elif key == wx.WXK_TAB:
2280             if not event.ControlDown():
2281                 flags = 0
2282                 if not event.ShiftDown(): flags |= wx.NavigationKeyEvent.IsForward
2283                 if event.CmdDown():       flags |= wx.NavigationKeyEvent.WinChange
2284                 self.Navigate(flags)
2285             else:
2286
2287                 if not nb or not isinstance(nb, AuiNotebook):
2288                     event.Skip()
2289                     return
2290
2291                 bForward = bWindowChange = 0
2292                 if not event.ShiftDown(): bForward |= wx.NavigationKeyEvent.IsForward
2293                 if event.CmdDown():       bWindowChange |= wx.NavigationKeyEvent.WinChange
2294
2295                 keyEvent = wx.NavigationKeyEvent()
2296                 keyEvent.SetDirection(bForward)
2297                 keyEvent.SetWindowChange(bWindowChange)
2298                 keyEvent.SetFromTab(True)
2299                 keyEvent.SetEventObject(nb)
2300
2301                 if not nb.GetEventHandler().ProcessEvent(keyEvent):
2302
2303                     # Not processed? Do an explicit tab into the page.
2304                     win = self.GetWindowFromIdx(self.GetActivePage())
2305                     if win:
2306                         win.SetFocus()
2307
2308                 self.SetFocus()
2309
2310                 return
2311
2312         else:
2313             event.Skip()
2314
2315
2316     def OnKeyDown2(self, event):
2317         """
2318         Deprecated.
2319
2320         Handles the ``wx.EVT_KEY_DOWN`` event for L{AuiTabCtrl}.
2321
2322         :param `event`: a `wx.KeyEvent` event to be processed.
2323
2324         :warning: This method implementation is now deprecated. Refer to L{OnKeyDown}
2325          for the correct one.
2326         """
2327
2328         if self.GetActivePage() == -1:
2329             event.Skip()
2330             return
2331
2332         # We can't leave tab processing to the system on Windows, tabs and keys
2333         # get eaten by the system and not processed properly if we specify both
2334         # wxTAB_TRAVERSAL and wxWANTS_CHARS. And if we specify just wxTAB_TRAVERSAL,
2335         # we don't key arrow key events.
2336
2337         key = event.GetKeyCode()
2338
2339         if key == wx.WXK_NUMPAD_PAGEUP:
2340             key = wx.WXK_PAGEUP
2341         if key == wx.WXK_NUMPAD_PAGEDOWN:
2342             key = wx.WXK_PAGEDOWN
2343         if key == wx.WXK_NUMPAD_HOME:
2344             key = wx.WXK_HOME
2345         if key == wx.WXK_NUMPAD_END:
2346             key = wx.WXK_END
2347         if key == wx.WXK_NUMPAD_LEFT:
2348             key = wx.WXK_LEFT
2349         if key == wx.WXK_NUMPAD_RIGHT:
2350             key = wx.WXK_RIGHT
2351
2352         if key == wx.WXK_TAB or key == wx.WXK_PAGEUP or key == wx.WXK_PAGEDOWN:
2353
2354             bCtrlDown = event.ControlDown()
2355             bShiftDown = event.ShiftDown()
2356
2357             bForward = (key == wx.WXK_TAB and not bShiftDown) or (key == wx.WXK_PAGEDOWN)
2358             bWindowChange = (key == wx.WXK_PAGEUP) or (key == wx.WXK_PAGEDOWN) or bCtrlDown
2359             bFromTab = (key == wx.WXK_TAB)
2360
2361             nb = self.GetParent()
2362             if not nb or not isinstance(nb, AuiNotebook):
2363                 event.Skip()
2364                 return
2365
2366             keyEvent = wx.NavigationKeyEvent()
2367             keyEvent.SetDirection(bForward)
2368             keyEvent.SetWindowChange(bWindowChange)
2369             keyEvent.SetFromTab(bFromTab)
2370             keyEvent.SetEventObject(nb)
2371
2372             if not nb.GetEventHandler().ProcessEvent(keyEvent):
2373
2374                 # Not processed? Do an explicit tab into the page.
2375                 win = self.GetWindowFromIdx(self.GetActivePage())
2376                 if win:
2377                     win.SetFocus()
2378
2379             return
2380
2381         if len(self._pages) < 2:
2382             event.Skip()
2383             return
2384
2385         newPage = -1
2386
2387         if self.GetLayoutDirection() == wx.Layout_RightToLeft:
2388             forwardKey = wx.WXK_LEFT
2389             backwardKey = wx.WXK_RIGHT
2390         else:
2391             forwardKey = wx.WXK_RIGHT
2392             backwardKey = wx.WXK_LEFT
2393
2394         if key == forwardKey:
2395             if self.GetActivePage() == -1:
2396                 newPage = 0
2397             elif self.GetActivePage() < len(self._pages) - 1:
2398                 newPage = self.GetActivePage() + 1
2399
2400         elif key == backwardKey:
2401             if self.GetActivePage() == -1:
2402                 newPage = len(self._pages) - 1
2403             elif self.GetActivePage() > 0:
2404                 newPage = self.GetActivePage() - 1
2405
2406         elif key == wx.WXK_HOME:
2407             newPage = 0
2408
2409         elif key == wx.WXK_END:
2410             newPage = len(self._pages) - 1
2411
2412         else:
2413             event.Skip()
2414
2415         if newPage != -1:
2416             e = AuiNotebookEvent(wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGING, self.GetId())
2417             e.SetSelection(newPage)
2418             e.SetOldSelection(newPage)
2419             e.SetEventObject(self)
2420             self.GetEventHandler().ProcessEvent(e)
2421
2422         else:
2423             event.Skip()
2424
2425
2426 # ----------------------------------------------------------------------
2427
2428 class TabFrame(wx.PyWindow):
2429     """
2430     TabFrame is an interesting case. It's important that all child pages
2431     of the multi-notebook control are all actually children of that control
2432     (and not grandchildren). TabFrame facilitates this. There is one
2433     instance of TabFrame for each tab control inside the multi-notebook.
2434
2435     It's important to know that TabFrame is not a real window, but it merely
2436     used to capture the dimensions/positioning of the internal tab control and
2437     it's managed page windows.
2438     """
2439
2440     def __init__(self, parent):
2441         """
2442         Default class constructor.
2443         Used internally, do not call it in your code!
2444         """
2445
2446         pre = wx.PrePyWindow()
2447
2448         self._tabs = None
2449         self._rect = wx.Rect(0, 0, 200, 200)
2450         self._tab_ctrl_height = 20
2451         self._tab_rect = wx.Rect()
2452         self._parent = parent
2453
2454         self.PostCreate(pre)
2455
2456
2457     def SetTabCtrlHeight(self, h):
2458         """
2459         Sets the tab control height.
2460
2461         :param `h`: the tab area height.
2462         """
2463
2464         self._tab_ctrl_height = h
2465
2466
2467     def DoSetSize(self, x, y, width, height, flags=wx.SIZE_AUTO):
2468         """
2469         Sets the position and size of the window in pixels. The `flags`
2470         parameter indicates the interpretation of the other params if they are
2471         equal to -1.
2472
2473         :param `x`: the window `x` position;
2474         :param `y`: the window `y` position;
2475         :param `width`: the window width;
2476         :param `height`: the window height;
2477         :param `flags`: may have one of this bit set:
2478
2479          ===================================  ======================================
2480          Size Flags                           Description
2481          ===================================  ======================================
2482          ``wx.SIZE_AUTO``                     A -1 indicates that a class-specific default should be used.
2483          ``wx.SIZE_AUTO_WIDTH``               A -1 indicates that a class-specific default should be used for the width.
2484          ``wx.SIZE_AUTO_HEIGHT``              A -1 indicates that a class-specific default should be used for the height.
2485          ``wx.SIZE_USE_EXISTING``             Existing dimensions should be used if -1 values are supplied.
2486          ``wx.SIZE_ALLOW_MINUS_ONE``          Allow dimensions of -1 and less to be interpreted as real dimensions, not default values.
2487          ``wx.SIZE_FORCE``                    Normally, if the position and the size of the window are already the same as the parameters of this function, nothing is done. but with this flag a window resize may be forced even in this case (supported in wx 2.6.2 and later and only implemented for MSW and ignored elsewhere currently)
2488          ===================================  ======================================
2489
2490         :note: Overridden from `wx.PyControl`.
2491         """
2492
2493         self._rect = wx.Rect(x, y, max(1, width), max(1, height))
2494         self.DoSizing()
2495
2496
2497     def DoGetSize(self):
2498         """
2499         Returns the window size.
2500
2501         :note: Overridden from `wx.PyControl`.
2502         """
2503
2504         return self._rect.width, self._rect.height
2505
2506
2507     def DoGetClientSize(self):
2508         """
2509         Returns the window client size.
2510
2511         :note: Overridden from `wx.PyControl`.
2512         """
2513
2514         return self._rect.width, self._rect.height
2515
2516
2517     def Show(self, show=True):
2518         """
2519         Shows/hides the window.
2520
2521         :param `show`: ``True`` to show the window, ``False`` otherwise.
2522
2523         :note: Overridden from `wx.PyControl`, this method always returns ``False`` as
2524          L{TabFrame} should never be phisically shown on screen.
2525         """
2526
2527         return False
2528
2529
2530     def DoSizing(self):
2531         """ Does the actual sizing of the tab control. """
2532
2533         if not self._tabs:
2534             return
2535
2536         hideOnSingle = ((self._tabs.GetAGWFlags() & AUI_NB_HIDE_ON_SINGLE_TAB) and \
2537                         self._tabs.GetPageCount() <= 1)
2538
2539         if not hideOnSingle and not self._parent._hide_tabs:
2540             tab_height = self._tab_ctrl_height
2541
2542             self._tab_rect = wx.Rect(self._rect.x, self._rect.y, self._rect.width, self._tab_ctrl_height)
2543
2544             if self._tabs.GetAGWFlags() & AUI_NB_BOTTOM:
2545                 self._tab_rect = wx.Rect(self._rect.x, self._rect.y + self._rect.height - tab_height,
2546                                          self._rect.width, tab_height)
2547                 self._tabs.SetDimensions(self._rect.x, self._rect.y + self._rect.height - tab_height,
2548                                          self._rect.width, tab_height)
2549                 self._tabs.SetTabRect(wx.Rect(0, 0, self._rect.width, tab_height))
2550
2551             else:
2552
2553                 self._tab_rect = wx.Rect(self._rect.x, self._rect.y, self._rect.width, tab_height)
2554                 self._tabs.SetDimensions(self._rect.x, self._rect.y, self._rect.width, tab_height)
2555                 self._tabs.SetTabRect(wx.Rect(0, 0, self._rect.width, tab_height))
2556
2557             # TODO: elif (GetAGWFlags() & AUI_NB_LEFT)
2558             # TODO: elif (GetAGWFlags() & AUI_NB_RIGHT)
2559
2560             self._tabs.Refresh()
2561             self._tabs.Update()
2562
2563         else:
2564
2565             tab_height = 0
2566             self._tabs.SetDimensions(self._rect.x, self._rect.y, self._rect.width, tab_height)
2567             self._tabs.SetTabRect(wx.Rect(0, 0, self._rect.width, tab_height))
2568
2569         pages = self._tabs.GetPages()
2570
2571         for page in pages:
2572
2573             height = self._rect.height - tab_height
2574
2575             if height < 0:
2576                 # avoid passing negative height to wx.Window.SetSize(), this
2577                 # results in assert failures/GTK+ warnings
2578                 height = 0
2579
2580             if self._tabs.GetAGWFlags() & AUI_NB_BOTTOM:
2581                 page.window.SetDimensions(self._rect.x, self._rect.y, self._rect.width, height)
2582
2583             else:
2584                 page.window.SetDimensions(self._rect.x, self._rect.y + tab_height,
2585                                           self._rect.width, height)
2586
2587             # TODO: elif (GetAGWFlags() & AUI_NB_LEFT)
2588             # TODO: elif (GetAGWFlags() & AUI_NB_RIGHT)
2589
2590             if repr(page.window.__class__).find("AuiMDIChildFrame") >= 0:
2591                 page.window.ApplyMDIChildFrameRect()
2592
2593
2594     def Update(self):
2595         """
2596         Calling this method immediately repaints the invalidated area of the window
2597         and all of its children recursively while this would usually only happen when
2598         the flow of control returns to the event loop.
2599
2600         :note: Notice that this function doesn't invalidate any area of the window so
2601          nothing happens if nothing has been invalidated (i.e. marked as requiring a redraw).
2602          Use `Refresh` first if you want to immediately redraw the window unconditionally.
2603
2604         :note: Overridden from `wx.PyControl`.
2605         """
2606
2607         # does nothing
2608         pass
2609
2610
2611 # ----------------------------------------------------------------------
2612 # -- AuiNotebook class implementation --
2613
2614 class AuiNotebook(wx.PyPanel):
2615     """
2616     AuiNotebook is a notebook control which implements many features common in
2617     applications with dockable panes. Specifically, AuiNotebook implements functionality
2618     which allows the user to rearrange tab order via drag-and-drop, split the tab window
2619     into many different splitter configurations, and toggle through different themes to
2620     customize the control's look and feel.
2621
2622     An effort has been made to try to maintain an API as similar to that of `wx.Notebook`.
2623
2624     The default theme that is used is L{AuiDefaultTabArt}, which provides a modern, glossy
2625     look and feel. The theme can be changed by calling L{AuiNotebook.SetArtProvider}.
2626     """
2627
2628     def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
2629                  style=0, agwStyle=AUI_NB_DEFAULT_STYLE):
2630         """
2631         Default class constructor.
2632
2633         :param `parent`: the L{AuiNotebook} parent;
2634         :param `id`: an identifier for the control: a value of -1 is taken to mean a default;
2635         :param `pos`: the control position. A value of (-1, -1) indicates a default position,
2636          chosen by either the windowing system or wxPython, depending on platform;
2637         :param `size`: the control size. A value of (-1, -1) indicates a default size,
2638          chosen by either the windowing system or wxPython, depending on platform;
2639         :param `style`: the underlying `wx.PyPanel` window style;
2640         :param `agwStyle`: the AGW-specific window style. This can be a combination of the following bits:
2641
2642          ==================================== ==================================
2643          Flag name                            Description
2644          ==================================== ==================================
2645          ``AUI_NB_TOP``                       With this style, tabs are drawn along the top of the notebook
2646          ``AUI_NB_LEFT``                      With this style, tabs are drawn along the left of the notebook. Not implemented yet.
2647          ``AUI_NB_RIGHT``                     With this style, tabs are drawn along the right of the notebook. Not implemented yet.
2648          ``AUI_NB_BOTTOM``                    With this style, tabs are drawn along the bottom of the notebook
2649          ``AUI_NB_TAB_SPLIT``                 Allows the tab control to be split by dragging a tab
2650          ``AUI_NB_TAB_MOVE``                  Allows a tab to be moved horizontally by dragging
2651          ``AUI_NB_TAB_EXTERNAL_MOVE``         Allows a tab to be moved to another tab control
2652          ``AUI_NB_TAB_FIXED_WIDTH``           With this style, all tabs have the same width
2653          ``AUI_NB_SCROLL_BUTTONS``            With this style, left and right scroll buttons are displayed
2654          ``AUI_NB_WINDOWLIST_BUTTON``         With this style, a drop-down list of windows is available
2655          ``AUI_NB_CLOSE_BUTTON``              With this style, a close button is available on the tab bar
2656          ``AUI_NB_CLOSE_ON_ACTIVE_TAB``       With this style, a close button is available on the active tab
2657          ``AUI_NB_CLOSE_ON_ALL_TABS``         With this style, a close button is available on all tabs
2658          ``AUI_NB_MIDDLE_CLICK_CLOSE``        Allows to close L{AuiNotebook} tabs by mouse middle button click
2659          ``AUI_NB_SUB_NOTEBOOK``              This style is used by L{AuiManager} to create automatic AuiNotebooks
2660          ``AUI_NB_HIDE_ON_SINGLE_TAB``        Hides the tab window if only one tab is present
2661          ``AUI_NB_SMART_TABS``                Use Smart Tabbing, like ``Alt`` + ``Tab`` on Windows
2662          ``AUI_NB_USE_IMAGES_DROPDOWN``       Uses images on dropdown window list menu instead of check items
2663          ``AUI_NB_CLOSE_ON_TAB_LEFT``         Draws the tab close button on the left instead of on the right (a la Camino browser)
2664          ``AUI_NB_TAB_FLOAT``                 Allows the floating of single tabs. Known limitation: when the notebook is more or less full screen, tabs cannot be dragged far enough outside of the notebook to become floating pages
2665          ``AUI_NB_DRAW_DND_TAB``              Draws an image representation of a tab while dragging (on by default)
2666          ``AUI_NB_ORDER_BY_ACCESS``           Tab navigation order by last access time for the tabs
2667          ``AUI_NB_NO_TAB_FOCUS``              Don't draw tab focus rectangle
2668          ==================================== ==================================
2669
2670          Default value for `agwStyle` is:
2671          ``AUI_NB_DEFAULT_STYLE`` = ``AUI_NB_TOP`` | ``AUI_NB_TAB_SPLIT`` | ``AUI_NB_TAB_MOVE`` | ``AUI_NB_SCROLL_BUTTONS`` | ``AUI_NB_CLOSE_ON_ACTIVE_TAB`` | ``AUI_NB_MIDDLE_CLICK_CLOSE`` | ``AUI_NB_DRAW_DND_TAB``
2672
2673         """
2674
2675         self._curpage = -1
2676         self._tab_id_counter = AuiBaseTabCtrlId
2677         self._dummy_wnd = None
2678         self._hide_tabs = False
2679         self._sash_dclick_unsplit = False
2680         self._tab_ctrl_height = 20
2681         self._requested_bmp_size = wx.Size(-1, -1)
2682         self._requested_tabctrl_height = -1
2683         self._textCtrl = None
2684         self._tabBounds = (-1, -1)
2685         self._click_tab = None
2686
2687         wx.PyPanel.__init__(self, parent, id, pos, size, style|wx.BORDER_NONE|wx.TAB_TRAVERSAL)
2688         self._mgr = framemanager.AuiManager()
2689         self._tabs = AuiTabContainer(self)
2690
2691         self.InitNotebook(agwStyle)
2692
2693
2694     def GetTabContainer(self):
2695         """ Returns the instance of L{AuiTabContainer}. """
2696
2697         return self._tabs
2698
2699
2700     def InitNotebook(self, agwStyle):
2701         """
2702         Contains common initialization code called by all constructors.
2703
2704         :param `agwStyle`: the notebook style.
2705
2706         :see: L{__init__}
2707         """
2708
2709         self.SetName("AuiNotebook")
2710         self._agwFlags = agwStyle
2711
2712         self._popupWin = None
2713         self._naviIcon = None
2714         self._imageList = None
2715         self._last_drag_x = 0
2716
2717         self._normal_font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
2718         self._selected_font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
2719         self._selected_font.SetWeight(wx.BOLD)
2720
2721         self.SetArtProvider(TA.AuiDefaultTabArt())
2722
2723         self._dummy_wnd = wx.Window(self, wx.ID_ANY, wx.Point(0, 0), wx.Size(0, 0))
2724         self._dummy_wnd.SetSize((200, 200))
2725         self._dummy_wnd.Show(False)
2726
2727         self._mgr.SetManagedWindow(self)
2728         self._mgr.SetAGWFlags(AUI_MGR_DEFAULT)
2729         self._mgr.SetDockSizeConstraint(1.0, 1.0) # no dock size constraint
2730
2731         self._mgr.AddPane(self._dummy_wnd, framemanager.AuiPaneInfo().Name("dummy").Bottom().CaptionVisible(False).Show(False))
2732         self._mgr.Update()
2733
2734         self.Bind(wx.EVT_SIZE, self.OnSize)
2735         self.Bind(wx.EVT_CHILD_FOCUS, self.OnChildFocusNotebook)
2736         self.Bind(EVT_AUINOTEBOOK_PAGE_CHANGING, self.OnTabClicked,
2737                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2738         self.Bind(EVT_AUINOTEBOOK_BEGIN_DRAG, self.OnTabBeginDrag,
2739                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2740         self.Bind(EVT_AUINOTEBOOK_END_DRAG, self.OnTabEndDrag,
2741                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2742         self.Bind(EVT_AUINOTEBOOK_DRAG_MOTION, self.OnTabDragMotion,
2743                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2744         self.Bind(EVT_AUINOTEBOOK_CANCEL_DRAG, self.OnTabCancelDrag,
2745                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2746         self.Bind(EVT_AUINOTEBOOK_BUTTON, self.OnTabButton,
2747                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2748         self.Bind(EVT_AUINOTEBOOK_TAB_MIDDLE_DOWN, self.OnTabMiddleDown,
2749                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2750         self.Bind(EVT_AUINOTEBOOK_TAB_MIDDLE_UP, self.OnTabMiddleUp,
2751                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2752         self.Bind(EVT_AUINOTEBOOK_TAB_RIGHT_DOWN, self.OnTabRightDown,
2753                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2754         self.Bind(EVT_AUINOTEBOOK_TAB_RIGHT_UP, self.OnTabRightUp,
2755                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2756         self.Bind(EVT_AUINOTEBOOK_BG_DCLICK, self.OnTabBgDClick,
2757                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2758         self.Bind(EVT_AUINOTEBOOK_TAB_DCLICK, self.OnTabDClick,
2759                   id=AuiBaseTabCtrlId, id2=AuiBaseTabCtrlId+500)
2760
2761         self.Bind(wx.EVT_NAVIGATION_KEY, self.OnNavigationKeyNotebook)
2762
2763
2764     def SetArtProvider(self, art):
2765         """
2766         Sets the art provider to be used by the notebook.
2767
2768         :param `art`: an art provider.
2769         """
2770
2771         self._tabs.SetArtProvider(art)
2772         self.UpdateTabCtrlHeight(force=True)
2773
2774
2775     def SavePerspective(self):
2776         """
2777         Saves the entire user interface layout into an encoded string, which can then
2778         be stored by the application (probably using `wx.Config`). When a perspective
2779         is restored using L{LoadPerspective}, the entire user interface will return
2780         to the state it was when the perspective was saved.
2781         """
2782
2783         # Build list of panes/tabs
2784         tabs = ""
2785         all_panes = self._mgr.GetAllPanes()
2786
2787         for pane in all_panes:
2788
2789             if pane.name == "dummy":
2790                 continue
2791
2792             tabframe = pane.window
2793
2794             if tabs:
2795                 tabs += "|"
2796
2797             tabs += pane.name + "="
2798
2799             # add tab id's
2800             page_count = tabframe._tabs.GetPageCount()
2801
2802             for p in xrange(page_count):
2803
2804                 page = tabframe._tabs.GetPage(p)
2805                 page_idx = self._tabs.GetIdxFromWindow(page.window)
2806
2807                 if p:
2808                     tabs += ","
2809
2810                 if p == tabframe._tabs.GetActivePage():
2811                     tabs += "+"
2812                 elif page_idx == self._curpage:
2813                     tabs += "*"
2814
2815                 tabs += "%u"%page_idx
2816
2817         tabs += "@"
2818
2819         # Add frame perspective
2820         tabs += self._mgr.SavePerspective()
2821
2822         return tabs
2823
2824
2825     def LoadPerspective(self, layout):
2826         """
2827         Loads a layout which was saved with L{SavePerspective}.
2828
2829         :param `layout`: a string which contains a saved L{AuiNotebook} layout.
2830         """
2831
2832         # Remove all tab ctrls (but still keep them in main index)
2833         tab_count = self._tabs.GetPageCount()
2834         for i in xrange(tab_count):
2835             wnd = self._tabs.GetWindowFromIdx(i)
2836
2837             # find out which onscreen tab ctrl owns this tab
2838             ctrl, ctrl_idx = self.FindTab(wnd)
2839             if not ctrl:
2840                 return False
2841
2842             # remove the tab from ctrl
2843             if not ctrl.RemovePage(wnd):
2844                 return False
2845
2846         self.RemoveEmptyTabFrames()
2847
2848         sel_page = 0
2849         tabs = layout[0:layout.index("@")]
2850         to_break1 = False
2851
2852         while 1:
2853
2854             if "|" not in tabs:
2855                 to_break1 = True
2856                 tab_part = tabs
2857             else:
2858                 tab_part = tabs[0:tabs.index('|')]
2859
2860             if "=" not in tab_part:
2861                 # No pages in this perspective...
2862                 return False
2863
2864             # Get pane name
2865             pane_name = tab_part[0:tab_part.index("=")]
2866
2867             # create a new tab frame
2868             new_tabs = TabFrame(self)
2869             self._tab_id_counter += 1
2870             new_tabs._tabs = AuiTabCtrl(self, self._tab_id_counter)
2871             new_tabs._tabs.SetArtProvider(self._tabs.GetArtProvider().Clone())
2872             new_tabs.SetTabCtrlHeight(self._tab_ctrl_height)
2873             new_tabs._tabs.SetAGWFlags(self._agwFlags)
2874             dest_tabs = new_tabs._tabs
2875
2876             # create a pane info structure with the information
2877             # about where the pane should be added
2878             pane_info = framemanager.AuiPaneInfo().Name(pane_name).Bottom().CaptionVisible(False)
2879             self._mgr.AddPane(new_tabs, pane_info)
2880
2881             # Get list of tab id's and move them to pane
2882             tab_list = tab_part[tab_part.index("=")+1:]
2883             to_break2, active_found = False, False
2884
2885             while 1:
2886                 if "," not in tab_list:
2887                     to_break2 = True
2888                     tab = tab_list
2889                 else:
2890                     tab = tab_list[0:tab_list.index(",")]
2891                     tab_list = tab_list[tab_list.index(",")+1:]
2892
2893                 # Check if this page has an 'active' marker
2894                 c = tab[0]
2895                 if c in ['+', '*']:
2896                     tab = tab[1:]
2897
2898                 tab_idx = int(tab)
2899                 if tab_idx >= self.GetPageCount():
2900                     to_break1 = True
2901                     break
2902
2903                 # Move tab to pane
2904                 page = self._tabs.GetPage(tab_idx)
2905                 newpage_idx = dest_tabs.GetPageCount()
2906                 dest_tabs.InsertPage(page.window, page, newpage_idx)
2907
2908                 if c == '+':
2909                     dest_tabs.SetActivePage(newpage_idx)
2910                     active_found = True
2911                 elif c == '*':
2912                     sel_page = tab_idx
2913
2914                 if to_break2:
2915                     break
2916
2917             if not active_found:
2918                 dest_tabs.SetActivePage(0)
2919
2920             new_tabs.DoSizing()
2921             dest_tabs.DoShowHide()
2922             dest_tabs.Refresh()
2923
2924             if to_break1:
2925                 break
2926
2927             tabs = tabs[tabs.index('|')+1:]
2928
2929         # Load the frame perspective
2930         frames = layout[layout.index('@')+1:]
2931         self._mgr.LoadPerspective(frames)
2932
2933         # Force refresh of selection
2934         self._curpage = -1
2935         self.SetSelection(sel_page)
2936
2937         return True
2938
2939
2940     def SetTabCtrlHeight(self, height):
2941         """
2942         Sets the tab height.
2943
2944         By default, the tab control height is calculated by measuring the text
2945         height and bitmap sizes on the tab captions.
2946
2947         Calling this method will override that calculation and set the tab control
2948         to the specified height parameter. A call to this method will override
2949         any call to L{SetUniformBitmapSize}. Specifying -1 as the height will
2950         return the control to its default auto-sizing behaviour.
2951
2952         :param `height`: the tab control area height.
2953         """
2954
2955         self._requested_tabctrl_height = height
2956
2957         # if window is already initialized, recalculate the tab height
2958         if self._dummy_wnd:
2959             self.UpdateTabCtrlHeight()
2960
2961
2962     def SetUniformBitmapSize(self, size):
2963         """
2964         Ensures that all tabs will have the same height, even if some tabs
2965         don't have bitmaps. Passing ``wx.DefaultSize`` to this
2966         function will instruct the control to use dynamic tab height, which is
2967         the default behaviour. Under the default behaviour, when a tab with a
2968         large bitmap is added, the tab control's height will automatically
2969         increase to accommodate the larger bitmap.
2970
2971         :param `size`: an instance of `wx.Size` specifying the tab bitmap size.
2972         """
2973
2974         self._requested_bmp_size = wx.Size(*size)
2975
2976         # if window is already initialized, recalculate the tab height
2977         if self._dummy_wnd:
2978             self.UpdateTabCtrlHeight()
2979
2980
2981     def UpdateTabCtrlHeight(self, force=False):
2982         """
2983         UpdateTabCtrlHeight() does the actual tab resizing. It's meant
2984         to be used interally.
2985
2986         :param `force`: ``True`` to force the tab art to repaint.
2987         """
2988
2989         # get the tab ctrl height we will use
2990         height = self.CalculateTabCtrlHeight()
2991
2992         # if the tab control height needs to change, update
2993         # all of our tab controls with the new height
2994         if self._tab_ctrl_height != height or force:
2995             art = self._tabs.GetArtProvider()
2996
2997             self._tab_ctrl_height = height
2998
2999             all_panes = self._mgr.GetAllPanes()
3000             for pane in all_panes:
3001
3002                 if pane.name == "dummy":
3003                     continue
3004
3005                 tab_frame = pane.window
3006                 tabctrl = tab_frame._tabs
3007                 tab_frame.SetTabCtrlHeight(self._tab_ctrl_height)
3008                 tabctrl.SetArtProvider(art.Clone())
3009                 tab_frame.DoSizing()
3010
3011
3012     def UpdateHintWindowSize(self):
3013         """ Updates the L{AuiManager} hint window size. """
3014
3015         size = self.CalculateNewSplitSize()
3016
3017         # the placeholder hint window should be set to this size
3018         info = self._mgr.GetPane("dummy")
3019
3020         if info.IsOk():
3021             info.MinSize(size)
3022             info.BestSize(size)
3023             self._dummy_wnd.SetSize(size)
3024
3025
3026     def CalculateNewSplitSize(self):
3027         """ Calculates the size of the new split. """
3028
3029         # count number of tab controls
3030         tab_ctrl_count = 0
3031         all_panes = self._mgr.GetAllPanes()
3032
3033         for pane in all_panes:
3034             if pane.name == "dummy":
3035                 continue
3036
3037             tab_ctrl_count += 1
3038
3039         # if there is only one tab control, the first split
3040         # should happen around the middle
3041         if tab_ctrl_count < 2:
3042             new_split_size = self.GetClientSize()
3043             new_split_size.x /= 2
3044             new_split_size.y /= 2
3045
3046         else:
3047
3048             # this is in place of a more complicated calculation
3049             # that needs to be implemented
3050             new_split_size = wx.Size(180, 180)
3051
3052         return new_split_size
3053
3054
3055     def CalculateTabCtrlHeight(self):
3056         """ Calculates the tab control area height. """
3057
3058         # if a fixed tab ctrl height is specified,
3059         # just return that instead of calculating a
3060         # tab height
3061         if self._requested_tabctrl_height != -1:
3062             return self._requested_tabctrl_height
3063
3064         # find out new best tab height
3065         art = self._tabs.GetArtProvider()
3066
3067         return art.GetBestTabCtrlSize(self, self._tabs.GetPages(), self._requested_bmp_size)
3068
3069
3070     def GetArtProvider(self):
3071         """ Returns the associated art provider. """
3072
3073         return self._tabs.GetArtProvider()
3074
3075
3076     def SetAGWWindowStyleFlag(self, agwStyle):
3077         """
3078         Sets the AGW-specific style of the window.
3079
3080         :param `agwStyle`: the new window style. This can be a combination of the following bits:
3081
3082          ==================================== ==================================
3083          Flag name                            Description
3084          ==================================== ==================================
3085          ``AUI_NB_TOP``                       With this style, tabs are drawn along the top of the notebook
3086          ``AUI_NB_LEFT``                      With this style, tabs are drawn along the left of the notebook. Not implemented yet.
3087          ``AUI_NB_RIGHT``                     With this style, tabs are drawn along the right of the notebook. Not implemented yet.
3088          ``AUI_NB_BOTTOM``                    With this style, tabs are drawn along the bottom of the notebook
3089          ``AUI_NB_TAB_SPLIT``                 Allows the tab control to be split by dragging a tab
3090          ``AUI_NB_TAB_MOVE``                  Allows a tab to be moved horizontally by dragging
3091          ``AUI_NB_TAB_EXTERNAL_MOVE``         Allows a tab to be moved to another tab control
3092          ``AUI_NB_TAB_FIXED_WIDTH``           With this style, all tabs have the same width
3093          ``AUI_NB_SCROLL_BUTTONS``            With this style, left and right scroll buttons are displayed
3094          ``AUI_NB_WINDOWLIST_BUTTON``         With this style, a drop-down list of windows is available
3095          ``AUI_NB_CLOSE_BUTTON``              With this style, a close button is available on the tab bar
3096          ``AUI_NB_CLOSE_ON_ACTIVE_TAB``       With this style, a close button is available on the active tab
3097          ``AUI_NB_CLOSE_ON_ALL_TABS``         With this style, a close button is available on all tabs
3098          ``AUI_NB_MIDDLE_CLICK_CLOSE``        Allows to close L{AuiNotebook} tabs by mouse middle button click
3099          ``AUI_NB_SUB_NOTEBOOK``              This style is used by L{AuiManager} to create automatic AuiNotebooks
3100          ``AUI_NB_HIDE_ON_SINGLE_TAB``        Hides the tab window if only one tab is present
3101          ``AUI_NB_SMART_TABS``                Use Smart Tabbing, like ``Alt`` + ``Tab`` on Windows
3102          ``AUI_NB_USE_IMAGES_DROPDOWN``       Uses images on dropdown window list menu instead of check items
3103          ``AUI_NB_CLOSE_ON_TAB_LEFT``         Draws the tab close button on the left instead of on the right (a la Camino browser)
3104          ``AUI_NB_TAB_FLOAT``                 Allows the floating of single tabs. Known limitation: when the notebook is more or less full screen, tabs cannot be dragged far enough outside of the notebook to become floating pages
3105          ``AUI_NB_DRAW_DND_TAB``              Draws an image representation of a tab while dragging (on by default)
3106          ``AUI_NB_ORDER_BY_ACCESS``           Tab navigation order by last access time for the tabs
3107          ``AUI_NB_NO_TAB_FOCUS``              Don't draw tab focus rectangle
3108          ==================================== ==================================
3109
3110         :note: Please note that some styles cannot be changed after the window
3111          creation and that `Refresh` might need to be be called after changing the
3112          others for the change to take place immediately.
3113
3114         :todo: Implementation of flags ``AUI_NB_RIGHT`` and ``AUI_NB_LEFT``.
3115         """
3116
3117         self._agwFlags = agwStyle
3118
3119         # if the control is already initialized
3120         if self._mgr.GetManagedWindow() == self:
3121
3122             # let all of the tab children know about the new style
3123
3124             all_panes = self._mgr.GetAllPanes()
3125             for pane in all_panes:
3126                 if pane.name == "dummy":
3127                     continue
3128
3129                 tabframe = pane.window
3130                 tabctrl = tabframe._tabs
3131                 tabctrl.SetAGWFlags(self._agwFlags)
3132                 tabframe.DoSizing()
3133                 tabctrl.Refresh()
3134                 tabctrl.Update()
3135
3136
3137     def GetAGWWindowStyleFlag(self):
3138         """
3139         Returns the AGW-specific style of the window.
3140
3141         :see: L{SetAGWWindowStyleFlag} for a list of possible AGW-specific window styles.
3142         """
3143
3144         return self._agwFlags
3145
3146
3147     def AddPage(self, page, caption, select=False, bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap, control=None):
3148         """
3149         Adds a page. If the `select` parameter is ``True``, calling this will generate a
3150         page change event.
3151
3152         :param `page`: the page to be added;
3153         :param `caption`: specifies the text for the new page;
3154         :param `select`: specifies whether the page should be selected;
3155         :param `bitmap`: the `wx.Bitmap` to display in the enabled tab;
3156         :param `disabled_bitmap`: the `wx.Bitmap` to display in the disabled tab;
3157         :param `control`: a `wx.Window` instance inside a tab (or ``None``).
3158         """
3159
3160         return self.InsertPage(self.GetPageCount(), page, caption, select, bitmap, disabled_bitmap, control)
3161
3162
3163     def InsertPage(self, page_idx, page, caption, select=False, bitmap=wx.NullBitmap, disabled_bitmap=wx.NullBitmap,
3164                    control=None):
3165         """
3166         This is similar to L{AddPage}, but allows the ability to specify the insert location.
3167
3168         :param `page_idx`: specifies the position for the new page;
3169         :param `page`: the page to be added;
3170         :param `caption`: specifies the text for the new page;
3171         :param `select`: specifies whether the page should be selected;
3172         :param `bitmap`: the `wx.Bitmap` to display in the enabled tab;
3173         :param `disabled_bitmap`: the `wx.Bitmap` to display in the disabled tab;
3174         :param `control`: a `wx.Window` instance inside a tab (or ``None``).
3175         """
3176
3177         if not page:
3178             return False
3179
3180         page.Reparent(self)
3181         info = AuiNotebookPage()
3182         info.window = page
3183         info.caption = caption
3184         info.bitmap = bitmap
3185         info.active = False
3186         info.control = control
3187
3188         originalPaneMgr = framemanager.GetManager(page)
3189         if originalPaneMgr:
3190             originalPane = originalPaneMgr.GetPane(page)
3191
3192             if originalPane:
3193                 info.hasCloseButton = originalPane.HasCloseButton()
3194
3195         if bitmap.IsOk() and not disabled_bitmap.IsOk():
3196             disabled_bitmap = MakeDisabledBitmap(bitmap)
3197             info.dis_bitmap = disabled_bitmap
3198
3199         # if there are currently no tabs, the first added
3200         # tab must be active
3201         if self._tabs.GetPageCount() == 0:
3202             info.active = True
3203
3204         self._tabs.InsertPage(page, info, page_idx)
3205
3206         # if that was the first page added, even if
3207         # select is False, it must become the "current page"
3208         # (though no select events will be fired)
3209         if not select and self._tabs.GetPageCount() == 1:
3210             select = True
3211
3212         active_tabctrl = self.GetActiveTabCtrl()
3213         if page_idx >= active_tabctrl.GetPageCount():
3214             active_tabctrl.AddPage(page, info)
3215         else:
3216             active_tabctrl.InsertPage(page, info, page_idx)
3217
3218         force = False
3219         if control:
3220             force = True
3221             control.Reparent(active_tabctrl)
3222             control.Show()
3223
3224         self.UpdateTabCtrlHeight(force=force)
3225         self.DoSizing()
3226         active_tabctrl.DoShowHide()
3227
3228         # adjust selected index
3229         if self._curpage >= page_idx:
3230             self._curpage += 1
3231
3232         if select:
3233             self.SetSelectionToWindow(page)
3234
3235         return True
3236
3237
3238     def DeletePage(self, page_idx):
3239         """
3240         Deletes a page at the given index. Calling this method will generate a page
3241         change event.
3242
3243         :param `page_idx`: the page index to be deleted.
3244
3245         :note: L{DeletePage} removes a tab from the multi-notebook, and destroys the window as well.
3246
3247         :see: L{RemovePage}
3248         """
3249
3250         if page_idx >= self._tabs.GetPageCount():
3251             return False
3252
3253         wnd = self._tabs.GetWindowFromIdx(page_idx)
3254         # hide the window in advance, as this will
3255         # prevent flicker
3256         wnd.Show(False)
3257
3258         self.RemoveControlFromPage(page_idx)
3259
3260         if not self.RemovePage(page_idx):
3261             return False
3262
3263         wnd.Destroy()
3264
3265         return True
3266
3267
3268     def RemovePage(self, page_idx):
3269         """
3270         Removes a page, without deleting the window pointer.
3271
3272         :param `page_idx`: the page index to be removed.
3273
3274         :note: L{RemovePage} removes a tab from the multi-notebook, but does not destroy the window.
3275
3276         :see: L{DeletePage}
3277         """
3278
3279         # save active window pointer
3280         active_wnd = None
3281         if self._curpage >= 0:
3282             active_wnd = self._tabs.GetWindowFromIdx(self._curpage)
3283
3284         # save pointer of window being deleted
3285         wnd = self._tabs.GetWindowFromIdx(page_idx)
3286         new_active = None
3287
3288         # make sure we found the page
3289         if not wnd:
3290             return False
3291
3292         # find out which onscreen tab ctrl owns this tab
3293         ctrl, ctrl_idx = self.FindTab(wnd)
3294         if not ctrl:
3295             return False
3296
3297         currentPage = ctrl.GetPage(ctrl_idx)
3298         is_curpage = (self._curpage == page_idx)
3299         is_active_in_split = currentPage.active
3300
3301         # remove the tab from main catalog
3302         if not self._tabs.RemovePage(wnd):
3303             return False
3304
3305         # remove the tab from the onscreen tab ctrl
3306         ctrl.RemovePage(wnd)
3307
3308         if is_active_in_split:
3309
3310             ctrl_new_page_count = ctrl.GetPageCount()
3311
3312             if ctrl_idx >= ctrl_new_page_count:
3313                 ctrl_idx = ctrl_new_page_count - 1
3314
3315             if ctrl_idx >= 0 and ctrl_idx < ctrl.GetPageCount():
3316
3317                 ctrl_idx = self.FindNextActiveTab(ctrl_idx, ctrl)
3318
3319                 # set new page as active in the tab split
3320                 ctrl.SetActivePage(ctrl_idx)
3321
3322                 # if the page deleted was the current page for the
3323                 # entire tab control, then record the window
3324                 # pointer of the new active page for activation
3325                 if is_curpage:
3326                     new_active = ctrl.GetWindowFromIdx(ctrl_idx)
3327
3328         else:
3329
3330             # we are not deleting the active page, so keep it the same
3331             new_active = active_wnd
3332
3333         if not new_active:
3334
3335             # we haven't yet found a new page to active,
3336             # so select the next page from the main tab
3337             # catalogue
3338
3339             if 0 <= page_idx < self._tabs.GetPageCount():
3340                 new_active = self._tabs.GetPage(page_idx).window
3341             if not new_active and self._tabs.GetPageCount() > 0:
3342                 new_active = self._tabs.GetPage(0).window
3343
3344         self.RemoveEmptyTabFrames()
3345
3346         # set new active pane
3347         if new_active:
3348             if not self.IsBeingDeleted():
3349                 self._curpage = -1
3350                 self.SetSelectionToWindow(new_active)
3351         else:
3352             self._curpage = -1
3353             self._tabs.SetNoneActive()
3354
3355         return True
3356
3357
3358     def FindNextActiveTab(self, ctrl_idx, ctrl):
3359         """
3360         Finds the next active tab (used mainly when L{AuiNotebook} has inactive/disabled
3361         tabs in it).
3362
3363         :param `ctrl_idx`: the index of the first (most obvious) tab to check for active status;
3364         :param `ctrl`: an instance of L{AuiTabCtrl}.
3365         """
3366
3367         if self.GetEnabled(ctrl_idx):
3368             return ctrl_idx
3369
3370         for indx in xrange(ctrl_idx, ctrl.GetPageCount()):
3371             if self.GetEnabled(indx):
3372                 return indx
3373
3374         for indx in xrange(ctrl_idx, -1, -1):
3375             if self.GetEnabled(indx):
3376                 return indx
3377
3378         return 0
3379
3380
3381     def HideAllTabs(self, hidden=True):
3382         """
3383         Hides all tabs on the L{AuiNotebook} control.
3384
3385         :param `hidden`: if ``True`` hides all tabs.
3386         """
3387
3388         self._hide_tabs = hidden
3389
3390
3391     def SetSashDClickUnsplit(self, unsplit=True):
3392         """
3393         Sets whether to unsplit a splitted L{AuiNotebook} when double-clicking on a sash.
3394
3395         :param `unsplit`: ``True`` to unsplit on sash double-clicking, ``False`` otherwise.
3396         """
3397
3398         self._sash_dclick_unsplit = unsplit
3399
3400
3401     def GetSashDClickUnsplit(self):
3402         """
3403         Returns whether a splitted L{AuiNotebook} can be unsplitted by double-clicking
3404         on the splitter sash.
3405         """
3406
3407         return self._sash_dclick_unsplit
3408
3409
3410     def SetMinMaxTabWidth(self, minTabWidth, maxTabWidth):
3411         """
3412         Sets the minimum and/or the maximum tab widths for L{AuiNotebook} when the
3413         ``AUI_NB_TAB_FIXED_WIDTH`` style is defined.
3414
3415         Pass -1 to either `minTabWidth` or `maxTabWidth` to reset to the default tab
3416         width behaviour for L{AuiNotebook}.
3417
3418         :param `minTabWidth`: the minimum allowed tab width, in pixels;
3419         :param `maxTabWidth`: the maximum allowed tab width, in pixels.
3420
3421         :note: Minimum and maximum tabs widths are used only when the ``AUI_NB_TAB_FIXED_WIDTH``
3422          style is present.
3423         """
3424
3425         if minTabWidth > maxTabWidth:
3426             raise Exception("Minimum tab width must be less or equal than maximum tab width")
3427
3428         self._tabBounds = (minTabWidth, maxTabWidth)
3429         self.SetAGWWindowStyleFlag(self._agwFlags)
3430
3431
3432     def GetMinMaxTabWidth(self):
3433         """
3434         Returns the minimum and the maximum tab widths for L{AuiNotebook} when the
3435         ``AUI_NB_TAB_FIXED_WIDTH`` style is defined.
3436
3437         :note: Minimum and maximum tabs widths are used only when the ``AUI_NB_TAB_FIXED_WIDTH``
3438          style is present.
3439
3440         :see: L{SetMinMaxTabWidth} for more information.
3441         """
3442
3443         return self._tabBounds
3444
3445
3446     def GetPageIndex(self, page_wnd):
3447         """
3448         Returns the page index for the specified window. If the window is not
3449         found in the notebook, ``wx.NOT_FOUND`` is returned.
3450         """
3451
3452         return self._tabs.GetIdxFromWindow(page_wnd)
3453
3454
3455     def SetPageText(self, page_idx, text):
3456         """
3457         Sets the tab label for the page.
3458
3459         :param `page_idx`: the page index;
3460         :param `text`: the new tab label.
3461         """
3462
3463         if page_idx >= self._tabs.GetPageCount():
3464             return False
3465
3466         # update our own tab catalog
3467         page_info = self._tabs.GetPage(page_idx)
3468         should_refresh = page_info.caption != text
3469         page_info.caption = text
3470
3471         # update what's on screen
3472         ctrl, ctrl_idx = self.FindTab(page_info.window)
3473         if not ctrl:
3474             return False
3475
3476         info = ctrl.GetPage(ctrl_idx)
3477         should_refresh = should_refresh or info.caption != text
3478         info.caption = text
3479
3480         if should_refresh:
3481             ctrl.Refresh()
3482             ctrl.Update()
3483
3484         self.UpdateTabCtrlHeight(force=True)
3485
3486         return True
3487
3488
3489     def GetPageText(self, page_idx):
3490         """
3491         Returns the tab label for the page.
3492
3493         :param `page_idx`: the page index.
3494         """
3495
3496         if page_idx >= self._tabs.GetPageCount():
3497             return ""
3498
3499         # update our own tab catalog
3500         page_info = self._tabs.GetPage(page_idx)
3501         return page_info.caption
3502
3503
3504     def SetPageBitmap(self, page_idx, bitmap):
3505         """
3506         Sets the tab bitmap for the page.
3507
3508         :param `page_idx`: the page index;
3509         :param `bitmap`: an instance of `wx.Bitmap`.
3510         """
3511
3512         if page_idx >= self._tabs.GetPageCount():
3513             return False
3514
3515         # update our own tab catalog
3516         page_info = self._tabs.GetPage(page_idx)
3517         should_refresh = page_info.bitmap is not bitmap
3518         page_info.bitmap = bitmap
3519         if bitmap.IsOk() and not page_info.dis_bitmap.IsOk():
3520             page_info.dis_bitmap = MakeDisabledBitmap(bitmap)
3521
3522         # tab height might have changed
3523         self.UpdateTabCtrlHeight()
3524
3525         # update what's on screen
3526         ctrl, ctrl_idx = self.FindTab(page_info.window)
3527         if not ctrl:
3528             return False
3529
3530         info = ctrl.GetPage(ctrl_idx)
3531         should_refresh = should_refresh or info.bitmap is not bitmap
3532         info.bitmap = bitmap
3533         info.dis_bitmap = page_info.dis_bitmap
3534         if should_refresh:
3535             ctrl.Refresh()
3536             ctrl.Update()
3537
3538         return True
3539
3540
3541     def GetPageBitmap(self, page_idx):
3542         """
3543         Returns the tab bitmap for the page.
3544
3545         :param `page_idx`: the page index.
3546         """
3547
3548         if page_idx >= self._tabs.GetPageCount():
3549             return wx.NullBitmap
3550
3551         # update our own tab catalog
3552         page_info = self._tabs.GetPage(page_idx)
3553         return page_info.bitmap
3554
3555
3556     def SetImageList(self, imageList):
3557         """
3558         Sets the image list for the L{AuiNotebook} control.
3559
3560         :param `imageList`: an instance of `wx.ImageList`.
3561         """
3562
3563         self._imageList = imageList
3564
3565
3566     def AssignImageList(self, imageList):
3567         """
3568         Sets the image list for the L{AuiNotebook} control.
3569
3570         :param `imageList`: an instance of `wx.ImageList`.
3571         """
3572
3573         self.SetImageList(imageList)
3574
3575
3576     def GetImageList(self):
3577         """ Returns the associated image list (if any). """
3578
3579         return self._imageList
3580
3581
3582     def SetPageImage(self, page, image):
3583         """
3584         Sets the image index for the given page.
3585
3586         :param `page`: the page index;
3587         :param `image`: an index into the image list which was set with L{SetImageList}.
3588         """
3589
3590         if page >= self._tabs.GetPageCount():
3591             return False
3592
3593         if not isinstance(image, types.IntType):
3594             raise Exception("The image parameter must be an integer, you passed " \
3595                             "%s"%repr(image))
3596
3597         if not self._imageList:
3598             raise Exception("To use SetPageImage you need to associate an image list " \
3599                             "Using SetImageList or AssignImageList")
3600
3601         if image >= self._imageList.GetImageCount():
3602             raise Exception("Invalid image index (%d), the image list contains only" \
3603                             " (%d) bitmaps"%(image, self._imageList.GetImageCount()))
3604
3605         if image == -1:
3606             self.SetPageBitmap(page, wx.NullBitmap)
3607             return
3608
3609         bitmap = self._imageList.GetBitmap(image)
3610         self.SetPageBitmap(page, bitmap)
3611
3612
3613     def GetPageImage(self, page):
3614         """
3615         Returns the image index for the given page.
3616
3617         :param `page`: the given page for which to retrieve the image index.
3618         """
3619
3620         if page >= self._tabs.GetPageCount():
3621             return False
3622
3623         bitmap = self.GetPageBitmap(page)
3624         for indx in xrange(self._imageList.GetImageCount()):
3625             imgListBmp = self._imageList.GetBitmap(indx)
3626             if imgListBmp == bitmap:
3627                 return indx
3628
3629         return wx.NOT_FOUND
3630
3631
3632     def SetPageTextColour(self, page_idx, colour):
3633         """
3634         Sets the tab text colour for the page.
3635
3636         :param `page_idx`: the page index;
3637         :param `colour`: an instance of `wx.Colour`.
3638         """
3639
3640         if page_idx >= self._tabs.GetPageCount():
3641             return False
3642
3643         # update our own tab catalog
3644         page_info = self._tabs.GetPage(page_idx)
3645         should_refresh = page_info.text_colour != colour
3646         page_info.text_colour = colour
3647
3648         # update what's on screen
3649         ctrl, ctrl_idx = self.FindTab(page_info.window)
3650         if not ctrl:
3651             return False
3652
3653         info = ctrl.GetPage(ctrl_idx)
3654         should_refresh = should_refresh or info.text_colour != colour
3655         info.text_colour = page_info.text_colour
3656
3657         if should_refresh:
3658             ctrl.Refresh()
3659             ctrl.Update()
3660
3661         return True
3662
3663
3664     def GetPageTextColour(self, page_idx):
3665         """
3666         Returns the tab text colour for the page.
3667
3668         :param `page_idx`: the page index.
3669         """
3670
3671         if page_idx >= self._tabs.GetPageCount():
3672             return wx.NullColour
3673
3674         # update our own tab catalog
3675         page_info = self._tabs.GetPage(page_idx)
3676         return page_info.text_colour
3677
3678
3679     def AddControlToPage(self, page_idx, control):
3680         """
3681         Adds a control inside a tab (not in the tab area).
3682
3683         :param `page_idx`: the page index;
3684         :param `control`: an instance of `wx.Window`.
3685         """