correction for ape 5.1
[iramuteq] / aui / aui_switcherdialog.py
1 """
2 Description
3 ===========
4
5 The idea of `SwitcherDialog` is to make it easier to implement keyboard
6 navigation in AUI and other applications that have multiple panes and
7 tabs.
8
9 A key combination with a modifier (such as ``Ctrl`` + ``Tab``) shows the
10 dialog, and the user holds down the modifier whilst navigating with
11 ``Tab`` and arrow keys before releasing the modifier to dismiss the dialog
12 and activate the selected pane.
13
14 The switcher dialog is a multi-column menu with no scrolling, implemented
15 by the `MultiColumnListCtrl` class. You can have headings for your items
16 for logical grouping, and you can force a column break if you need to.
17
18 The modifier used for invoking and dismissing the dialog can be customised,
19 as can the colours, number of rows, and the key used for cycling through
20 the items. So you can use different keys on different platforms if
21 required (especially since ``Ctrl`` + ``Tab`` is reserved on some platforms).
22
23 Items are shown as names and optional 16x16 images.
24
25
26 Base Functionalities
27 ====================
28
29 To use the dialog, you set up the items in a `SwitcherItems` object,
30 before passing this to the `SwitcherDialog` instance.
31
32 Call L{SwitcherItems.AddItem} and optionally L{SwitcherItems.AddGroup} to add items and headings. These
33 functions take a label (to be displayed to the user), an identifying name,
34 an integer id, and a bitmap. The name and id are purely for application-defined
35 identification. You may also set a description to be displayed when each
36 item is selected; and you can set a window pointer for convenience when
37 activating the desired window after the dialog returns.
38
39 Have created the dialog, you call `ShowModal()`, and if the return value is
40 ``wx.ID_OK``, retrieve the selection from the dialog and activate the pane.
41
42 The sample code below shows a generic method of finding panes and notebook
43 tabs within the current L{AuiManager}, and using the pane name or notebook
44 tab position to display the pane.
45
46 The only other code to add is a menu item with the desired accelerator,
47 whose modifier matches the one you pass to L{SwitcherDialog.SetModifierKey} 
48 (the default being ``wx.WXK_CONTROL``).
49
50
51 Usage
52 =====
53
54 Menu item::
55
56     if wx.Platform == "__WXMAC__":
57         switcherAccel = "Alt+Tab"
58     elif wx.Platform == "__WXGTK__":
59         switcherAccel = "Ctrl+/"
60     else:
61         switcherAccel = "Ctrl+Tab"
62
63     view_menu.Append(ID_SwitchPane, _("S&witch Window...") + "\t" + switcherAccel)
64
65
66 Event handler::
67
68     def OnSwitchPane(self, event):
69
70         items = SwitcherItems()
71         items.SetRowCount(12)
72
73         # Add the main windows and toolbars, in two separate columns
74         # We'll use the item 'id' to store the notebook selection, or -1 if not a page
75
76         for k in xrange(2):
77             if k == 0:
78                 items.AddGroup(_("Main Windows"), "mainwindows")
79             else:
80                 items.AddGroup(_("Toolbars"), "toolbars").BreakColumn()
81
82             for pane in self._mgr.GetAllPanes():
83                 name = pane.name
84                 caption = pane.caption
85
86                 toolbar = isinstance(info.window, wx.ToolBar) or isinstance(info.window, aui.AuiToolBar)
87                 if caption and (toolBar  and k == 1) or (not toolBar and k == 0):
88                     items.AddItem(caption, name, -1).SetWindow(pane.window)
89
90         # Now add the wxAuiNotebook pages
91
92         items.AddGroup(_("Notebook Pages"), "pages").BreakColumn()
93
94         for pane in self._mgr.GetAllPanes():
95             nb = pane.window
96             if isinstance(nb, aui.AuiNotebook):
97                 for j in xrange(nb.GetPageCount()):
98
99                     name = nb.GetPageText(j)
100                     win = nb.GetPage(j)
101
102                     items.AddItem(name, name, j, nb.GetPageBitmap(j)).SetWindow(win)
103
104         # Select the focused window
105
106         idx = items.GetIndexForFocus()
107         if idx != wx.NOT_FOUND:
108             items.SetSelection(idx)
109
110         if wx.Platform == "__WXMAC__":
111             items.SetBackgroundColour(wx.WHITE)
112         
113         # Show the switcher dialog
114
115         dlg = SwitcherDialog(items, wx.GetApp().GetTopWindow())
116
117         # In GTK+ we can't use Ctrl+Tab; we use Ctrl+/ instead and tell the switcher
118         # to treat / in the same was as tab (i.e. cycle through the names)
119
120         if wx.Platform == "__WXGTK__":
121             dlg.SetExtraNavigationKey(wxT('/'))
122
123         if wx.Platform == "__WXMAC__":
124             dlg.SetBackgroundColour(wx.WHITE)
125             dlg.SetModifierKey(wx.WXK_ALT)
126
127         ans = dlg.ShowModal()
128
129         if ans == wx.ID_OK and dlg.GetSelection() != -1:
130             item = items.GetItem(dlg.GetSelection())
131
132             if item.GetId() == -1:
133                 info = self._mgr.GetPane(item.GetName())
134                 info.Show()
135                 self._mgr.Update()
136                 info.window.SetFocus()
137
138             else:
139                 nb = item.GetWindow().GetParent()
140                 win = item.GetWindow();
141                 if isinstance(nb, aui.AuiNotebook):
142                     nb.SetSelection(item.GetId())
143                     win.SetFocus()
144
145
146 """
147
148 import wx
149
150 import auibook
151 from aui_utilities import FindFocusDescendant
152 from aui_constants import SWITCHER_TEXT_MARGIN_X, SWITCHER_TEXT_MARGIN_Y
153
154
155 # Define a translation function
156 _ = wx.GetTranslation
157
158     
159 class SwitcherItem(object):
160     """ An object containing information about one item. """
161     
162     def __init__(self, item=None):
163         """ Default class constructor. """
164
165         self._id = 0
166         self._isGroup = False
167         self._breakColumn = False
168         self._rowPos = 0
169         self._colPos = 0
170         self._window = None
171         self._description = ""
172
173         self._textColour = wx.NullColour
174         self._bitmap = wx.NullBitmap
175         self._font = wx.NullFont
176         
177         if item:
178             self.Copy(item)
179
180
181     def Copy(self, item):
182         """
183         Copy operator between 2 L{SwitcherItem} instances.
184
185         :param `item`: another instance of L{SwitcherItem}.
186         """
187
188         self._id = item._id
189         self._name = item._name
190         self._title = item._title
191         self._isGroup = item._isGroup
192         self._breakColumn = item._breakColumn
193         self._rect = item._rect
194         self._font = item._font
195         self._textColour = item._textColour
196         self._bitmap = item._bitmap
197         self._description = item._description
198         self._rowPos = item._rowPos
199         self._colPos = item._colPos
200         self._window = item._window
201
202
203     def SetTitle(self, title):
204
205         self._title = title
206         return self
207     
208
209     def GetTitle(self):
210         
211         return self._title
212
213
214     def SetName(self, name):
215
216         self._name = name
217         return self
218
219     
220     def GetName(self):
221
222         return self._name
223
224
225     def SetDescription(self, descr):
226
227         self._description = descr
228         return self
229
230
231     def GetDescription(self):
232
233         return self._description
234     
235
236     def SetId(self, id):
237
238         self._id = id
239         return self
240
241     
242     def GetId(self):
243
244         return self._id
245
246
247     def SetIsGroup(self, isGroup):
248
249         self._isGroup = isGroup
250         return self
251
252     
253     def GetIsGroup(self):
254
255         return self._isGroup
256     
257
258     def BreakColumn(self, breakCol=True):
259
260         self._breakColumn = breakCol
261         return self
262
263     
264     def GetBreakColumn(self):
265
266         return self._breakColumn
267
268
269     def SetRect(self, rect):
270
271         self._rect = rect
272         return self
273
274     
275     def GetRect(self):
276         
277         return self._rect
278
279
280     def SetTextColour(self, colour):
281
282         self._textColour = colour
283         return self
284
285     
286     def GetTextColour(self):
287
288         return self._textColour
289     
290
291     def SetFont(self, font):
292
293         self._font = font
294         return self
295
296     
297     def GetFont(self):
298
299         return self._font
300     
301
302     def SetBitmap(self, bitmap):
303
304         self._bitmap = bitmap
305         return self
306
307     
308     def GetBitmap(self):
309
310         return self._bitmap
311
312
313     def SetRowPos(self, pos):
314
315         self._rowPos = pos
316         return self
317
318     
319     def GetRowPos(self):
320
321         return self._rowPos
322     
323
324     def SetColPos(self, pos):
325
326         self._colPos = pos
327         return self
328
329     
330     def GetColPos(self):
331
332         return self._colPos
333     
334
335     def SetWindow(self, win):
336
337         self._window = win
338         return self
339     
340
341     def GetWindow(self):
342
343         return self._window
344
345     
346 class SwitcherItems(object):
347     """ An object containing switcher items. """
348
349     def __init__(self, items=None):
350         """ Default class constructor. """
351
352         self._selection = -1
353         self._rowCount = 10
354         self._columnCount = 0
355
356         self._backgroundColour = wx.NullColour
357         self._textColour = wx.NullColour
358         self._selectionColour = wx.NullColour
359         self._selectionOutlineColour = wx.NullColour
360         self._itemFont = wx.NullFont
361
362         self._items = []        
363         
364         if wx.Platform == "__WXMSW__":
365             # If on Windows XP/Vista, use more appropriate colours
366             self.SetSelectionOutlineColour(wx.Colour(49, 106, 197))
367             self.SetSelectionColour(wx.Colour(193, 210, 238))
368
369         if items:
370             self.Copy(items)
371             
372
373     def Copy(self, items):
374         """
375         Copy operator between 2 L{SwitcherItems}.
376
377         :param `items`: another instance of L{SwitcherItems}.
378         """
379         
380         self.Clear()
381
382         for item in items._items:
383             self._items.append(item)
384         
385         self._selection = items._selection
386         self._rowCount = items._rowCount
387         self._columnCount = items._columnCount
388
389         self._backgroundColour = items._backgroundColour
390         self._textColour = items._textColour
391         self._selectionColour = items._selectionColour
392         self._selectionOutlineColour = items._selectionOutlineColour
393         self._itemFont = items._itemFont
394
395
396     def AddItem(self, titleOrItem, name=None, id=0, bitmap=wx.NullBitmap):
397
398         if isinstance(titleOrItem, SwitcherItem):
399             self._items.append(titleOrItem)
400             return self._items[-1]
401         
402         item = SwitcherItem()
403         item.SetTitle(titleOrItem)
404         item.SetName(name)
405         item.SetId(id)
406         item.SetBitmap(bitmap)
407
408         self._items.append(item)
409         return self._items[-1]
410
411
412     def AddGroup(self, title, name, id=0, bitmap=wx.NullBitmap):
413
414         item = self.AddItem(title, name, id, bitmap)
415         item.SetIsGroup(True)
416
417         return item
418
419
420     def Clear(self):
421
422         self._items = []
423
424
425     def FindItemByName(self, name):
426
427         for i in xrange(len(self._items)):
428             if self._items[i].GetName() == name:
429                 return i
430         
431         return wx.NOT_FOUND
432
433
434     def FindItemById(self, id):
435
436         for i in xrange(len(self._items)):
437             if self._items[i].GetId() == id:
438                 return i
439         
440         return wx.NOT_FOUND
441
442
443     def SetSelection(self, sel):
444
445         self._selection = sel
446
447
448     def SetSelectionByName(self, name):
449
450         idx = self.FindItemByName(name)
451         if idx != wx.NOT_FOUND:
452             self.SetSelection(idx)
453
454
455     def GetSelection(self):
456
457         return self._selection
458     
459
460     def GetItem(self, i):
461
462         return self._items[i]
463
464
465     def GetItemCount(self):
466
467         return len(self._items)
468     
469
470     def SetRowCount(self, rows):
471
472         self._rowCount = rows
473
474         
475     def GetRowCount(self):
476
477         return self._rowCount
478
479
480     def SetColumnCount(self, cols):
481
482         self._columnCount = cols
483
484         
485     def GetColumnCount(self):
486
487         return self._columnCount
488     
489
490     def SetBackgroundColour(self, colour):
491
492         self._backgroundColour = colour
493
494         
495     def GetBackgroundColour(self):
496
497         return self._backgroundColour
498     
499
500     def SetTextColour(self, colour):
501
502         self._textColour = colour
503
504         
505     def GetTextColour(self):
506
507         return self._textColour
508     
509
510     def SetSelectionColour(self, colour):
511
512         self._selectionColour = colour
513
514         
515     def GetSelectionColour(self):
516
517         return self._selectionColour
518     
519
520     def SetSelectionOutlineColour(self, colour):
521
522         self._selectionOutlineColour = colour
523
524         
525     def GetSelectionOutlineColour(self):
526
527         return self._selectionOutlineColour
528     
529
530     def SetItemFont(self, font):
531
532         self._itemFont = font
533
534         
535     def GetItemFont(self):
536
537         return self._itemFont 
538     
539
540     def PaintItems(self, dc, win):
541
542         backgroundColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE)
543         standardTextColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
544         selectionColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
545         selectionOutlineColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
546         standardFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
547         groupFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
548         groupFont.SetWeight(wx.BOLD)
549
550         if self.GetBackgroundColour().IsOk():
551             backgroundColour = self.GetBackgroundColour()
552
553         if self.GetTextColour().IsOk():
554             standardTextColour = self.GetTextColour()
555
556         if self.GetSelectionColour().IsOk():
557             selectionColour = self.GetSelectionColour()
558
559         if self.GetSelectionOutlineColour().IsOk():
560             selectionOutlineColour = self.GetSelectionOutlineColour()
561
562         if self.GetItemFont().IsOk():
563         
564             standardFont = self.GetItemFont()   
565             groupFont = wx.Font(standardFont.GetPointSize(), standardFont.GetFamily(), standardFont.GetStyle(),
566                                 wx.BOLD, standardFont.GetUnderlined(), standardFont.GetFaceName())
567         
568         textMarginX = SWITCHER_TEXT_MARGIN_X
569
570         dc.SetLogicalFunction(wx.COPY)
571         dc.SetBrush(wx.Brush(backgroundColour))
572         dc.SetPen(wx.TRANSPARENT_PEN)
573         dc.DrawRectangleRect(win.GetClientRect())
574         dc.SetBackgroundMode(wx.TRANSPARENT)
575
576         for i in xrange(len(self._items)):
577             item = self._items[i]
578             if i == self._selection:
579                 dc.SetPen(wx.Pen(selectionOutlineColour))
580                 dc.SetBrush(wx.Brush(selectionColour))
581                 dc.DrawRectangleRect(item.GetRect())
582             
583             clippingRect = wx.Rect(*item.GetRect())
584             clippingRect.Deflate(1, 1)
585
586             dc.SetClippingRect(clippingRect)
587
588             if item.GetTextColour().IsOk():
589                 dc.SetTextForeground(item.GetTextColour())
590             else:
591                 dc.SetTextForeground(standardTextColour)
592             
593             if item.GetFont().IsOk():
594                 dc.SetFont(item.GetFont())
595             else:
596                 if item.GetIsGroup():
597                     dc.SetFont(groupFont)
598                 else:
599                     dc.SetFont(standardFont)
600             
601             w, h = dc.GetTextExtent(item.GetTitle())
602             x = item.GetRect().x
603
604             x += textMarginX
605
606             if not item.GetIsGroup():
607                 if item.GetBitmap().IsOk() and item.GetBitmap().GetWidth() <= 16 \
608                    and item.GetBitmap().GetHeight() <= 16:
609                     x -= textMarginX
610                     dc.DrawBitmap(item.GetBitmap(), x, item.GetRect().y + \
611                                   (item.GetRect().height - item.GetBitmap().GetHeight())/2,
612                                   True)
613                     x += 16 + textMarginX
614                 #x += textMarginX
615             
616             y = item.GetRect().y + (item.GetRect().height - h)/2
617             dc.DrawText(item.GetTitle(), x, y)
618             dc.DestroyClippingRegion()
619     
620
621     def CalculateItemSize(self, dc):
622
623         # Start off allowing for an icon
624         sz = wx.Size(150, 16)
625         standardFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
626         groupFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
627         groupFont.SetWeight(wx.BOLD)
628
629         textMarginX = SWITCHER_TEXT_MARGIN_X
630         textMarginY = SWITCHER_TEXT_MARGIN_Y
631         maxWidth = 300
632         maxHeight = 40
633
634         if self.GetItemFont().IsOk():
635             standardFont = self.GetItemFont()   
636
637         for item in self._items:
638             if item.GetFont().IsOk():
639                 dc.SetFont(item.GetFont())
640             else:
641                 if item.GetIsGroup():
642                     dc.SetFont(groupFont)
643                 else:
644                     dc.SetFont(standardFont)
645
646             w, h = dc.GetTextExtent(item.GetTitle())
647             w += 16 + 2*textMarginX
648
649             if w > sz.x:
650                 sz.x = min(w, maxWidth)
651             if h > sz.y:
652                 sz.y = min(h, maxHeight)
653         
654         if sz == wx.Size(16, 16):
655             sz = wx.Size(100, 25)
656         else:
657             sz.x += textMarginX*2
658             sz.y += textMarginY*2
659         
660         return sz
661
662
663     def GetIndexForFocus(self):
664
665         for i, item in enumerate(self._items):        
666             if item.GetWindow():
667             
668                 if FindFocusDescendant(item.GetWindow()):
669                     return i
670             
671         return wx.NOT_FOUND
672
673
674 class MultiColumnListCtrl(wx.PyControl):
675     """ A control for displaying several columns (not scrollable). """
676
677     def __init__(self, parent, aui_manager, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
678                  style=0, validator=wx.DefaultValidator, name="MultiColumnListCtrl"):
679
680         wx.PyControl.__init__(self, parent, id, pos, size, style, validator, name)
681
682         self._overallSize = wx.Size(200, 100)
683         self._modifierKey = wx.WXK_CONTROL
684         self._extraNavigationKey = 0
685         self._aui_manager = aui_manager
686         
687         self.SetInitialSize(size)
688         self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
689
690         self.Bind(wx.EVT_PAINT, self.OnPaint)
691         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
692         self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseEvent)
693         self.Bind(wx.EVT_CHAR, self.OnChar)
694         self.Bind(wx.EVT_KEY_DOWN, self.OnKey)
695         self.Bind(wx.EVT_KEY_UP, self.OnKey)
696
697
698     def __del__(self):
699
700         self._aui_manager.HideHint()
701
702         
703     def DoGetBestSize(self):
704
705         return self._overallSize
706
707
708     def OnEraseBackground(self, event):
709         
710         pass
711
712
713     def OnPaint(self, event):
714
715         dc = wx.AutoBufferedPaintDC(self)
716         rect = self.GetClientRect()
717
718         if self._items.GetColumnCount() == 0:
719             self.CalculateLayout(dc)
720
721         if self._items.GetColumnCount() == 0:
722             return
723
724         self._items.PaintItems(dc, self)
725
726
727     def OnMouseEvent(self, event):
728
729         if event.LeftDown():
730             self.SetFocus()
731     
732
733     def OnChar(self, event):
734
735         event.Skip()        
736
737
738     def OnKey(self, event):
739
740         if event.GetEventType() == wx.wxEVT_KEY_UP:
741             if event.GetKeyCode() == self.GetModifierKey():
742                 topLevel = wx.GetTopLevelParent(self)
743                 closeEvent = wx.CloseEvent(wx.wxEVT_CLOSE_WINDOW, topLevel.GetId())
744                 closeEvent.SetEventObject(topLevel)
745                 closeEvent.SetCanVeto(False)
746                 
747                 topLevel.GetEventHandler().ProcessEvent(closeEvent)
748                 return
749                 
750             event.Skip()
751             return
752
753         keyCode = event.GetKeyCode()
754         
755         if keyCode in [wx.WXK_ESCAPE, wx.WXK_RETURN]:
756             if keyCode == wx.WXK_ESCAPE:
757                 self._items.SetSelection(-1)
758
759             topLevel = wx.GetTopLevelParent(self)
760             closeEvent = wx.CloseEvent(wx.wxEVT_CLOSE_WINDOW, topLevel.GetId())
761             closeEvent.SetEventObject(topLevel)
762             closeEvent.SetCanVeto(False)
763             
764             topLevel.GetEventHandler().ProcessEvent(closeEvent)
765             return
766         
767         elif keyCode in [wx.WXK_TAB, self.GetExtraNavigationKey()]:
768             if event.ShiftDown():
769             
770                 self._items.SetSelection(self._items.GetSelection() - 1)
771                 if self._items.GetSelection() < 0:
772                     self._items.SetSelection(self._items.GetItemCount() - 1)
773
774                 self.AdvanceToNextSelectableItem(-1)
775             
776             else:
777             
778                 self._items.SetSelection(self._items.GetSelection() + 1)
779                 if self._items.GetSelection() >= self._items.GetItemCount():
780                     self._items.SetSelection(0)
781
782                 self.AdvanceToNextSelectableItem(1)
783             
784             self.GenerateSelectionEvent()
785             self.Refresh()
786         
787         elif keyCode in [wx.WXK_DOWN, wx.WXK_NUMPAD_DOWN]:
788             self._items.SetSelection(self._items.GetSelection() + 1)
789             if self._items.GetSelection() >= self._items.GetItemCount():
790                 self._items.SetSelection(0)
791             
792             self.AdvanceToNextSelectableItem(1)
793             self.GenerateSelectionEvent()
794             self.Refresh()
795         
796         elif keyCode in [wx.WXK_UP, wx.WXK_NUMPAD_UP]:
797             self._items.SetSelection(self._items.GetSelection() - 1)
798             if self._items.GetSelection() < 0:
799                 self._items.SetSelection(self._items.GetItemCount() - 1)
800             
801             self.AdvanceToNextSelectableItem(-1)
802             self.GenerateSelectionEvent()
803             self.Refresh()
804         
805         elif keyCode in [wx.WXK_HOME, wx.WXK_NUMPAD_HOME]:
806             self._items.SetSelection(0)
807             self.AdvanceToNextSelectableItem(1)
808             self.GenerateSelectionEvent()
809             self.Refresh()
810         
811         elif keyCode in [wx.WXK_END, wx.WXK_NUMPAD_END]:
812             self._items.SetSelection(self._items.GetItemCount() - 1)
813             self.AdvanceToNextSelectableItem(-1)
814             self.GenerateSelectionEvent()
815             self.Refresh()
816         
817         elif keyCode in [wx.WXK_LEFT, wx.WXK_NUMPAD_LEFT]:
818             item = self._items.GetItem(self._items.GetSelection())
819
820             row = item.GetRowPos()
821             newCol = item.GetColPos() - 1
822             if newCol < 0:
823                 newCol = self._items.GetColumnCount() - 1
824
825             # Find the first item from the end whose row matches and whose column is equal or lower
826             for i in xrange(self._items.GetItemCount()-1, -1, -1):
827                 item2 = self._items.GetItem(i)
828                 if item2.GetColPos() == newCol and item2.GetRowPos() <= row:
829                     self._items.SetSelection(i)
830                     break
831
832             self.AdvanceToNextSelectableItem(-1)
833             self.GenerateSelectionEvent()
834             self.Refresh()
835         
836         elif keyCode in [wx.WXK_RIGHT, wx.WXK_NUMPAD_RIGHT]:
837             item = self._items.GetItem(self._items.GetSelection())
838
839             row = item.GetRowPos()
840             newCol = item.GetColPos() + 1
841             if newCol >= self._items.GetColumnCount():
842                 newCol = 0
843
844             # Find the first item from the end whose row matches and whose column is equal or lower
845             for i in xrange(self._items.GetItemCount()-1, -1, -1):
846                 item2 = self._items.GetItem(i)
847                 if item2.GetColPos() == newCol and item2.GetRowPos() <= row:
848                     self._items.SetSelection(i)
849                     break
850
851             self.AdvanceToNextSelectableItem(1)
852             self.GenerateSelectionEvent()
853             self.Refresh()
854         
855         else:
856             event.Skip()
857
858
859     def AdvanceToNextSelectableItem(self, direction):
860
861         if self._items.GetItemCount() < 2:
862             return
863
864         if self._items.GetSelection() == -1:
865             self._items.SetSelection(0)
866
867         oldSel = self._items.GetSelection()
868
869         while 1:
870         
871             if self._items.GetItem(self._items.GetSelection()).GetIsGroup():
872             
873                 self._items.SetSelection(self._items.GetSelection() + direction)
874                 if self._items.GetSelection() == -1:
875                     self._items.SetSelection(self._items.GetItemCount()-1)
876                 elif self._items.GetSelection() == self._items.GetItemCount():
877                     self._items.SetSelection(0)
878                 if self._items.GetSelection() == oldSel:
879                     break
880             
881             else:
882                 break
883
884         self.SetTransparency()
885         selection = self._items.GetItem(self._items.GetSelection()).GetWindow()
886         pane = self._aui_manager.GetPane(selection)
887
888         if not pane.IsOk():
889             if isinstance(selection.GetParent(), auibook.AuiNotebook):
890                 self.SetTransparency(selection)
891                 self._aui_manager.ShowHint(selection.GetScreenRect())
892                 wx.CallAfter(self.SetFocus)
893                 self.SetFocus()
894                 return
895             else:
896                 self._aui_manager.HideHint()
897                 return
898         if not pane.IsShown():
899             self._aui_manager.HideHint()
900             return
901
902         self.SetTransparency(selection)
903         self._aui_manager.ShowHint(selection.GetScreenRect())
904         # NOTE: this is odd but it is the only way for the focus to
905         #       work correctly on wxMac...
906         wx.CallAfter(self.SetFocus)
907         self.SetFocus()        
908     
909
910     def SetTransparency(self, selection=None):
911
912         if not self.GetParent().CanSetTransparent():
913             return
914         
915         if selection is not None:
916             intersects = False
917             if selection.GetScreenRect().Intersects(self.GetParent().GetScreenRect()):
918                 intersects = True
919                 self.GetParent().SetTransparent(200)
920                 return
921
922         self.GetParent().SetTransparent(255)
923
924
925     def GenerateSelectionEvent(self):
926
927         event = wx.CommandEvent(wx.wxEVT_COMMAND_LISTBOX_SELECTED, self.GetId())
928         event.SetEventObject(self)
929         event.SetInt(self._items.GetSelection())
930         self.GetEventHandler().ProcessEvent(event)
931
932
933     def CalculateLayout(self, dc=None):
934
935         if dc is None:
936             dc = wx.ClientDC(self)
937
938         if self._items.GetSelection() == -1:
939             self._items.SetSelection(0)
940
941         columnCount = 1
942
943         # Spacing between edge of window or between columns
944         xMargin = 4
945         yMargin = 4
946
947         # Inter-row spacing
948         rowSpacing = 2
949
950         itemSize = self._items.CalculateItemSize(dc)
951         self._overallSize = wx.Size(350, 200)
952
953         currentRow = 0
954         x = xMargin
955         y = yMargin
956
957         breaking = False
958         i = 0
959         
960         while 1:
961         
962             oldOverallSize = self._overallSize
963             item = self._items.GetItem(i)
964             
965             item.SetRect(wx.Rect(x, y, itemSize.x, itemSize.y))
966             item.SetColPos(columnCount-1)
967             item.SetRowPos(currentRow)
968
969             if item.GetRect().GetBottom() > self._overallSize.y:
970                 self._overallSize.y = item.GetRect().GetBottom() + yMargin
971
972             if item.GetRect().GetRight() > self._overallSize.x:
973                 self._overallSize.x = item.GetRect().GetRight() + xMargin
974
975             currentRow += 1
976
977             y += rowSpacing + itemSize.y
978             stopBreaking = breaking
979
980             if currentRow > self._items.GetRowCount() or (item.GetBreakColumn() and not breaking and currentRow != 1):
981                 currentRow = 0
982                 columnCount += 1
983                 x += xMargin + itemSize.x
984                 y = yMargin
985
986                 # Make sure we don't orphan a group
987                 if item.GetIsGroup() or (item.GetBreakColumn() and not breaking):
988                     self._overallSize = oldOverallSize
989
990                     if item.GetBreakColumn():
991                         breaking = True
992
993                     # Repeat the last item, in the next column
994                     i -= 1
995                 
996             if stopBreaking:
997                 breaking = False
998
999             i += 1
1000             
1001             if i >= self._items.GetItemCount():
1002                 break
1003             
1004         self._items.SetColumnCount(columnCount)
1005         self.InvalidateBestSize()
1006
1007
1008     def SetItems(self, items):
1009         
1010         self._items = items
1011
1012         
1013     def GetItems(self):
1014
1015         return self._items
1016
1017  
1018     def SetExtraNavigationKey(self, keyCode):
1019         """
1020         Set an extra key that can be used to cycle through items,
1021         in case not using the ``Ctrl`` + ``Tab`` combination.
1022         """
1023
1024         self._extraNavigationKey = keyCode
1025
1026
1027     def GetExtraNavigationKey(self):
1028
1029         return self._extraNavigationKey
1030
1031
1032     def SetModifierKey(self, modifierKey):
1033         """
1034         Set the modifier used to invoke the dialog, and therefore to test for
1035         release.
1036         """
1037
1038         self._modifierKey = modifierKey
1039
1040         
1041     def GetModifierKey(self):
1042
1043         return self._modifierKey
1044
1045     
1046
1047 class SwitcherDialog(wx.Dialog):
1048     """
1049     SwitcherDialog shows a L{MultiColumnListCtrl} with a list of panes
1050     and tabs for the user to choose. ``Ctrl`` + ``Tab`` cycles through them.
1051     """
1052
1053     def __init__(self, items, parent, aui_manager, id=wx.ID_ANY, title=_("Pane Switcher"), pos=wx.DefaultPosition,
1054                  size=wx.DefaultSize, style=wx.STAY_ON_TOP|wx.DIALOG_NO_PARENT|wx.BORDER_SIMPLE):
1055         """ Default class constructor. """
1056         
1057         self._switcherBorderStyle = (style & wx.BORDER_MASK)
1058         if self._switcherBorderStyle == wx.BORDER_NONE:
1059             self._switcherBorderStyle = wx.BORDER_SIMPLE
1060
1061         style &= wx.BORDER_MASK
1062         style |= wx.BORDER_NONE
1063
1064         wx.Dialog.__init__(self, parent, id, title, pos, size, style)
1065
1066         self._listCtrl = MultiColumnListCtrl(self, aui_manager,
1067                                              style=wx.WANTS_CHARS|wx.NO_BORDER)
1068         self._listCtrl.SetItems(items)
1069         self._listCtrl.CalculateLayout()
1070
1071         self._descriptionCtrl = wx.html.HtmlWindow(self, size=(-1, 100), style=wx.BORDER_NONE)
1072         self._descriptionCtrl.SetBackgroundColour(self.GetBackgroundColour())
1073
1074         if wx.Platform == "__WXGTK__":
1075             fontSize = 11
1076             self._descriptionCtrl.SetStandardFonts(fontSize)
1077
1078         sizer = wx.BoxSizer(wx.VERTICAL)
1079         self.SetSizer(sizer)
1080         sizer.Add(self._listCtrl, 1, wx.ALL|wx.EXPAND, 10)
1081         sizer.Add(self._descriptionCtrl, 0, wx.ALL|wx.EXPAND, 10)
1082         sizer.SetSizeHints(self)
1083
1084         self._listCtrl.SetFocus()
1085
1086         self.Centre(wx.BOTH)
1087
1088         if self._listCtrl.GetItems().GetSelection() == -1:
1089             self._listCtrl.GetItems().SetSelection(0)
1090
1091         self._listCtrl.AdvanceToNextSelectableItem(1)
1092
1093         self.ShowDescription(self._listCtrl.GetItems().GetSelection())
1094
1095         self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
1096         self.Bind(wx.EVT_ACTIVATE, self.OnActivate)
1097         self.Bind(wx.EVT_LISTBOX, self.OnSelectItem)
1098         self.Bind(wx.EVT_PAINT, self.OnPaint)
1099
1100         # Attributes
1101         self._closing = False
1102         if wx.Platform == "__WXMSW__":
1103             self._borderColour = wx.Colour(49, 106, 197)
1104         else:
1105             self._borderColour = wx.BLACK
1106
1107         self._aui_manager = aui_manager
1108         
1109
1110     def OnCloseWindow(self, event):
1111
1112         if self._closing:
1113             return
1114
1115         if self.IsModal():
1116             self._closing = True
1117
1118             if self.GetSelection() == -1:
1119                 self.EndModal(wx.ID_CANCEL)
1120             else:
1121                 self.EndModal(wx.ID_OK)
1122     
1123         self._aui_manager.HideHint()
1124
1125
1126     def GetSelection(self):
1127
1128         return self._listCtrl.GetItems().GetSelection()
1129
1130
1131     def OnActivate(self, event):
1132
1133         if not event.GetActive():
1134             if not self._closing:
1135                 self._closing = True
1136                 self.EndModal(wx.ID_CANCEL)
1137             
1138
1139     def OnPaint(self, event):
1140
1141         dc = wx.PaintDC(self)
1142
1143         if self._switcherBorderStyle == wx.BORDER_SIMPLE:
1144         
1145             dc.SetPen(wx.Pen(self._borderColour))
1146             dc.SetBrush(wx.TRANSPARENT_BRUSH)
1147
1148             rect = self.GetClientRect()
1149             dc.DrawRectangleRect(rect)
1150
1151             # Draw border around the HTML control
1152             rect = wx.Rect(*self._descriptionCtrl.GetRect())
1153             rect.Inflate(1, 1)
1154             dc.DrawRectangleRect(rect)
1155
1156     
1157     def OnSelectItem(self, event):
1158
1159         self.ShowDescription(event.GetSelection())
1160
1161
1162 # Convert a colour to a 6-digit hex string
1163     def ColourToHexString(self, col):
1164
1165         hx = '%02x%02x%02x' % tuple([int(c) for c in col])
1166         return hx
1167
1168
1169     def ShowDescription(self, i):
1170
1171         item = self._listCtrl.GetItems().GetItem(i)
1172         colour = self._listCtrl.GetItems().GetBackgroundColour()
1173         
1174         if not colour.IsOk():
1175             colour = self.GetBackgroundColour()
1176
1177         backgroundColourHex = self.ColourToHexString(colour)
1178         html = _("<body bgcolor=\"#") + backgroundColourHex + _("\"><b>") + item.GetTitle() + _("</b>")
1179
1180         if item.GetDescription():
1181             html += _("<p>")
1182             html += item.GetDescription()
1183         
1184         html += _("</body>")
1185         self._descriptionCtrl.SetPage(html)
1186
1187
1188     def SetExtraNavigationKey(self, keyCode):
1189
1190         self._extraNavigationKey = keyCode
1191         if self._listCtrl:
1192             self._listCtrl.SetExtraNavigationKey(keyCode)
1193
1194
1195     def GetExtraNavigationKey(self):
1196
1197         return self._extraNavigationKey
1198     
1199         
1200     def SetModifierKey(self, modifierKey):
1201
1202         self._modifierKey = modifierKey
1203         if self._listCtrl:
1204             self._listCtrl.SetModifierKey(modifierKey)
1205
1206
1207     def GetModifierKey(self):
1208
1209         return self._modifierKey        
1210
1211
1212     def SetBorderColour(self, colour):
1213
1214         self._borderColour = colour
1215
1216