1# --------------------------------------------------------------------------------- #
2# FLATMENU wxPython IMPLEMENTATION
3#
4# Andrea Gavana, @ 03 Nov 2006
5# Latest Revision: 27 Dec 2012, 21.00 GMT
6#
7# TODO List
8#
9# 1. Work is still in progress, so other functionalities may be added in the future;
10# 2. No shadows under MAC, but it may be possible to create them using Carbon.
11#
12#
13# For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please
14# Write To Me At:
15#
16# andrea.gavana@maerskoil.com
17# andrea.gavana@gmail.com
18#
19# Or, Obviously, To The wxPython Mailing List!!!
20#
21#
22# Tags:     phoenix-port, py3-port, unittest, documented
23# --------------------------------------------------------------------------------- #
24
25"""
26:class:`~wx.lib.agw.flatmenu.FlatMenu` is a generic menu implementation.
27
28
29Description
30===========
31
32:class:`FlatMenu`, like the name implies, it is a generic menu implementation.
33I tried to provide a full functionality for menus, menubar and toolbar.
34
35
36:class:`FlatMenu` supports the following features:
37
38- Fires all the events (UI & Cmd);
39- Check items;
40- Separators;
41- Enabled / Disabled menu items;
42- Images on items;
43- Toolbar support, with images and separators;
44- Controls in toolbar (work in progress);
45- Toolbar tools tooltips (done: thanks to Peter Kort);
46- Accelerators for menus;
47- Accelerators for menubar;
48- Radio items in menus;
49- Integration with AUI;
50- Scrolling when menu is too big to fit the screen;
51- Menu navigation with keyboard;
52- Drop down arrow button to the right of the menu, it always contains the
53  "Customize" option, which will popup an options dialog. The dialog has the
54  following abilities:
55
56  (a) Ability to add/remove menus;
57  (b) Select different colour schemes for the menu bar / toolbar;
58  (c) Control various options, such as: colour for highlight menu item, draw
59      border around menus (classic look only);
60  (d) Toolbar floating appearance.
61
62- Allows user to specify grey bitmap for disabled menus/toolbar tools;
63- If no grey bitmap is provided, it generates one from the existing bitmap;
64- Hidden toolbar items / menu bar items - will appear in a small popmenu
65  to the right if they are hidden;
66- 4 different colour schemes for the menu bar (more can easily added);
67- Scrolling is available if the menu height is greater than the screen height;
68- Context menus for menu items;
69- Show/hide the drop down arrow which allows the customization of :class:`FlatMenu`;
70- Multiple columns menu window;
71- Tooltips for menus and toolbar items on a :class:`StatusBar` (if present);
72- Transparency (alpha channel) for menu windows (for platforms supporting it);
73- FileHistory support through a pure-Python :class:`FileHistory` implementation;
74- Possibility to set a background bitmap for a :class:`FlatMenu`;
75- First attempt in adding controls to FlatToolbar;
76- Added a MiniBar (thanks to Vladiuz);
77- Added :class:`ToolBar` methods AddCheckTool/AddRadioTool (thanks to Vladiuz).
78
79
80Usage
81=====
82
83Usage example::
84
85    import wx
86    import wx.lib.agw.flatmenu as FM
87
88    class MyFrame(wx.Frame):
89
90        def __init__(self, parent):
91
92            wx.Frame.__init__(self, parent, -1, "FlatMenu Demo")
93
94            self.CreateMenu()
95
96            panel = wx.Panel(self, -1)
97            btn = wx.Button(panel, -1, "Hello", (15, 12), (100, 120))
98
99            main_sizer = wx.BoxSizer(wx.VERTICAL)
100            main_sizer.Add(self.menubar, 0, wx.EXPAND)
101            main_sizer.Add(panel, 1, wx.EXPAND)
102
103            self.SetSizer(main_sizer)
104            main_sizer.Layout()
105
106
107        def CreateMenu(self):
108
109            self.menubar = FM.FlatMenuBar(self, -1)
110
111            f_menu = FM.FlatMenu()
112            e_menu = FM.FlatMenu()
113            v_menu = FM.FlatMenu()
114            t_menu = FM.FlatMenu()
115            w_menu = FM.FlatMenu()
116
117            # Append the menu items to the menus
118            f_menu.Append(-1, "Simple\tCtrl+N", "Text", None)
119            e_menu.Append(-1, "FlatMenu", "Text", None)
120            v_menu.Append(-1, "Example", "Text", None)
121            t_menu.Append(-1, "Hello", "Text", None)
122            w_menu.Append(-1, "World", "Text", None)
123
124            # Append menus to the menubar
125            self.menubar.Append(f_menu, "&File")
126            self.menubar.Append(e_menu, "&Edit")
127            self.menubar.Append(v_menu, "&View")
128            self.menubar.Append(t_menu, "&Options")
129            self.menubar.Append(w_menu, "&Help")
130
131
132    # our normal wxApp-derived class, as usual
133
134    app = wx.App(0)
135
136    frame = MyFrame(None)
137    app.SetTopWindow(frame)
138    frame.Show()
139
140    app.MainLoop()
141
142
143
144Supported Platforms
145===================
146
147:class:`FlatMenu` has been tested on the following platforms:
148  * Windows (Windows XP, Vista);
149  * Linux Ubuntu (Dapper 6.06)
150
151
152
153Window Styles
154=============
155
156This class supports the following window styles:
157
158========================= =========== ==================================================
159Window Styles             Hex Value   Description
160========================= =========== ==================================================
161``FM_OPT_IS_LCD``                 0x1 Use this style if your computer uses a LCD screen.
162``FM_OPT_MINIBAR``                0x2 Use this if you plan to use the toolbar only.
163``FM_OPT_SHOW_CUSTOMIZE``         0x4 Show "customize link" in the `More` menu, you will need to write your own handler. See demo.
164``FM_OPT_SHOW_TOOLBAR``           0x8 Set this option is you are planning to use the toolbar.
165========================= =========== ==================================================
166
167
168Events Processing
169=================
170
171This class processes the following events:
172
173================================= ==================================================
174Event Name                        Description
175================================= ==================================================
176``EVT_FLAT_MENU_DISMISSED``       Used internally.
177``EVT_FLAT_MENU_ITEM_MOUSE_OUT``  Fires an event when the mouse leaves a :class:`FlatMenuItem`.
178``EVT_FLAT_MENU_ITEM_MOUSE_OVER`` Fires an event when the mouse enters a :class:`FlatMenuItem`.
179``EVT_FLAT_MENU_SELECTED``        Fires the :class:`EVT_MENU` event for :class:`FlatMenu`.
180================================= ==================================================
181
182
183License And Version
184===================
185
186:class:`FlatMenu` is distributed under the wxPython license.
187
188Latest Revision: Andrea Gavana @ 27 Dec 2012, 21.00 GMT
189
190Version 1.0
191
192"""
193
194__docformat__ = "epytext"
195__version__ = "1.0"
196
197import wx
198import os
199import math
200
201import wx.lib.colourutils as colourutils
202
203import six
204
205from .fmcustomizedlg import FMCustomizeDlg
206from .artmanager import ArtManager, DCSaver
207from .fmresources import *
208
209# FlatMenu styles
210FM_OPT_IS_LCD = 1
211""" Use this style if your computer uses a LCD screen. """
212FM_OPT_MINIBAR = 2
213""" Use this if you plan to use the toolbar only. """
214FM_OPT_SHOW_CUSTOMIZE = 4
215""" Show "customize link" in the `More` menu, you will need to write your own handler. See demo. """
216FM_OPT_SHOW_TOOLBAR = 8
217""" Set this option is you are planning to use the toolbar. """
218
219# Some checking to see if we can draw shadows behind the popup menus
220# at least on Windows. *REQUIRES* Mark Hammond's win32all extensions
221# and ctypes, on Windows obviouly. Mac and GTK have no shadows under
222# the menus, and it has been reported that shadows don't work well
223# on Windows 2000 and previous.
224
225_libimported = None
226_DELAY = 5000
227
228# Define a translation string
229_ = wx.GetTranslation
230
231if wx.Platform == "__WXMSW__":
232    osVersion = wx.GetOsVersion()
233    # Shadows behind menus are supported only in XP
234    if osVersion[1] == 5 and osVersion[2] == 1:
235        try:
236            import win32api
237            import win32gui
238            _libimported = "MH"
239        except:
240            try:
241                import ctypes
242                _libimported = "ctypes"
243            except:
244                pass
245    else:
246        _libimported = None
247
248# Simple hack, but I don't know how to make it work on Mac
249# I don't have  Mac ;-)
250#if wx.Platform == "__WXMAC__":
251#    try:
252#        import ctypes
253#        _carbon_dll = ctypes.cdll.LoadLibrary(r'/System/Frameworks/Carbon.framework/Carbon')
254#    except:
255#        _carbon_dll = None
256
257
258# FIXME: No way to get shadows on Windows with the original code...
259# May anyone share some suggestion on how to make it work??
260# Right now I am using win32api to create shadows behind wx.PopupWindow,
261# but this will result in *all* the popup windows in an application
262# to have shadows behind them, even the user defined wx.PopupWindow
263# that do not derive from FlatMenu.
264
265CPP_AUI = True
266
267try:
268    import wx.aui as AUI
269    AuiPaneInfo = AUI.AuiPaneInfo
270    """ Default AuiPaneInfo as in :class:`~wx.lib.agw.aui.AuiPaneInfo`. """
271except ImportError:
272    CPP_AUI = False
273
274try:
275    from . import aui as PyAUI
276    PyAuiPaneInfo = PyAUI.AuiPaneInfo
277    """ Default AuiPaneInfo as in :class:`framemanager`. """
278except ImportError:
279    pass
280
281# Check for the new method in 2.7 (not present in 2.6.3.3)
282if wx.VERSION_STRING < "2.7":
283    wx.Rect.Contains = lambda self, point: wx.Rect.Inside(self, point)
284
285
286wxEVT_FLAT_MENU_DISMISSED = wx.NewEventType()
287wxEVT_FLAT_MENU_SELECTED = wx.wxEVT_COMMAND_MENU_SELECTED
288wxEVT_FLAT_MENU_ITEM_MOUSE_OVER = wx.NewEventType()
289wxEVT_FLAT_MENU_ITEM_MOUSE_OUT = wx.NewEventType()
290
291EVT_FLAT_MENU_DISMISSED = wx.PyEventBinder(wxEVT_FLAT_MENU_DISMISSED, 1)
292""" Used internally. """
293EVT_FLAT_MENU_SELECTED = wx.PyEventBinder(wxEVT_FLAT_MENU_SELECTED, 2)
294""" Fires the wx.EVT_MENU event for :class:`FlatMenu`. """
295EVT_FLAT_MENU_RANGE = wx.PyEventBinder(wxEVT_FLAT_MENU_SELECTED, 2)
296""" Fires the wx.EVT_MENU event for a series of :class:`FlatMenu`. """
297EVT_FLAT_MENU_ITEM_MOUSE_OUT = wx.PyEventBinder(wxEVT_FLAT_MENU_ITEM_MOUSE_OUT, 1)
298""" Fires an event when the mouse leaves a :class:`FlatMenuItem`. """
299EVT_FLAT_MENU_ITEM_MOUSE_OVER = wx.PyEventBinder(wxEVT_FLAT_MENU_ITEM_MOUSE_OVER, 1)
300""" Fires an event when the mouse enters a :class:`FlatMenuItem`. """
301
302
303def GetAccelIndex(label):
304    """
305    Returns the mnemonic index of the label and the label stripped of the ampersand mnemonic
306    (e.g. 'lab&el' ==> will result in 3 and labelOnly = label).
307
308    :param string `label`: a string possibly containining an ampersand.
309    """
310
311    indexAccel = 0
312    while True:
313        indexAccel = label.find("&", indexAccel)
314        if indexAccel == -1:
315            return indexAccel, label
316        if label[indexAccel:indexAccel+2] == "&&":
317            label = label[0:indexAccel] + label[indexAccel+1:]
318            indexAccel += 1
319        else:
320            break
321
322    labelOnly = label[0:indexAccel] + label[indexAccel+1:]
323
324    return indexAccel, labelOnly
325
326
327def ConvertToMonochrome(bmp):
328    """
329    Converts a bitmap to monochrome colour.
330
331    :param `bmp`: a valid :class:`wx.Bitmap` object.
332    """
333
334    mem_dc = wx.MemoryDC()
335    shadow = wx.Bitmap(bmp.GetWidth(), bmp.GetHeight())
336    mem_dc.SelectObject(shadow)
337    mem_dc.DrawBitmap(bmp, 0, 0, True)
338    mem_dc.SelectObject(wx.NullBitmap)
339    img = shadow.ConvertToImage()
340    img = img.ConvertToMono(0, 0, 0)
341
342    # we now have black where the original bmp was drawn,
343    # white elsewhere
344    shadow = wx.Bitmap(img)
345    shadow.SetMask(wx.Mask(shadow, wx.BLACK))
346
347    # Convert the black to grey
348    tmp = wx.Bitmap(bmp.GetWidth(), bmp.GetHeight())
349    col = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW)
350    mem_dc.SelectObject(tmp)
351    mem_dc.SetPen(wx.Pen(col))
352    mem_dc.SetBrush(wx.Brush(col))
353    mem_dc.DrawRectangle(0, 0, bmp.GetWidth(), bmp.GetHeight())
354    mem_dc.DrawBitmap(shadow, 0, 0, True)   # now contains a bitmap with grey where the image was, white elsewhere
355    mem_dc.SelectObject(wx.NullBitmap)
356    shadow = tmp
357    shadow.SetMask(wx.Mask(shadow, wx.WHITE))
358
359    return shadow
360
361
362# ---------------------------------------------------------------------------- #
363# Class FMRendererMgr
364# ---------------------------------------------------------------------------- #
365
366class FMRendererMgr(object):
367    """
368    This class represents a manager that handles all the renderers defined.
369    Every instance of this class will share the same state, so everyone can
370    instantiate their own and a call to :meth:`FMRendererMgr.SetTheme() <FMRendererMgr.SetTheme>` anywhere will affect everyone.
371    """
372
373    def __new__(cls, *p, **k):
374        if not '_instance' in cls.__dict__:
375            cls._instance = object.__new__(cls)
376        return cls._instance
377
378
379    def __init__(self):
380        """ Default class constructor. """
381
382        # If we have already initialized don't do it again. There is only one
383        # FMRendererMgr process-wide.
384
385        if hasattr(self, '_alreadyInitialized'):
386            return
387
388        self._alreadyInitialized = True
389
390        self._currentTheme = StyleDefault
391        self._renderers = []
392        self._renderers.append(FMRenderer())
393        self._renderers.append(FMRendererXP())
394        self._renderers.append(FMRendererMSOffice2007())
395        self._renderers.append(FMRendererVista())
396
397
398    def GetRenderer(self):
399        """ Returns the current theme's renderer. """
400
401        return self._renderers[self._currentTheme]
402
403
404    def AddRenderer(self, renderer):
405        """
406        Adds a user defined custom renderer.
407
408        :param `renderer`: a class derived from :class:`FMRenderer`.
409        """
410
411        lastRenderer = len(self._renderers)
412        self._renderers.append(renderer)
413
414        return lastRenderer
415
416
417    def SetTheme(self, theme):
418        """
419        Sets the current theme.
420
421        :param `theme`: an integer specifying the theme to use.
422        """
423
424        if theme < 0 or theme > len(self._renderers):
425            raise ValueError("Error invalid theme specified.")
426
427        self._currentTheme = theme
428
429
430# ---------------------------------------------------------------------------- #
431# Class FMRenderer
432# ---------------------------------------------------------------------------- #
433
434class FMRenderer(object):
435    """
436    Base class for the :class:`FlatMenu` renderers. This class implements the common
437    methods of all the renderers.
438    """
439
440    def __init__(self):
441        """ Default class constructor. """
442
443        self.separatorHeight = 5
444        self.drawLeftMargin = False
445        self.highlightCheckAndRadio = False
446        self.scrollBarButtons = False   # Display scrollbar buttons if the menu doesn't fit on the screen
447                                        # otherwise default to up and down arrow menu items
448
449        self.itemTextColourDisabled = ArtManager.Get().LightColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT), 30)
450
451        # Background Colours
452        self.menuFaceColour     = wx.WHITE
453        self.menuBarFaceColour  = ArtManager.Get().LightColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE), 80)
454
455        self.menuBarFocusFaceColour     = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
456        self.menuBarFocusBorderColour   = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
457        self.menuBarPressedFaceColour   = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
458        self.menuBarPressedBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
459
460        self.menuFocusFaceColour     = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
461        self.menuFocusBorderColour   = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
462        self.menuPressedFaceColour   = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
463        self.menuPressedBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
464
465        self.buttonFaceColour          = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
466        self.buttonBorderColour        = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
467        self.buttonFocusFaceColour     = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
468        self.buttonFocusBorderColour   = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
469        self.buttonPressedFaceColour   = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
470        self.buttonPressedBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
471
472        # create wxBitmaps from the xpm's
473        self._rightBottomCorner = self.ConvertToBitmap(shadow_center_xpm, shadow_center_alpha)
474        self._bottom = self.ConvertToBitmap(shadow_bottom_xpm, shadow_bottom_alpha)
475        self._bottomLeft = self.ConvertToBitmap(shadow_bottom_left_xpm, shadow_bottom_left_alpha)
476        self._rightTop = self.ConvertToBitmap(shadow_right_top_xpm, shadow_right_top_alpha)
477        self._right = self.ConvertToBitmap(shadow_right_xpm, shadow_right_alpha)
478
479        self._bitmaps = {}
480        bmp = self.ConvertToBitmap(arrow_down, alpha=None)
481        bmp.SetMask(wx.Mask(bmp, wx.Colour(0, 128, 128)))
482        self._bitmaps.update({"arrow_down": bmp})
483
484        bmp = self.ConvertToBitmap(arrow_up, alpha=None)
485        bmp.SetMask(wx.Mask(bmp, wx.Colour(0, 128, 128)))
486        self._bitmaps.update({"arrow_up": bmp})
487
488        self._toolbarSeparatorBitmap = wx.NullBitmap
489        self.raiseToolbar = False
490
491
492    def SetMenuBarHighlightColour(self, colour):
493        """
494        Set the colour to highlight focus on the menu bar.
495
496        :param `colour`: a valid instance of :class:`wx.Colour`.
497        """
498
499        self.menuBarFocusFaceColour    = colour
500        self.menuBarFocusBorderColour  = colour
501        self.menuBarPressedFaceColour  = colour
502        self.menuBarPressedBorderColour= colour
503
504
505    def SetMenuHighlightColour(self,colour):
506        """
507        Set the colour to highlight focus on the menu.
508
509        :param `colour`: a valid instance of :class:`wx.Colour`.
510        """
511
512        self.menuFocusFaceColour    = colour
513        self.menuFocusBorderColour  = colour
514        self.menuPressedFaceColour     = colour
515        self.menuPressedBorderColour   = colour
516
517
518    def GetColoursAccordingToState(self, state):
519        """
520        Returns a :class:`wx.Colour` according to the menu item state.
521
522        :param integer `state`: one of the following bits:
523
524         ==================== ======= ==========================
525         Item State            Value  Description
526         ==================== ======= ==========================
527         ``ControlPressed``         0 The item is pressed
528         ``ControlFocus``           1 The item is focused
529         ``ControlDisabled``        2 The item is disabled
530         ``ControlNormal``          3 Normal state
531         ==================== ======= ==========================
532
533        """
534
535        # switch according to the status
536        if state == ControlFocus:
537            upperBoxTopPercent = 95
538            upperBoxBottomPercent = 50
539            lowerBoxTopPercent = 40
540            lowerBoxBottomPercent = 90
541            concaveUpperBox = True
542            concaveLowerBox = True
543
544        elif state == ControlPressed:
545            upperBoxTopPercent = 75
546            upperBoxBottomPercent = 90
547            lowerBoxTopPercent = 90
548            lowerBoxBottomPercent = 40
549            concaveUpperBox = True
550            concaveLowerBox = True
551
552        elif state == ControlDisabled:
553            upperBoxTopPercent = 100
554            upperBoxBottomPercent = 100
555            lowerBoxTopPercent = 70
556            lowerBoxBottomPercent = 70
557            concaveUpperBox = True
558            concaveLowerBox = True
559
560        else:
561            upperBoxTopPercent = 90
562            upperBoxBottomPercent = 50
563            lowerBoxTopPercent = 30
564            lowerBoxBottomPercent = 75
565            concaveUpperBox = True
566            concaveLowerBox = True
567
568        return upperBoxTopPercent, upperBoxBottomPercent, lowerBoxTopPercent, lowerBoxBottomPercent, \
569               concaveUpperBox, concaveLowerBox
570
571
572    def ConvertToBitmap(self, xpm, alpha=None):
573        """
574        Convert the given image to a bitmap, optionally overlaying an alpha
575        channel to it.
576
577        :param `xpm`: a list of strings formatted as XPM;
578        :param `alpha`: a list of alpha values, the same size as the xpm bitmap.
579        """
580
581        if alpha is not None:
582
583            img = wx.Bitmap(xpm)
584            img = img.ConvertToImage()
585            x, y = img.GetWidth(), img.GetHeight()
586            img.InitAlpha()
587            for jj in range(y):
588                for ii in range(x):
589                    img.SetAlpha(ii, jj, alpha[jj*x+ii])
590
591        else:
592
593            stream = six.BytesIO(xpm)
594            img = wx.Image(stream)
595
596        return wx.Bitmap(img)
597
598
599    def DrawLeftMargin(self, item, dc, menuRect):
600        """
601        Draws the menu left margin.
602
603        :param `item`: an instance of :class:`FlatMenuItem`;
604        :param `dc`: an instance of :class:`wx.DC`;
605        :param `menuRect`: an instance of :class:`wx.Rect`, representing the menu client rectangle.
606        """
607
608        raise Exception("This style doesn't support Drawing a Left Margin")
609
610
611    def DrawToolbarSeparator(self, dc, rect):
612        """
613        Draws a separator inside the toolbar in :class:`FlatMenuBar`.
614
615        :param `dc`: an instance of :class:`wx.DC`;
616        :param `rect`: an instance of :class:`wx.Rect`, representing the bitmap client rectangle.
617        """
618
619        # Place a separator bitmap
620        bmp = wx.Bitmap(rect.width, rect.height)
621        mem_dc = wx.MemoryDC()
622        mem_dc.SelectObject(bmp)
623        mem_dc.SetPen(wx.BLACK_PEN)
624        mem_dc.SetBrush(wx.BLACK_BRUSH)
625
626        mem_dc.DrawRectangle(0, 0, bmp.GetWidth(), bmp.GetHeight())
627
628        col = self.menuBarFaceColour
629        col1 = ArtManager.Get().LightColour(col, 40)
630        col2 = ArtManager.Get().LightColour(col, 70)
631
632        mem_dc.SetPen(wx.Pen(col2))
633        mem_dc.DrawLine(5, 0, 5, bmp.GetHeight())
634
635        mem_dc.SetPen(wx.Pen(col1))
636        mem_dc.DrawLine(6, 0, 6, bmp.GetHeight())
637
638        mem_dc.SelectObject(wx.NullBitmap)
639        bmp.SetMask(wx.Mask(bmp, wx.BLACK))
640
641        dc.DrawBitmap(bmp, rect.x, rect.y, True)
642
643
644    # assumption: the background was already drawn on the dc
645    def DrawBitmapShadow(self, dc, rect, where=BottomShadow|RightShadow):
646        """
647        Draws a shadow using background bitmap.
648
649        :param `dc`: an instance of :class:`wx.DC`;
650        :param `rect`: an instance of :class:`wx.Rect`, representing the bitmap client rectangle;
651        :param integer `where`: where to draw the shadow. This can be any combination of the
652         following bits:
653
654         ========================== ======= ================================
655         Shadow Settings             Value  Description
656         ========================== ======= ================================
657         ``RightShadow``                  1 Right side shadow
658         ``BottomShadow``                 2 Not full bottom shadow
659         ``BottomShadowFull``             4 Full bottom shadow
660         ========================== ======= ================================
661
662        """
663
664        shadowSize = 5
665
666        # the rect must be at least 5x5 pixles
667        if rect.height < 2*shadowSize or rect.width < 2*shadowSize:
668            return
669
670        # Start by drawing the right bottom corner
671        if where & BottomShadow or where & BottomShadowFull:
672            dc.DrawBitmap(self._rightBottomCorner, rect.x+rect.width, rect.y+rect.height, True)
673
674        # Draw right side shadow
675        xx = rect.x + rect.width
676        yy = rect.y + rect.height - shadowSize
677
678        if where & RightShadow:
679            while yy - rect.y > 2*shadowSize:
680                dc.DrawBitmap(self._right, xx, yy, True)
681                yy -= shadowSize
682
683            dc.DrawBitmap(self._rightTop, xx, yy - shadowSize, True)
684
685        if where & BottomShadow:
686            xx = rect.x + rect.width - shadowSize
687            yy = rect.height + rect.y
688            while xx - rect.x > 2*shadowSize:
689                dc.DrawBitmap(self._bottom, xx, yy, True)
690                xx -= shadowSize
691
692            dc.DrawBitmap(self._bottomLeft, xx - shadowSize, yy, True)
693
694        if where & BottomShadowFull:
695            xx = rect.x + rect.width - shadowSize
696            yy = rect.height + rect.y
697            while xx - rect.x >= 0:
698                dc.DrawBitmap(self._bottom, xx, yy, True)
699                xx -= shadowSize
700
701            dc.DrawBitmap(self._bottom, xx, yy, True)
702
703
704    def DrawToolBarBg(self, dc, rect):
705        """
706        Draws the toolbar background
707
708        :param `dc`: an instance of :class:`wx.DC`;
709        :param `rect`: an instance of :class:`wx.Rect`, representing the toolbar client rectangle.
710        """
711
712        if not self.raiseToolbar:
713            return
714
715        dcsaver = DCSaver(dc)
716
717        # fill with gradient
718        colour = self.menuBarFaceColour
719
720        dc.SetPen(wx.Pen(colour))
721        dc.SetBrush(wx.Brush(colour))
722
723        dc.DrawRectangle(rect)
724        self.DrawBitmapShadow(dc, rect)
725
726
727    def DrawSeparator(self, dc, xCoord, yCoord, textX, sepWidth):
728        """
729        Draws a separator inside a :class:`FlatMenu`.
730
731        :param `dc`: an instance of :class:`wx.DC`;
732        :param integer `xCoord`: the current x position where to draw the separator;
733        :param integer `yCoord`: the current y position where to draw the separator;
734        :param integer `textX`: the menu item label x position;
735        :param integer `sepWidth`: the width of the separator, in pixels.
736        """
737
738        dcsaver = DCSaver(dc)
739        sepRect1 = wx.Rect(xCoord + textX, yCoord + 1, sepWidth/2, 1)
740        sepRect2 = wx.Rect(xCoord + textX + sepWidth/2, yCoord + 1, sepWidth/2-1, 1)
741
742        artMgr = ArtManager.Get()
743        backColour = artMgr.GetMenuFaceColour()
744        lightColour = wx.Colour("LIGHT GREY")
745
746        artMgr.PaintStraightGradientBox(dc, sepRect1, backColour, lightColour, False)
747        artMgr.PaintStraightGradientBox(dc, sepRect2, lightColour, backColour, False)
748
749
750    def DrawMenuItem(self, item, dc, xCoord, yCoord, imageMarginX, markerMarginX, textX, rightMarginX, selected=False, backgroundImage=None):
751        """
752        Draws the menu item.
753
754        :param `item`: a :class:`FlatMenuItem` instance;
755        :param `dc`: an instance of :class:`wx.DC`;
756        :param integer `xCoord`: the current x position where to draw the menu;
757        :param integer `yCoord`: the current y position where to draw the menu;
758        :param integer `imageMarginX`: the spacing between the image and the menu border;
759        :param integer `markerMarginX`: the spacing between the checkbox/radio marker and
760         the menu border;
761        :param integer `textX`: the menu item label x position;
762        :param integer `rightMarginX`: the right margin between the text and the menu border;
763        :param bool `selected`: ``True`` if this menu item is currentl hovered by the mouse,
764         ``False`` otherwise.
765        :param `backgroundImage`: if not ``None``, an instance of :class:`wx.Bitmap` which will
766         become the background image for this :class:`FlatMenu`.
767        """
768
769        borderXSize = item._parentMenu.GetBorderXWidth()
770        itemHeight = item._parentMenu.GetItemHeight()
771        menuWidth  = item._parentMenu.GetMenuWidth()
772
773        # Define the item actual rectangle area
774        itemRect = wx.Rect(xCoord, yCoord, menuWidth, itemHeight)
775
776        # Define the drawing area
777        rect = wx.Rect(xCoord+2, yCoord, menuWidth - 4, itemHeight)
778
779        # Draw the background
780        backColour = self.menuFaceColour
781        penColour  = backColour
782        backBrush = wx.Brush(backColour)
783        leftMarginWidth = item._parentMenu.GetLeftMarginWidth()
784
785        if backgroundImage is None:
786            pen = wx.Pen(penColour)
787            dc.SetPen(pen)
788            dc.SetBrush(backBrush)
789            dc.DrawRectangle(rect)
790
791        # Draw the left margin gradient
792        if self.drawLeftMargin:
793            self.DrawLeftMargin(item, dc, itemRect)
794
795        # check if separator
796        if item.IsSeparator():
797            # Separator is a small grey line separating between menu items.
798            sepWidth = xCoord + menuWidth - textX - 1
799            self.DrawSeparator(dc, xCoord, yCoord, textX, sepWidth)
800            return
801
802        # Keep the item rect
803        item._rect = itemRect
804
805        # Get the bitmap base on the item state (disabled, selected ..)
806        bmp = item.GetSuitableBitmap(selected)
807
808        # First we draw the selection rectangle
809        if selected:
810            self.DrawMenuButton(dc, rect.Deflate(1,0), ControlFocus)
811            #copy.Inflate(0, menubar._spacer)
812
813        if bmp.IsOk():
814
815            # Calculate the postion to place the image
816            imgHeight = bmp.GetHeight()
817            imgWidth  = bmp.GetWidth()
818
819            if imageMarginX == 0:
820                xx = rect.x + (leftMarginWidth - imgWidth)/2
821            else:
822                xx = rect.x + ((leftMarginWidth - rect.height) - imgWidth)/2 + rect.height
823
824            yy = rect.y + (rect.height - imgHeight)/2
825            dc.DrawBitmap(bmp, xx, yy, True)
826
827        if item.GetKind() == wx.ITEM_CHECK:
828
829            # Checkable item
830            if item.IsChecked():
831
832                # Draw surrounding rectangle around the selection box
833                xx = rect.x + 1
834                yy = rect.y + 1
835                rr = wx.Rect(xx, yy, rect.height-2, rect.height-2)
836
837                if not selected and self.highlightCheckAndRadio:
838                    self.DrawButton(dc, rr, ControlFocus)
839
840                dc.DrawBitmap(item._checkMarkBmp, rr.x + (rr.width - 16)/2, rr.y + (rr.height - 16)/2, True)
841
842        if item.GetKind() == wx.ITEM_RADIO:
843
844            # Checkable item
845            if item.IsChecked():
846
847                # Draw surrounding rectangle around the selection box
848                xx = rect.x + 1
849                yy = rect.y + 1
850                rr = wx.Rect(xx, yy, rect.height-2, rect.height-2)
851
852                if not selected and self.highlightCheckAndRadio:
853                    self.DrawButton(dc, rr, ControlFocus)
854
855                dc.DrawBitmap(item._radioMarkBmp, rr.x + (rr.width - 16)/2, rr.y + (rr.height - 16)/2, True)
856
857        # Draw text - without accelerators
858        text = item.GetLabel()
859
860        if text:
861
862            font = item.GetFont()
863            if font is None:
864                font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
865
866            if selected:
867                enabledTxtColour = colourutils.BestLabelColour(self.menuFocusFaceColour, bw=True)
868            else:
869                enabledTxtColour = colourutils.BestLabelColour(self.menuFaceColour, bw=True)
870
871            disabledTxtColour = self.itemTextColourDisabled
872            textColour = (item.IsEnabled() and [enabledTxtColour] or [disabledTxtColour])[0]
873
874            if item.IsEnabled() and item.GetTextColour():
875                textColour = item.GetTextColour()
876
877            dc.SetFont(font)
878            w, h = dc.GetTextExtent(text)
879            dc.SetTextForeground(textColour)
880
881            if item._mnemonicIdx != wx.NOT_FOUND:
882
883                # We divide the drawing to 3 parts
884                text1 = text[0:item._mnemonicIdx]
885                text2 = text[item._mnemonicIdx]
886                text3 = text[item._mnemonicIdx+1:]
887
888                w1, dummy = dc.GetTextExtent(text1)
889                w2, dummy = dc.GetTextExtent(text2)
890                w3, dummy = dc.GetTextExtent(text3)
891
892                posx = xCoord + textX + borderXSize
893                posy = (itemHeight - h)/2 + yCoord
894
895                # Draw first part
896                dc.DrawText(text1, posx, posy)
897
898                # mnemonic
899                if "__WXGTK__" not in wx.Platform:
900                    font.SetUnderlined(True)
901                    dc.SetFont(font)
902
903                posx += w1
904                dc.DrawText(text2, posx, posy)
905
906                # last part
907                font.SetUnderlined(False)
908                dc.SetFont(font)
909                posx += w2
910                dc.DrawText(text3, posx, posy)
911
912            else:
913
914                w, h = dc.GetTextExtent(text)
915                dc.DrawText(text, xCoord + textX + borderXSize, (itemHeight - h)/2 + yCoord)
916
917
918        # Now draw accelerator
919        # Accelerators are aligned to the right
920        if item.GetAccelString():
921
922            accelWidth, accelHeight = dc.GetTextExtent(item.GetAccelString())
923            dc.DrawText(item.GetAccelString(), xCoord + rightMarginX - accelWidth, (itemHeight - accelHeight)/2 + yCoord)
924
925        # Check if this item has sub-menu - if it does, draw
926        # right arrow on the right margin
927        if item.GetSubMenu():
928
929            # Draw arrow
930            rightArrowBmp = wx.Bitmap(menu_right_arrow_xpm)
931            rightArrowBmp.SetMask(wx.Mask(rightArrowBmp, wx.WHITE))
932
933            xx = xCoord + rightMarginX + borderXSize
934            rr = wx.Rect(xx, rect.y + 1, rect.height-2, rect.height-2)
935            dc.DrawBitmap(rightArrowBmp, rr.x + 4, rr.y +(rr.height-16)/2, True)
936
937
938    def DrawMenuBarButton(self, dc, rect, state):
939        """
940        Draws the highlight on a :class:`FlatMenuBar`.
941
942        :param `dc`: an instance of :class:`wx.DC`;
943        :param `rect`: an instance of :class:`wx.Rect`, representing the button client rectangle;
944        :param integer `state`: the button state.
945        """
946
947        # switch according to the status
948        if state == ControlFocus:
949            penColour   = self.menuBarFocusBorderColour
950            brushColour = self.menuBarFocusFaceColour
951        elif state == ControlPressed:
952            penColour   = self.menuBarPressedBorderColour
953            brushColour = self.menuBarPressedFaceColour
954
955        dcsaver = DCSaver(dc)
956        dc.SetPen(wx.Pen(penColour))
957        dc.SetBrush(wx.Brush(brushColour))
958        dc.DrawRectangle(rect)
959
960
961    def DrawMenuButton(self, dc, rect, state):
962        """
963        Draws the highlight on a FlatMenu
964
965        :param `dc`: an instance of :class:`wx.DC`;
966        :param `rect`: an instance of :class:`wx.Rect`, representing the button client rectangle;
967        :param integer `state`: the button state.
968        """
969
970        # switch according to the status
971        if state == ControlFocus:
972            penColour   = self.menuFocusBorderColour
973            brushColour = self.menuFocusFaceColour
974        elif state == ControlPressed:
975            penColour   = self.menuPressedBorderColour
976            brushColour = self.menuPressedFaceColour
977
978        dcsaver = DCSaver(dc)
979        dc.SetPen(wx.Pen(penColour))
980        dc.SetBrush(wx.Brush(brushColour))
981        dc.DrawRectangle(rect)
982
983
984    def DrawScrollButton(self, dc, rect, state):
985        """
986        Draws the scroll button
987
988        :param `dc`: an instance of :class:`wx.DC`;
989        :param `rect`: an instance of :class:`wx.Rect`, representing the button client rectangle;
990        :param integer `state`: the button state.
991        """
992
993        if not self.scrollBarButtons:
994            return
995
996        colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
997        colour = ArtManager.Get().LightColour(colour, 30)
998
999        artMgr = ArtManager.Get()
1000
1001        # Keep old pen and brush
1002        dcsaver = DCSaver(dc)
1003
1004        # Define the rounded rectangle base on the given rect
1005        # we need an array of 9 points for it
1006        baseColour = colour
1007
1008        # Define the middle points
1009        leftPt = wx.Point(rect.x, rect.y + (rect.height / 2))
1010        rightPt = wx.Point(rect.x + rect.width-1, rect.y + (rect.height / 2))
1011
1012        # Define the top region
1013        top = wx.Rect((rect.GetLeft(), rect.GetTop()), rightPt)
1014        bottom = wx.Rect(leftPt, (rect.GetRight(), rect.GetBottom()))
1015
1016        upperBoxTopPercent, upperBoxBottomPercent, lowerBoxTopPercent, lowerBoxBottomPercent, \
1017                            concaveUpperBox, concaveLowerBox = self.GetColoursAccordingToState(state)
1018
1019        topStartColour = artMgr.LightColour(baseColour, upperBoxTopPercent)
1020        topEndColour = artMgr.LightColour(baseColour, upperBoxBottomPercent)
1021        bottomStartColour = artMgr.LightColour(baseColour, lowerBoxTopPercent)
1022        bottomEndColour = artMgr.LightColour(baseColour, lowerBoxBottomPercent)
1023
1024        artMgr.PaintStraightGradientBox(dc, top, topStartColour, topEndColour)
1025        artMgr.PaintStraightGradientBox(dc, bottom, bottomStartColour, bottomEndColour)
1026
1027        rr = wx.Rect(rect.x, rect.y, rect.width, rect.height)
1028        dc.SetBrush(wx.TRANSPARENT_BRUSH)
1029
1030        frameColour = artMgr.LightColour(baseColour, 60)
1031        dc.SetPen(wx.Pen(frameColour))
1032        dc.DrawRectangle(rr)
1033
1034        wc = artMgr.LightColour(baseColour, 80)
1035        dc.SetPen(wx.Pen(wc))
1036        rr.Deflate(1, 1)
1037        dc.DrawRectangle(rr)
1038
1039
1040    def DrawButton(self, dc, rect, state, colour=None):
1041        """
1042        Draws a button.
1043
1044        :param `dc`: an instance of :class:`wx.DC`;
1045        :param `rect`: an instance of :class:`wx.Rect`, representing the button client rectangle;
1046        :param integer `state`: the button state;
1047        :param `colour`: if not ``None``, an instance of :class:`wx.Colour` to be used to draw
1048         the :class:`FlatMenuItem` background.
1049        """
1050
1051        # switch according to the status
1052        if state == ControlFocus:
1053            if colour == None:
1054                penColour   = self.buttonFocusBorderColour
1055                brushColour = self.buttonFocusFaceColour
1056            else:
1057                penColour   = colour
1058                brushColour = ArtManager.Get().LightColour(colour, 75)
1059
1060        elif state == ControlPressed:
1061            if colour == None:
1062                penColour   = self.buttonPressedBorderColour
1063                brushColour = self.buttonPressedFaceColour
1064            else:
1065                penColour   = colour
1066                brushColour = ArtManager.Get().LightColour(colour, 60)
1067        else:
1068            if colour == None:
1069                penColour   = self.buttonBorderColour
1070                brushColour = self.buttonFaceColour
1071            else:
1072                penColour   = colour
1073                brushColour = ArtManager.Get().LightColour(colour, 75)
1074
1075        dcsaver = DCSaver(dc)
1076        dc.SetPen(wx.Pen(penColour))
1077        dc.SetBrush(wx.Brush(brushColour))
1078        dc.DrawRectangle(rect)
1079
1080
1081    def DrawMenuBarBackground(self, dc, rect):
1082        """
1083        Draws the menu bar background colour according to the menubar.GetBackgroundColour
1084
1085        :param `dc`: an instance of :class:`wx.DC`;
1086        :param `rect`: an instance of :class:`wx.Rect`, representing the menubar client rectangle.
1087        """
1088
1089        dcsaver = DCSaver(dc)
1090
1091        # fill with gradient
1092        colour = self.menuBarFaceColour
1093
1094        dc.SetPen(wx.Pen(colour))
1095        dc.SetBrush(wx.Brush(colour))
1096        dc.DrawRectangle(rect)
1097
1098
1099    def DrawMenuBar(self, menubar, dc):
1100        """
1101        Draws everything for :class:`FlatMenuBar`.
1102
1103        :param `menubar`: an instance of :class:`FlatMenuBar`.
1104        :param `dc`: an instance of :class:`wx.DC`.
1105        """
1106
1107        #artMgr = ArtManager.Get()
1108        fnt = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
1109        textColour = colourutils.BestLabelColour(menubar.GetBackgroundColour(), bw=True)
1110        highlightTextColour = colourutils.BestLabelColour(self.menuBarFocusFaceColour, bw=True)
1111
1112        dc.SetFont(fnt)
1113        dc.SetTextForeground(textColour)
1114
1115        clientRect = menubar.GetClientRect()
1116
1117        self.DrawMenuBarBackground(dc, clientRect)
1118
1119        padding, dummy = dc.GetTextExtent("W")
1120
1121        posx = 0
1122        posy = menubar._margin
1123
1124        # ---------------------------------------------------------------------------
1125        # Draw as much items as we can if the screen is not wide enough, add all
1126        # missing items to a drop down menu
1127        # ---------------------------------------------------------------------------
1128        menuBarRect = menubar.GetClientRect()
1129
1130        # mark all items as non-visibles at first
1131        for item in menubar._items:
1132            item.SetRect(wx.Rect())
1133
1134        for item in menubar._items:
1135
1136            # Handle accelerator ('&')
1137            title = item.GetTitle()
1138
1139            fixedText = title
1140            location, labelOnly = GetAccelIndex(fixedText)
1141
1142            # Get the menu item rect
1143            textWidth, textHeight = dc.GetTextExtent(fixedText)
1144            #rect = wx.Rect(posx+menubar._spacer/2, posy, textWidth, textHeight)
1145            rect = wx.Rect(posx+padding/2, posy, textWidth, textHeight)
1146
1147            # Can we draw more??
1148            # the +DROP_DOWN_ARROW_WIDTH  is the width of the drop down arrow
1149            if posx + rect.width + DROP_DOWN_ARROW_WIDTH >= menuBarRect.width:
1150                break
1151
1152            # In this style the button highlight includes the menubar margin
1153            button_rect = wx.Rect(*rect)
1154            button_rect.height = menubar._menuBarHeight
1155            #button_rect.width = rect.width + menubar._spacer
1156            button_rect.width = rect.width + padding
1157            button_rect.x = posx
1158            button_rect.y = 0
1159
1160            # Keep the item rectangle, will be used later in functions such
1161            # as 'OnLeftDown', 'OnMouseMove'
1162            copy = wx.Rect(*button_rect)
1163            #copy.Inflate(0, menubar._spacer)
1164            item.SetRect(copy)
1165
1166            selected = False
1167            if item.GetState() == ControlFocus:
1168                self.DrawMenuBarButton(dc, button_rect, ControlFocus)
1169                dc.SetTextForeground(highlightTextColour)
1170                selected = True
1171            else:
1172                dc.SetTextForeground(textColour)
1173
1174            ww, hh = dc.GetTextExtent(labelOnly)
1175            textOffset = (rect.width - ww) / 2
1176
1177            if not menubar._isLCD and item.GetTextBitmap().IsOk() and not selected:
1178                dc.DrawBitmap(item.GetTextBitmap(), rect.x, rect.y, True)
1179            elif not menubar._isLCD and item.GetSelectedTextBitmap().IsOk() and selected:
1180                dc.DrawBitmap(item.GetSelectedTextBitmap(), rect.x, rect.y, True)
1181            else:
1182                if not menubar._isLCD:
1183                    # Draw the text on a bitmap using memory dc,
1184                    # so on following calls we will use this bitmap instead
1185                    # of calculating everything from scratch
1186                    bmp = wx.Bitmap(rect.width, rect.height)
1187                    memDc = wx.MemoryDC()
1188                    memDc.SelectObject(bmp)
1189                    if selected:
1190                        memDc.SetTextForeground(highlightTextColour)
1191                    else:
1192                        memDc.SetTextForeground(textColour)
1193
1194                    # Fill the bitmap with the masking colour
1195                    memDc.SetPen(wx.Pen(wx.Colour(255, 0, 0)) )
1196                    memDc.SetBrush(wx.Brush(wx.Colour(255, 0, 0)) )
1197                    memDc.DrawRectangle(0, 0, rect.width, rect.height)
1198                    memDc.SetFont(fnt)
1199
1200                if location == wx.NOT_FOUND or location >= len(fixedText):
1201                    # draw the text
1202                    if not menubar._isLCD:
1203                        memDc.DrawText(title, textOffset, 0)
1204                    dc.DrawText(title, rect.x + textOffset, rect.y)
1205
1206                else:
1207
1208                    # underline the first '&'
1209                    before = labelOnly[0:location]
1210                    underlineLetter = labelOnly[location]
1211                    after = labelOnly[location+1:]
1212
1213                    # before
1214                    if not menubar._isLCD:
1215                        memDc.DrawText(before, textOffset, 0)
1216                    dc.DrawText(before, rect.x + textOffset, rect.y)
1217
1218                    # underlineLetter
1219                    if "__WXGTK__" not in wx.Platform:
1220                        w1, h = dc.GetTextExtent(before)
1221                        fnt.SetUnderlined(True)
1222                        dc.SetFont(fnt)
1223                        dc.DrawText(underlineLetter, rect.x + w1 + textOffset, rect.y)
1224                        if not menubar._isLCD:
1225                            memDc.SetFont(fnt)
1226                            memDc.DrawText(underlineLetter, textOffset + w1, 0)
1227
1228                    else:
1229                        w1, h = dc.GetTextExtent(before)
1230                        dc.DrawText(underlineLetter, rect.x + w1 + textOffset, rect.y)
1231                        if not menubar._isLCD:
1232                            memDc.DrawText(underlineLetter, textOffset + w1, 0)
1233
1234                        # Draw the underline ourselves since using the Underline in GTK,
1235                        # causes the line to be too close to the letter
1236
1237                        uderlineLetterW, uderlineLetterH = dc.GetTextExtent(underlineLetter)
1238                        dc.DrawLine(rect.x + w1 + textOffset, rect.y + uderlineLetterH - 2,
1239                                    rect.x + w1 + textOffset + uderlineLetterW, rect.y + uderlineLetterH - 2)
1240
1241                    # after
1242                    w2, h = dc.GetTextExtent(underlineLetter)
1243                    fnt.SetUnderlined(False)
1244                    dc.SetFont(fnt)
1245                    dc.DrawText(after, rect.x + w1 + w2 + textOffset, rect.y)
1246                    if not menubar._isLCD:
1247                        memDc.SetFont(fnt)
1248                        memDc.DrawText(after,  w1 + w2 + textOffset, 0)
1249
1250                    if not menubar._isLCD:
1251                        memDc.SelectObject(wx.NullBitmap)
1252                        # Set masking colour to the bitmap
1253                        bmp.SetMask(wx.Mask(bmp, wx.Colour(255, 0, 0)))
1254                        if selected:
1255                            item.SetSelectedTextBitmap(bmp)
1256                        else:
1257                            item.SetTextBitmap(bmp)
1258
1259            posx += rect.width + padding # + menubar._spacer
1260
1261        # Get a background image of the more menu button
1262        moreMenubtnBgBmpRect = wx.Rect(*menubar.GetMoreMenuButtonRect())
1263        if not menubar._moreMenuBgBmp:
1264            menubar._moreMenuBgBmp = wx.Bitmap(moreMenubtnBgBmpRect.width, moreMenubtnBgBmpRect.height)
1265
1266        if menubar._showToolbar and len(menubar._tbButtons) > 0:
1267            rectX      = 0
1268            rectWidth  = clientRect.width - moreMenubtnBgBmpRect.width
1269            if len(menubar._items) == 0:
1270                rectHeight = clientRect.height
1271                rectY      = 0
1272            else:
1273                rectHeight = clientRect.height - menubar._menuBarHeight
1274                rectY      = menubar._menuBarHeight
1275            rr = wx.Rect(rectX, rectY, rectWidth, rectHeight)
1276            self.DrawToolBarBg(dc, rr)
1277            menubar.DrawToolbar(dc, rr)
1278
1279        if menubar._showCustomize or menubar.GetInvisibleMenuItemCount() > 0 or  menubar.GetInvisibleToolbarItemCount() > 0:
1280            memDc = wx.MemoryDC()
1281            memDc.SelectObject(menubar._moreMenuBgBmp)
1282            try:
1283                memDc.Blit(0, 0, menubar._moreMenuBgBmp.GetWidth(), menubar._moreMenuBgBmp.GetHeight(), dc,
1284                           moreMenubtnBgBmpRect.x, moreMenubtnBgBmpRect.y)
1285            except:
1286                pass
1287            memDc.SelectObject(wx.NullBitmap)
1288
1289            # Draw the drop down arrow button
1290            menubar.DrawMoreButton(dc, menubar._dropDownButtonState)
1291            # Set the button rect
1292            menubar._dropDownButtonArea = moreMenubtnBgBmpRect
1293
1294
1295    def DrawMenu(self, flatmenu, dc):
1296        """
1297        Draws the menu.
1298
1299        :param `flatmenu`: the :class:`FlatMenu` instance we need to paint;
1300        :param `dc`: an instance of :class:`wx.DC`.
1301        """
1302
1303        menuRect = flatmenu.GetClientRect()
1304        menuBmp = wx.Bitmap(menuRect.width, menuRect.height)
1305
1306        mem_dc = wx.MemoryDC()
1307        mem_dc.SelectObject(menuBmp)
1308
1309        # colour the menu face with background colour
1310        backColour = self.menuFaceColour
1311        penColour  = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW)
1312
1313        backBrush = wx.Brush(backColour)
1314        pen = wx.Pen(penColour)
1315
1316        mem_dc.SetPen(pen)
1317        mem_dc.SetBrush(backBrush)
1318        mem_dc.DrawRectangle(menuRect)
1319
1320        backgroundImage = flatmenu._backgroundImage
1321
1322        if backgroundImage:
1323            mem_dc.DrawBitmap(backgroundImage, flatmenu._leftMarginWidth, 0, True)
1324
1325        # draw items
1326        posy = 3
1327        nItems = len(flatmenu._itemsArr)
1328
1329        # make all items as non-visible first
1330        for item in flatmenu._itemsArr:
1331            item.Show(False)
1332
1333        visibleItems = 0
1334        screenHeight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
1335
1336        numCols = flatmenu.GetNumberColumns()
1337        switch, posx, index = 1e6, 0, 0
1338        if numCols > 1:
1339            switch = int(math.ceil((nItems - flatmenu._first)/float(numCols)))
1340
1341        # If we have to scroll and are not using the scroll bar buttons we need to draw
1342        # the scroll up menu item at the top.
1343        if not self.scrollBarButtons and flatmenu._showScrollButtons:
1344            posy += flatmenu.GetItemHeight()
1345
1346        for nCount in range(flatmenu._first, nItems):
1347
1348            visibleItems += 1
1349            item = flatmenu._itemsArr[nCount]
1350            self.DrawMenuItem(item, mem_dc, posx, posy,
1351                              flatmenu._imgMarginX, flatmenu._markerMarginX,
1352                              flatmenu._textX, flatmenu._rightMarginPosX,
1353                              nCount == flatmenu._selectedItem,
1354                              backgroundImage=backgroundImage)
1355            posy += item.GetHeight()
1356            item.Show()
1357
1358            if visibleItems >= switch:
1359                posy = 2
1360                index += 1
1361                posx = flatmenu._menuWidth*index
1362                visibleItems = 0
1363
1364            # make sure we draw only visible items
1365            pp = flatmenu.ClientToScreen(wx.Point(0, posy))
1366
1367            menuBottom = (self.scrollBarButtons and [pp.y] or [pp.y + flatmenu.GetItemHeight()*2])[0]
1368
1369            if menuBottom > screenHeight:
1370                break
1371
1372        if flatmenu._showScrollButtons:
1373            if flatmenu._upButton:
1374                flatmenu._upButton.Draw(mem_dc)
1375            if flatmenu._downButton:
1376                flatmenu._downButton.Draw(mem_dc)
1377
1378        dc.Blit(0, 0, menuBmp.GetWidth(), menuBmp.GetHeight(), mem_dc, 0, 0)
1379
1380
1381# ---------------------------------------------------------------------------- #
1382# Class FMRendererMSOffice2007
1383# ---------------------------------------------------------------------------- #
1384
1385class FMRendererMSOffice2007(FMRenderer):
1386    """ Windows Office 2007 style. """
1387
1388    def __init__(self):
1389        """ Default class constructor. """
1390
1391        FMRenderer.__init__(self)
1392
1393        self.drawLeftMargin = True
1394        self.separatorHeight = 3
1395        self.highlightCheckAndRadio = True
1396        self.scrollBarButtons = True   # Display scrollbar buttons if the menu doesn't fit on the screen
1397
1398        self.menuBarFaceColour   = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE)
1399
1400        self.buttonBorderColour        = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
1401        self.buttonFaceColour          = ArtManager.Get().LightColour(self.buttonBorderColour, 75)
1402        self.buttonFocusBorderColour   = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
1403        self.buttonFocusFaceColour     = ArtManager.Get().LightColour(self.buttonFocusBorderColour, 75)
1404        self.buttonPressedBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
1405        self.buttonPressedFaceColour   = ArtManager.Get().LightColour(self.buttonPressedBorderColour, 60)
1406
1407        self.menuFocusBorderColour   = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
1408        self.menuFocusFaceColour     = ArtManager.Get().LightColour(self.buttonFocusBorderColour, 75)
1409        self.menuPressedBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
1410        self.menuPressedFaceColour   = ArtManager.Get().LightColour(self.buttonPressedBorderColour, 60)
1411
1412        self.menuBarFocusBorderColour   = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
1413        self.menuBarFocusFaceColour     = ArtManager.Get().LightColour(self.buttonFocusBorderColour, 75)
1414        self.menuBarPressedBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
1415        self.menuBarPressedFaceColour   = ArtManager.Get().LightColour(self.buttonPressedBorderColour, 60)
1416
1417
1418    def DrawLeftMargin(self, item, dc, menuRect):
1419        """
1420        Draws the menu left margin.
1421
1422        :param `item`: the :class:`FlatMenuItem` to paint;
1423        :param `dc`: an instance of :class:`wx.DC`;
1424        :param `menuRect`: an instance of :class:`wx.Rect`, representing the menu client rectangle.
1425        """
1426
1427        # Construct the margin rectangle
1428        marginRect = wx.Rect(menuRect.x+1, menuRect.y, item._parentMenu.GetLeftMarginWidth(), menuRect.height)
1429
1430        # Set the gradient colours
1431        artMgr = ArtManager.Get()
1432        faceColour = self.menuFaceColour
1433
1434        dcsaver = DCSaver(dc)
1435        marginColour = artMgr.DarkColour(faceColour, 5)
1436        dc.SetPen(wx.Pen(marginColour))
1437        dc.SetBrush(wx.Brush(marginColour))
1438        dc.DrawRectangle(marginRect)
1439
1440        dc.SetPen(wx.WHITE_PEN)
1441        dc.DrawLine(marginRect.x + marginRect.width, marginRect.y, marginRect.x + marginRect.width, marginRect.y + marginRect.height)
1442
1443        borderColour = artMgr.DarkColour(faceColour, 10)
1444        dc.SetPen(wx.Pen(borderColour))
1445        dc.DrawLine(marginRect.x + marginRect.width-1, marginRect.y, marginRect.x + marginRect.width-1, marginRect.y + marginRect.height)
1446
1447
1448    def DrawMenuButton(self, dc, rect, state):
1449        """
1450        Draws the highlight on a :class:`FlatMenu`.
1451
1452        :param `dc`: an instance of :class:`wx.DC`;
1453        :param `rect`: an instance of :class:`wx.Rect`, representing the button client rectangle;
1454        :param integer `state`: the button state.
1455        """
1456
1457        self.DrawButton(dc, rect, state)
1458
1459
1460    def DrawMenuBarButton(self, dc, rect, state):
1461        """
1462        Draws the highlight on a :class:`FlatMenuBar`.
1463
1464        :param `dc`: an instance of :class:`wx.DC`;
1465        :param `rect`: an instance of :class:`wx.Rect`, representing the button client rectangle;
1466        :param integer `state`: the button state.
1467        """
1468
1469        self.DrawButton(dc, rect, state)
1470
1471
1472    def DrawButton(self, dc, rect, state, colour=None):
1473        """
1474        Draws a button using the Office 2007 theme.
1475
1476        :param `dc`: an instance of :class:`wx.DC`;
1477        :param `rect`: an instance of :class:`wx.Rect`, representing the button client rectangle;
1478        :param integer `state`: the button state;
1479        :param `colour`: if not ``None``, an instance of :class:`wx.Colour` to be used to draw
1480         the :class:`FlatMenuItem` background.
1481        """
1482
1483        colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
1484        colour = ArtManager.Get().LightColour(colour, 30)
1485        self.DrawButtonColour(dc, rect, state, colour)
1486
1487
1488    def DrawButtonColour(self, dc, rect, state, colour):
1489        """
1490        Draws a button using the Office 2007 theme.
1491
1492        :param `dc`: an instance of :class:`wx.DC`;
1493        :param `rect`: an instance of :class:`wx.Rect`, representing the button client rectangle;
1494        :param integer `state`: the button state;
1495        :param `colour`: a valid :class:`wx.Colour` instance.
1496        """
1497
1498        artMgr = ArtManager.Get()
1499
1500        # Keep old pen and brush
1501        dcsaver = DCSaver(dc)
1502
1503        # Define the rounded rectangle base on the given rect
1504        # we need an array of 9 points for it
1505        baseColour = colour
1506
1507        # Define the middle points
1508        leftPt = wx.Point(rect.x, rect.y + (rect.height / 2))
1509        rightPt = wx.Point(rect.x + rect.width-1, rect.y + (rect.height / 2))
1510
1511        # Define the top region
1512        top = wx.Rect((rect.GetLeft(), rect.GetTop()), rightPt)
1513        bottom = wx.Rect(leftPt, (rect.GetRight(), rect.GetBottom()))
1514
1515        upperBoxTopPercent, upperBoxBottomPercent, lowerBoxTopPercent, lowerBoxBottomPercent, \
1516                            concaveUpperBox, concaveLowerBox = self.GetColoursAccordingToState(state)
1517
1518        topStartColour = artMgr.LightColour(baseColour, upperBoxTopPercent)
1519        topEndColour = artMgr.LightColour(baseColour, upperBoxBottomPercent)
1520        bottomStartColour = artMgr.LightColour(baseColour, lowerBoxTopPercent)
1521        bottomEndColour = artMgr.LightColour(baseColour, lowerBoxBottomPercent)
1522
1523        artMgr.PaintStraightGradientBox(dc, top, topStartColour, topEndColour)
1524        artMgr.PaintStraightGradientBox(dc, bottom, bottomStartColour, bottomEndColour)
1525
1526        rr = wx.Rect(rect.x, rect.y, rect.width, rect.height)
1527        dc.SetBrush(wx.TRANSPARENT_BRUSH)
1528
1529        frameColour = artMgr.LightColour(baseColour, 60)
1530        dc.SetPen(wx.Pen(frameColour))
1531        dc.DrawRectangle(rr)
1532
1533        wc = artMgr.LightColour(baseColour, 80)
1534        dc.SetPen(wx.Pen(wc))
1535        rr.Deflate(1, 1)
1536        dc.DrawRectangle(rr)
1537
1538
1539    def DrawMenuBarBackground(self, dc, rect):
1540        """
1541        Draws the menu bar background according to the active theme.
1542
1543        :param `dc`: an instance of :class:`wx.DC`;
1544        :param `rect`: an instance of :class:`wx.Rect`, representing the menubar client rectangle.
1545        """
1546
1547        # Keep old pen and brush
1548        dcsaver = DCSaver(dc)
1549        artMgr = ArtManager.Get()
1550        baseColour = self.menuBarFaceColour
1551
1552        dc.SetBrush(wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE)))
1553        dc.SetPen(wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE)))
1554        dc.DrawRectangle(rect)
1555
1556        # Define the rounded rectangle base on the given rect
1557        # we need an array of 9 points for it
1558        regPts = [wx.Point() for ii in range(9)]
1559        radius = 2
1560
1561        regPts[0] = wx.Point(rect.x, rect.y + radius)
1562        regPts[1] = wx.Point(rect.x+radius, rect.y)
1563        regPts[2] = wx.Point(rect.x+rect.width-radius-1, rect.y)
1564        regPts[3] = wx.Point(rect.x+rect.width-1, rect.y + radius)
1565        regPts[4] = wx.Point(rect.x+rect.width-1, rect.y + rect.height - radius - 1)
1566        regPts[5] = wx.Point(rect.x+rect.width-radius-1, rect.y + rect.height-1)
1567        regPts[6] = wx.Point(rect.x+radius, rect.y + rect.height-1)
1568        regPts[7] = wx.Point(rect.x, rect.y + rect.height - radius - 1)
1569        regPts[8] = regPts[0]
1570
1571        # Define the middle points
1572
1573        factor = artMgr.GetMenuBgFactor()
1574
1575        leftPt1 = wx.Point(rect.x, rect.y + (rect.height / factor))
1576        leftPt2 = wx.Point(rect.x, rect.y + (rect.height / factor)*(factor-1))
1577
1578        rightPt1 = wx.Point(rect.x + rect.width, rect.y + (rect.height / factor))
1579        rightPt2 = wx.Point(rect.x + rect.width, rect.y + (rect.height / factor)*(factor-1))
1580
1581        # Define the top region
1582        topReg = [wx.Point() for ii in range(7)]
1583        topReg[0] = regPts[0]
1584        topReg[1] = regPts[1]
1585        topReg[2] = wx.Point(regPts[2].x+1, regPts[2].y)
1586        topReg[3] = wx.Point(regPts[3].x + 1, regPts[3].y)
1587        topReg[4] = wx.Point(rightPt1.x, rightPt1.y+1)
1588        topReg[5] = wx.Point(leftPt1.x, leftPt1.y+1)
1589        topReg[6] = topReg[0]
1590
1591        # Define the middle region
1592        middle = wx.Rect(leftPt1, wx.Point(rightPt2.x - 2, rightPt2.y))
1593
1594        # Define the bottom region
1595        bottom = wx.Rect(leftPt2, wx.Point(rect.GetRight() - 1, rect.GetBottom()))
1596
1597        topStartColour    = artMgr.LightColour(baseColour, 90)
1598        topEndColour      = artMgr.LightColour(baseColour, 60)
1599        bottomStartColour = artMgr.LightColour(baseColour, 40)
1600        bottomEndColour   = artMgr.LightColour(baseColour, 20)
1601
1602        topRegion = wx.Region(topReg)
1603
1604        artMgr.PaintGradientRegion(dc, topRegion, topStartColour, topEndColour)
1605        artMgr.PaintStraightGradientBox(dc, bottom, bottomStartColour, bottomEndColour)
1606        artMgr.PaintStraightGradientBox(dc, middle, topEndColour, bottomStartColour)
1607
1608
1609    def DrawToolBarBg(self, dc, rect):
1610        """
1611        Draws the toolbar background according to the active theme.
1612
1613        :param `dc`: an instance of :class:`wx.DC`;
1614        :param `rect`: an instance of :class:`wx.Rect`, representing the toolbar client rectangle.
1615        """
1616
1617        artMgr = ArtManager.Get()
1618
1619        if not artMgr.GetRaiseToolbar():
1620            return
1621
1622        # Keep old pen and brush
1623        dcsaver = DCSaver(dc)
1624
1625        baseColour = self.menuBarFaceColour
1626        baseColour = artMgr.LightColour(baseColour, 20)
1627
1628        dc.SetBrush(wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE)))
1629        dc.SetPen(wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE)))
1630        dc.DrawRectangle(rect)
1631
1632        radius = 2
1633
1634        # Define the rounded rectangle base on the given rect
1635        # we need an array of 9 points for it
1636        regPts = [None]*9
1637
1638        regPts[0] = wx.Point(rect.x, rect.y + radius)
1639        regPts[1] = wx.Point(rect.x+radius, rect.y)
1640        regPts[2] = wx.Point(rect.x+rect.width-radius-1, rect.y)
1641        regPts[3] = wx.Point(rect.x+rect.width-1, rect.y + radius)
1642        regPts[4] = wx.Point(rect.x+rect.width-1, rect.y + rect.height - radius - 1)
1643        regPts[5] = wx.Point(rect.x+rect.width-radius-1, rect.y + rect.height-1)
1644        regPts[6] = wx.Point(rect.x+radius, rect.y + rect.height-1)
1645        regPts[7] = wx.Point(rect.x, rect.y + rect.height - radius - 1)
1646        regPts[8] = regPts[0]
1647
1648        # Define the middle points
1649        factor = artMgr.GetMenuBgFactor()
1650
1651        leftPt1 = wx.Point(rect.x, rect.y + (rect.height / factor))
1652        rightPt1 = wx.Point(rect.x + rect.width, rect.y + (rect.height / factor))
1653
1654        leftPt2 = wx.Point(rect.x, rect.y + (rect.height / factor)*(factor-1))
1655        rightPt2 = wx.Point(rect.x + rect.width, rect.y + (rect.height / factor)*(factor-1))
1656
1657        # Define the top region
1658        topReg = [None]*7
1659        topReg[0] = regPts[0]
1660        topReg[1] = regPts[1]
1661        topReg[2] = wx.Point(regPts[2].x+1, regPts[2].y)
1662        topReg[3] = wx.Point(regPts[3].x + 1, regPts[3].y)
1663        topReg[4] = wx.Point(rightPt1.x, rightPt1.y+1)
1664        topReg[5] = wx.Point(leftPt1.x, leftPt1.y+1)
1665        topReg[6] = topReg[0]
1666
1667        # Define the middle region
1668        middle = wx.Rect(leftPt1, wx.Point(rightPt2.x - 2, rightPt2.y))
1669
1670        # Define the bottom region
1671        bottom = wx.Rect(leftPt2, wx.Point(rect.GetRight() - 1, rect.GetBottom()))
1672
1673        topStartColour   = artMgr.LightColour(baseColour, 90)
1674        topEndColour = artMgr.LightColour(baseColour, 60)
1675        bottomStartColour = artMgr.LightColour(baseColour, 40)
1676        bottomEndColour   = artMgr.LightColour(baseColour, 20)
1677
1678        topRegion = wx.Region(topReg)
1679
1680        artMgr.PaintGradientRegion(dc, topRegion, topStartColour, topEndColour)
1681        artMgr.PaintStraightGradientBox(dc, bottom, bottomStartColour, bottomEndColour)
1682        artMgr.PaintStraightGradientBox(dc, middle, topEndColour, bottomStartColour)
1683
1684        artMgr.DrawBitmapShadow(dc, rect)
1685
1686
1687    def GetTextColourEnable(self):
1688        """ Returns the colour used for text colour when enabled. """
1689
1690        return wx.Colour("MIDNIGHT BLUE")
1691
1692
1693# ---------------------------------------------------------------------------- #
1694# Class FMRendererVista
1695# ---------------------------------------------------------------------------- #
1696
1697class FMRendererVista(FMRendererMSOffice2007):
1698    """ Windows Vista-like style. """
1699
1700    def __init__(self):
1701        """ Default class constructor. """
1702
1703        FMRendererMSOffice2007.__init__(self)
1704
1705
1706    def DrawButtonColour(self, dc, rect, state, colour):
1707        """
1708        Draws a button using the Vista theme.
1709
1710        :param `dc`: an instance of :class:`wx.DC`;
1711        :param `rect`: the an instance of :class:`wx.Rect`, representing the button client rectangle;
1712        :param integer `state`: the button state;
1713        :param `colour`: a valid :class:`wx.Colour` instance.
1714        """
1715
1716        artMgr = ArtManager.Get()
1717
1718        # Keep old pen and brush
1719        dcsaver = DCSaver(dc)
1720
1721        outer = rgbSelectOuter
1722        inner = rgbSelectInner
1723        top = rgbSelectTop
1724        bottom = rgbSelectBottom
1725
1726        bdrRect = wx.Rect(*rect)
1727        filRect = wx.Rect(*rect)
1728        filRect.Deflate(1,1)
1729
1730        r1, g1, b1 = int(top.Red()), int(top.Green()), int(top.Blue())
1731        r2, g2, b2 = int(bottom.Red()), int(bottom.Green()), int(bottom.Blue())
1732        dc.GradientFillLinear(filRect, top, bottom, wx.SOUTH)
1733
1734        dc.SetBrush(wx.TRANSPARENT_BRUSH)
1735        dc.SetPen(wx.Pen(outer))
1736        dc.DrawRoundedRectangle(bdrRect, 3)
1737        bdrRect.Deflate(1, 1)
1738        dc.SetPen(wx.Pen(inner))
1739        dc.DrawRoundedRectangle(bdrRect, 2)
1740
1741
1742# ---------------------------------------------------------------------------- #
1743# Class FMRendererXP
1744# ---------------------------------------------------------------------------- #
1745
1746class FMRendererXP(FMRenderer):
1747    """ Xp-Style renderer. """
1748
1749    def __init__(self):
1750        """ Default class constructor. """
1751
1752        FMRenderer.__init__(self)
1753
1754        self.drawLeftMargin = True
1755        self.separatorHeight = 3
1756        self.highlightCheckAndRadio = True
1757        self.scrollBarButtons = True   # Display scrollbar buttons if the menu doesn't fit on the screen
1758
1759        self.buttonBorderColour        = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
1760        self.buttonFaceColour          = ArtManager.Get().LightColour(self.buttonBorderColour, 75)
1761        self.buttonFocusBorderColour   = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
1762        self.buttonFocusFaceColour     = ArtManager.Get().LightColour(self.buttonFocusBorderColour, 75)
1763        self.buttonPressedBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
1764        self.buttonPressedFaceColour   = ArtManager.Get().LightColour(self.buttonPressedBorderColour, 60)
1765
1766        self.menuFocusBorderColour   = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
1767        self.menuFocusFaceColour     = ArtManager.Get().LightColour(self.buttonFocusBorderColour, 75)
1768        self.menuPressedBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
1769        self.menuPressedFaceColour   = ArtManager.Get().LightColour(self.buttonPressedBorderColour, 60)
1770
1771        self.menuBarFocusBorderColour   = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
1772        self.menuBarFocusFaceColour     = ArtManager.Get().LightColour(self.buttonFocusBorderColour, 75)
1773        self.menuBarPressedBorderColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
1774        self.menuBarPressedFaceColour   = ArtManager.Get().LightColour(self.buttonPressedBorderColour, 60)
1775
1776
1777    def DrawLeftMargin(self, item, dc, menuRect):
1778        """
1779        Draws the menu left margin.
1780
1781        :param `item`: the :class:`FlatMenuItem` to paint;
1782        :param `dc`: an instance of :class:`wx.DC`;
1783        :param `menuRect`: an instance of :class:`wx.Rect`, representing the menu client rectangle.
1784        """
1785
1786        # Construct the margin rectangle
1787        marginRect = wx.Rect(menuRect.x+1, menuRect.y, item._parentMenu.GetLeftMarginWidth(), menuRect.height)
1788
1789        # Set the gradient colours
1790        artMgr = ArtManager.Get()
1791        faceColour = self.menuFaceColour
1792
1793        startColour = artMgr.DarkColour(faceColour, 20)
1794        endColour   = faceColour
1795        artMgr.PaintStraightGradientBox(dc, marginRect, startColour, endColour, False)
1796
1797
1798    def DrawMenuBarBackground(self, dc, rect):
1799        """
1800        Draws the menu bar background according to the active theme.
1801
1802        :param `dc`: an instance of :class:`wx.DC`;
1803        :param `rect`: an instance of :class:`wx.Rect`, representing the menubar client rectangle.
1804        """
1805
1806        # For office style, we simple draw a rectangle with a gradient colouring
1807        artMgr = ArtManager.Get()
1808        vertical = artMgr.GetMBVerticalGradient()
1809
1810        dcsaver = DCSaver(dc)
1811
1812        # fill with gradient
1813        startColour = artMgr.GetMenuBarFaceColour()
1814        if artMgr.IsDark(startColour):
1815            startColour = artMgr.LightColour(startColour, 50)
1816
1817        endColour = artMgr.LightColour(startColour, 90)
1818        artMgr.PaintStraightGradientBox(dc, rect, startColour, endColour, vertical)
1819
1820        # Draw the border
1821        if artMgr.GetMenuBarBorder():
1822
1823            dc.SetPen(wx.Pen(startColour))
1824            dc.SetBrush(wx.TRANSPARENT_BRUSH)
1825            dc.DrawRectangle(rect)
1826
1827
1828    def DrawToolBarBg(self, dc, rect):
1829        """
1830        Draws the toolbar background according to the active theme.
1831
1832        :param `dc`: an instance of :class:`wx.DC`;
1833        :param `rect`: an instance of :class:`wx.Rect`, representing the toolbar client rectangle.
1834        """
1835
1836        artMgr = ArtManager.Get()
1837
1838        if not artMgr.GetRaiseToolbar():
1839            return
1840
1841        # For office style, we simple draw a rectangle with a gradient colouring
1842        vertical = artMgr.GetMBVerticalGradient()
1843
1844        dcsaver = DCSaver(dc)
1845
1846        # fill with gradient
1847        startColour = artMgr.GetMenuBarFaceColour()
1848        if artMgr.IsDark(startColour):
1849            startColour = artMgr.LightColour(startColour, 50)
1850
1851        startColour = artMgr.LightColour(startColour, 20)
1852
1853        endColour   = artMgr.LightColour(startColour, 90)
1854        artMgr.PaintStraightGradientBox(dc, rect, startColour, endColour, vertical)
1855        artMgr.DrawBitmapShadow(dc, rect)
1856
1857
1858    def GetTextColourEnable(self):
1859        """ Returns the colour used for text colour when enabled. """
1860
1861        return wx.BLACK
1862
1863
1864# ----------------------------------------------------------------------------
1865# File history (a.k.a. MRU, most recently used, files list)
1866# ----------------------------------------------------------------------------
1867
1868def GetMRUEntryLabel(n, path):
1869    """
1870    Returns the string used for the MRU list items in the menu.
1871
1872    :param integer `n`: the index of the file name in the MRU list;
1873    :param string `path`: the full path of the file name.
1874
1875    :note: The index `n` is 0-based, as usual, but the strings start from 1.
1876    """
1877
1878    # we need to quote '&' characters which are used for mnemonics
1879    pathInMenu = path.replace("&", "&&")
1880    return "&%d %s"%(n + 1, pathInMenu)
1881
1882
1883# ----------------------------------------------------------------------------
1884# File history management
1885# ----------------------------------------------------------------------------
1886
1887class FileHistory(object):
1888    """
1889    The :class:`FileHistory` encapsulates a user interface convenience, the list of most
1890    recently visited files as shown on a menu (usually the File menu).
1891
1892    :class:`FileHistory` can manage one or more file menus. More than one menu may be
1893    required in an MDI application, where the file history should appear on each MDI
1894    child menu as well as the MDI parent frame.
1895    """
1896
1897    def __init__(self, maxFiles=9, idBase=wx.ID_FILE1):
1898        """
1899        Default class constructor.
1900
1901        :param integer `maxFiles`: the maximum number of files that should be stored and displayed;
1902        :param integer `idBase`: defaults to ``wx.ID_FILE1`` and represents the id given to the first
1903         history menu item.
1904
1905        :note: Since menu items can't share the same ID you should change `idBase` to one of
1906         your own defined IDs when using more than one :class:`FileHistory` in your application.
1907        """
1908
1909        # The ID of the first history menu item (Doesn't have to be wxID_FILE1)
1910        self._idBase = idBase
1911
1912        # Last n files
1913        self._fileHistory = []
1914
1915        # Menus to maintain (may need several for an MDI app)
1916        self._fileMenus = []
1917
1918        # Max files to maintain
1919        self._fileMaxFiles = maxFiles
1920
1921
1922    def GetMaxFiles(self):
1923        """ Returns the maximum number of files that can be stored. """
1924
1925        return self._fileMaxFiles
1926
1927
1928    # Accessors
1929    def GetHistoryFile(self, index):
1930        """
1931        Returns the file at this index (zero-based).
1932
1933        :param integer `index`: the index at which the file is stored in the file list (zero-based).
1934        """
1935
1936        return self._fileHistory[index]
1937
1938
1939    def GetCount(self):
1940        """ Returns the number of files currently stored in the file history. """
1941
1942        return len(self._fileHistory)
1943
1944
1945    def GetMenus(self):
1946        """
1947        Returns the list of menus that are managed by this file history object.
1948
1949        :see: :meth:`~FileHistory.UseMenu`.
1950        """
1951
1952        return self._fileMenus
1953
1954
1955    # Set/get base id
1956    def SetBaseId(self, baseId):
1957        """
1958        Sets the base identifier for the range used for appending items.
1959
1960        :param integer `baseId`: the base identifier for the range used for appending items.
1961        """
1962
1963        self._idBase = baseId
1964
1965
1966    def GetBaseId(self):
1967        """ Returns the base identifier for the range used for appending items. """
1968
1969        return self._idBase
1970
1971
1972    def GetNoHistoryFiles(self):
1973        """ Returns the number of files currently stored in the file history. """
1974
1975        return self.GetCount()
1976
1977
1978    def AddFileToHistory(self, fnNew):
1979        """
1980        Adds a file to the file history list, if the object has a pointer to an
1981        appropriate file menu.
1982
1983        :param string `fnNew`: the file name to add to the history list.
1984        """
1985
1986        # check if we don't already have this file
1987        numFiles = len(self._fileHistory)
1988
1989        for index, fileH in enumerate(self._fileHistory):
1990            if fnNew == fileH:
1991                # we do have it, move it to the top of the history
1992                self.RemoveFileFromHistory(index)
1993                numFiles -= 1
1994                break
1995
1996        # if we already have a full history, delete the one at the end
1997        if numFiles == self._fileMaxFiles:
1998            self.RemoveFileFromHistory(numFiles-1)
1999
2000        # add a new menu item to all file menus (they will be updated below)
2001        for menu in self._fileMenus:
2002            if numFiles == 0 and menu.GetMenuItemCount() > 0:
2003                menu.AppendSeparator()
2004
2005            # label doesn't matter, it will be set below anyhow, but it can't
2006            # be empty (this is supposed to indicate a stock item)
2007            menu.Append(self._idBase + numFiles, " ")
2008
2009        # insert the new file in the beginning of the file history
2010        self._fileHistory.insert(0, fnNew)
2011        numFiles += 1
2012
2013        # update the labels in all menus
2014        for index in range(numFiles):
2015
2016            # if in same directory just show the filename otherwise the full path
2017            fnOld = self._fileHistory[index]
2018            oldPath, newPath = os.path.split(fnOld)[0], os.path.split(fnNew)[0]
2019
2020            if oldPath == newPath:
2021                pathInMenu = os.path.split(fnOld)[1]
2022
2023            else:
2024                # file in different directory
2025                # absolute path could also set relative path
2026                pathInMenu = self._fileHistory[index]
2027
2028            for menu in self._fileMenus:
2029                menu.SetLabel(self._idBase + index, GetMRUEntryLabel(index, pathInMenu))
2030
2031
2032    def RemoveFileFromHistory(self, index):
2033        """
2034        Removes the specified file from the history.
2035
2036        :param integer `index`: the zero-based index indicating the file name position in
2037         the file list.
2038        """
2039
2040        numFiles = len(self._fileHistory)
2041        if index >= numFiles:
2042            raise Exception("Invalid index in RemoveFileFromHistory: %d (only %d files)"%(index, numFiles))
2043
2044        # delete the element from the array
2045        self._fileHistory.pop(index)
2046        numFiles -= 1
2047
2048        for menu in self._fileMenus:
2049            # shift filenames up
2050            for j in range(numFiles):
2051                menu.SetLabel(self._idBase + j, GetMRUEntryLabel(j, self._fileHistory[j]))
2052
2053            # delete the last menu item which is unused now
2054            lastItemId = self._idBase + numFiles
2055            if menu.FindItem(lastItemId):
2056                menu.Delete(lastItemId)
2057
2058            if not self._fileHistory:
2059                lastMenuItem = menu.GetMenuItems()[-1]
2060                if lastMenuItem.IsSeparator():
2061                    menu.Delete(lastMenuItem)
2062
2063                #else: menu is empty somehow
2064
2065
2066    def UseMenu(self, menu):
2067        """
2068        Adds this menu to the list of those menus that are managed by this file history
2069        object.
2070
2071        :param `menu`: an instance of :class:`FlatMenu`.
2072
2073        :see: :meth:`~FileHistory.AddFilesToMenu` for initializing the menu with filenames that are already
2074         in the history when this function is called, as this is not done automatically.
2075        """
2076
2077        if menu not in self._fileMenus:
2078            self._fileMenus.append(menu)
2079
2080
2081    def RemoveMenu(self, menu):
2082        """
2083        Removes this menu from the list of those managed by this object.
2084
2085        :param `menu`: an instance of :class:`FlatMenu`.
2086        """
2087
2088        self._fileMenus.remove(menu)
2089
2090
2091    def Load(self, config):
2092        """
2093        Loads the file history from the given `config` object.
2094
2095        :param `config`: an instance of :class:`Config <ConfigBase>`.
2096
2097        :note: This function should be called explicitly by the application.
2098
2099        :see: :meth:`~FileHistory.Save`.
2100        """
2101
2102        self._fileHistory = []
2103        buffer = "file%d"
2104        count = 1
2105
2106        while 1:
2107            historyFile = config.Read(buffer%count)
2108            if not historyFile or len(self._fileHistory) >= self._fileMaxFiles:
2109                break
2110
2111            self._fileHistory.append(historyFile)
2112            count += 1
2113
2114        self.AddFilesToMenu()
2115
2116
2117    def Save(self, config):
2118        """
2119        Saves the file history to the given `config` object.
2120
2121        :param `config`: an instance of :class:`Config <ConfigBase>`.
2122
2123        :note: This function should be called explicitly by the application.
2124
2125        :see: :meth:`~FileHistory.Load`.
2126        """
2127
2128        buffer = "file%d"
2129
2130        for index in range(self._fileMaxFiles):
2131
2132            if index < len(self._fileHistory):
2133                config.Write(buffer%(index+1), self._fileHistory[i])
2134            else:
2135                config.Write(buffer%(index+1), "")
2136
2137
2138    def AddFilesToMenu(self, menu=None):
2139        """
2140        Appends the files in the history list, to all menus managed by the file history object
2141        if `menu` is ``None``. Otherwise it calls the auxiliary method :meth:`~FileHistory.AddFilesToMenu2`.
2142
2143        :param `menu`: if not ``None``, an instance of :class:`FlatMenu`.
2144        """
2145
2146        if not self._fileHistory:
2147            return
2148
2149        if menu is not None:
2150            self.AddFilesToMenu2(menu)
2151            return
2152
2153        for menu in self._fileMenus:
2154            self.AddFilesToMenu2(menu)
2155
2156
2157    def AddFilesToMenu2(self, menu):
2158        """
2159        Appends the files in the history list, to the given menu only.
2160
2161        :param `menu`: an instance of :class:`FlatMenu`.
2162        """
2163
2164        if not self._fileHistory:
2165            return
2166
2167        if menu.GetMenuItemCount():
2168            menu.AppendSeparator()
2169
2170        for index in range(len(self._fileHistory)):
2171            menu.Append(self._idBase + index, GetMRUEntryLabel(index, self._fileHistory[i]))
2172
2173
2174# ---------------------------------------------------------------------------- #
2175# Class FlatMenuEvent
2176# ---------------------------------------------------------------------------- #
2177
2178class FlatMenuEvent(wx.PyCommandEvent):
2179    """
2180    Event class that supports the :class:`FlatMenu`-compatible event called
2181    ``EVT_FLAT_MENU_SELECTED``.
2182    """
2183
2184    def __init__(self, eventType, eventId=1):
2185        """
2186        Default class constructor.
2187
2188        :param integer `eventType`: the event type;
2189        :param integer `eventId`: the event identifier.
2190        """
2191
2192        wx.PyCommandEvent.__init__(self, eventType, eventId)
2193        self._eventType = eventType
2194
2195
2196# ---------------------------------------------------------------------------- #
2197# Class MenuEntryInfo
2198# ---------------------------------------------------------------------------- #
2199
2200class MenuEntryInfo(object):
2201    """
2202    Internal class which holds information about a menu.
2203    """
2204
2205    def __init__(self, titleOrMenu="", menu=None, state=ControlNormal, cmd=wx.ID_ANY):
2206        """
2207        Default class constructor.
2208
2209        Used internally. Do not call it in your code!
2210
2211        :param `titleOrMenu`: if it is a string, it represents the new menu label,
2212         otherwise it is another instance of :class:`wx.MenuEntryInfo` from which the attributes
2213         are copied;
2214        :param `menu`: the associated :class:`FlatMenu` object;
2215        :param integer `state`: the menu item state. This can be one of the following:
2216
2217         ==================== ======= ==========================
2218         Item State            Value  Description
2219         ==================== ======= ==========================
2220         ``ControlPressed``         0 The item is pressed
2221         ``ControlFocus``           1 The item is focused
2222         ``ControlDisabled``        2 The item is disabled
2223         ``ControlNormal``          3 Normal state
2224         ==================== ======= ==========================
2225
2226        :param integer `cmd`: the menu accelerator identifier.
2227        """
2228
2229        if isinstance(titleOrMenu, six.string_types):
2230
2231            self._title = titleOrMenu
2232            self._menu = menu
2233
2234            self._rect = wx.Rect()
2235            self._state = state
2236            if cmd == wx.ID_ANY:
2237                cmd = wx.NewIdRef()
2238
2239            self._cmd = cmd             # the menu itself accelerator id
2240
2241        else:
2242
2243            self._title = titleOrMenu._title
2244            self._menu = titleOrMenu._menu
2245            self._rect = titleOrMenu._rect
2246            self._state = titleOrMenu._state
2247            self._cmd = titleOrMenu._cmd
2248
2249        self._textBmp = wx.NullBitmap
2250        self._textSelectedBmp = wx.NullBitmap
2251
2252
2253    def GetTitle(self):
2254        """ Returns the associated menu title. """
2255
2256        return self._title
2257
2258
2259    def GetMenu(self):
2260        """ Returns the associated menu. """
2261
2262        return self._menu
2263
2264
2265    def SetRect(self, rect):
2266        """
2267        Sets the associated menu client rectangle.
2268
2269        :param `rect`: an instance of :class:`wx.Rect`, representing the menu client rectangle.
2270        """
2271
2272        self._rect = rect
2273
2274
2275    def GetRect(self):
2276        """ Returns the associated menu client rectangle. """
2277
2278        return self._rect
2279
2280
2281    def SetState(self, state):
2282        """
2283        Sets the associated menu state.
2284
2285        :param integer `state`: the menu item state. This can be one of the following:
2286
2287         ==================== ======= ==========================
2288         Item State            Value  Description
2289         ==================== ======= ==========================
2290         ``ControlPressed``         0 The item is pressed
2291         ``ControlFocus``           1 The item is focused
2292         ``ControlDisabled``        2 The item is disabled
2293         ``ControlNormal``          3 Normal state
2294         ==================== ======= ==========================
2295        """
2296
2297        self._state = state
2298
2299
2300    def GetState(self):
2301        """
2302        Returns the associated menu state.
2303
2304        :see: :meth:`~MenuEntryInfo.SetState` for a list of valid menu states.
2305        """
2306
2307        return self._state
2308
2309
2310    def SetTextBitmap(self, bmp):
2311        """
2312        Sets the associated menu bitmap.
2313
2314        :param `bmp`: a valid :class:`wx.Bitmap` object.
2315        """
2316
2317        self._textBmp = bmp
2318
2319
2320    def SetSelectedTextBitmap(self, bmp):
2321        """
2322        Sets the associated selected menu bitmap.
2323
2324        :param `bmp`: a valid :class:`wx.Bitmap` object.
2325        """
2326
2327        self._textSelectedBmp = bmp
2328
2329
2330    def GetTextBitmap(self):
2331        """ Returns the associated menu bitmap. """
2332
2333        return self._textBmp
2334
2335
2336    def GetSelectedTextBitmap(self):
2337        """ Returns the associated selected menu bitmap. """
2338
2339        return self._textSelectedBmp
2340
2341
2342    def GetCmdId(self):
2343        """ Returns the associated menu accelerator identifier. """
2344
2345        return self._cmd
2346
2347
2348# ---------------------------------------------------------------------------- #
2349# Class StatusBarTimer
2350# ---------------------------------------------------------------------------- #
2351
2352class StatusBarTimer(wx.Timer):
2353    """ Timer used for deleting :class:`StatusBar` long help after ``_DELAY`` seconds. """
2354
2355    def __init__(self, owner):
2356        """
2357        Default class constructor.
2358        For internal use: do not call it in your code!
2359
2360        :param `owner`: the :class:`Timer` owner (:class:`FlatMenuBar`).
2361        """
2362
2363        wx.Timer.__init__(self)
2364        self._owner = owner
2365
2366
2367    def Notify(self):
2368        """ The timer has expired. """
2369
2370        self._owner.OnStatusBarTimer()
2371
2372
2373# ---------------------------------------------------------------------------- #
2374# Class FlatMenuBar
2375# ---------------------------------------------------------------------------- #
2376
2377class FlatMenuBar(wx.Panel):
2378    """
2379    Implements the generic owner-drawn menu bar for :class:`FlatMenu`.
2380    """
2381
2382    def __init__(self, parent, id=wx.ID_ANY, iconSize=SmallIcons,
2383                 spacer=SPACER, options=FM_OPT_SHOW_CUSTOMIZE|FM_OPT_IS_LCD):
2384        """
2385        Default class constructor.
2386
2387        :param `parent`: the menu bar parent, must not be ``None``;
2388        :param integer `id`: the window identifier. If ``wx.ID_ANY``, will automatically create an identifier;
2389        :param integer `iconSize`: size of the icons in the toolbar. This can be one of the
2390         following values (in pixels):
2391
2392         ==================== ======= =============================
2393         `iconSize` Bit        Value  Description
2394         ==================== ======= =============================
2395         ``LargeIcons``            32 Use large 32x32 icons
2396         ``SmallIcons``            16 Use standard 16x16 icons
2397         ==================== ======= =============================
2398
2399        :param integer `spacer`: the space between the menu bar text and the menu bar border;
2400        :param integer `options`: a combination of the following bits:
2401
2402         ========================= ========= =============================
2403         `options` Bit             Hex Value  Description
2404         ========================= ========= =============================
2405         ``FM_OPT_IS_LCD``               0x1 Use this style if your computer uses a LCD screen
2406         ``FM_OPT_MINIBAR``              0x2 Use this if you plan to use toolbar only
2407         ``FM_OPT_SHOW_CUSTOMIZE``       0x4 Show "customize link" in more menus, you will need to write your own handler. See demo.
2408         ``FM_OPT_SHOW_TOOLBAR``         0x8 Set this option is you are planing to use the toolbar
2409         ========================= ========= =============================
2410
2411        """
2412
2413        self._rendererMgr = FMRendererMgr()
2414        self._parent = parent
2415        self._curretHiliteItem = -1
2416
2417        self._items = []
2418        self._dropDownButtonArea = wx.Rect()
2419        self._tbIconSize = iconSize
2420        self._tbButtons = []
2421        self._interval = 20      # 20 milliseconds
2422        self._showTooltip = -1
2423
2424        self._haveTip = False
2425        self._statusTimer = None
2426        self._spacer = SPACER
2427        self._margin = spacer
2428        self._toolbarSpacer = TOOLBAR_SPACER
2429        self._toolbarMargin = TOOLBAR_MARGIN
2430
2431        self._showToolbar = options & FM_OPT_SHOW_TOOLBAR
2432        self._showCustomize = options & FM_OPT_SHOW_CUSTOMIZE
2433        self._isLCD = options & FM_OPT_IS_LCD
2434        self._isMinibar = options & FM_OPT_MINIBAR
2435        self._options = options
2436
2437        self._dropDownButtonState = ControlNormal
2438        self._moreMenu = None
2439        self._dlg = None
2440        self._tbMenu = None
2441        self._moreMenuBgBmp = None
2442        self._lastRadioGroup = 0
2443        self._mgr = None
2444
2445        self._barHeight = 0
2446        self._menuBarHeight = 0
2447        self.SetBarHeight()
2448
2449        wx.Panel.__init__(self, parent, id, size=(-1, self._barHeight), style=wx.WANTS_CHARS)
2450
2451        self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
2452        self.Bind(wx.EVT_PAINT, self.OnPaint)
2453        self.Bind(wx.EVT_SIZE, self.OnSize)
2454        self.Bind(wx.EVT_MOTION, self.OnMouseMove)
2455        self.Bind(EVT_FLAT_MENU_DISMISSED, self.OnMenuDismissed)
2456        self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveMenuBar)
2457        self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
2458        self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown)
2459        self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
2460        self.Bind(wx.EVT_IDLE, self.OnIdle)
2461
2462        if "__WXGTK__" in wx.Platform:
2463            self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)
2464
2465        self.SetFocus()
2466
2467        # start the stop watch
2468        self._watch = wx.StopWatch()
2469        self._watch.Start()
2470
2471
2472    def Append(self, menu, title):
2473        """
2474        Adds the item to the end of the menu bar.
2475
2476        :param `menu`: the menu to which we are appending a new item, an instance of :class:`FlatMenu`;
2477        :param string `title`: the menu item label, must not be empty.
2478
2479        :see: :meth:`~FlatMenuBar.Insert`.
2480        """
2481
2482        menu._menuBarFullTitle = title
2483        position, label = GetAccelIndex(title)
2484        menu._menuBarLabelOnly = label
2485
2486        return self.Insert(len(self._items), menu, title)
2487
2488
2489    def OnIdle(self, event):
2490        """
2491        Handles the ``wx.EVT_IDLE`` event for :class:`FlatMenuBar`.
2492
2493        :param `event`: a :class:`IdleEvent` event to be processed.
2494        """
2495
2496        refresh = False
2497
2498        if self._watch.Time() > self._interval:
2499
2500            # it is time to process UpdateUIEvents
2501            for but in self._tbButtons:
2502                event = wx.UpdateUIEvent(but._tbItem.GetId())
2503                event.Enable(but._tbItem.IsEnabled())
2504                event.SetText(but._tbItem.GetLabel())
2505                event.SetEventObject(self)
2506
2507                self.GetEventHandler().ProcessEvent(event)
2508
2509                if but._tbItem.GetLabel() != event.GetText() or but._tbItem.IsEnabled() != event.GetEnabled():
2510                    refresh = True
2511
2512                but._tbItem.SetLabel(event.GetText())
2513                but._tbItem.Enable(event.GetEnabled())
2514
2515            self._watch.Start() # Reset the timer
2516
2517        # we need to update the menu bar
2518        if refresh:
2519            self.Refresh()
2520
2521
2522    def SetBarHeight(self):
2523        """ Recalculates the :class:`FlatMenuBar` height when its settings change. """
2524
2525        mem_dc = wx.MemoryDC()
2526        mem_dc.SelectObject(wx.Bitmap(1, 1))
2527        dummy, self._barHeight = mem_dc.GetTextExtent("Tp")
2528        mem_dc.SelectObject(wx.NullBitmap)
2529
2530        if not self._isMinibar:
2531            self._barHeight += 2*self._margin # The menu bar margin
2532        else:
2533            self._barHeight  = 0
2534
2535        self._menuBarHeight = self._barHeight
2536
2537        if self._showToolbar :
2538            # add the toolbar height to the menubar height
2539            self._barHeight += self._tbIconSize + 2*self._toolbarMargin
2540
2541        if self._mgr is None:
2542            return
2543
2544        pn = self._mgr.GetPane("flat_menu_bar")
2545        pn.MinSize(wx.Size(-1, self._barHeight))
2546        self._mgr.Update()
2547        self.Refresh()
2548
2549
2550    def SetOptions(self, options):
2551        """
2552        Sets the :class:`FlatMenuBar` options, whether to show a toolbar, to use LCD screen settings etc...
2553
2554        :param integer `options`: a combination of the following bits:
2555
2556         ========================= ========= =============================
2557         `options` Bit             Hex Value  Description
2558         ========================= ========= =============================
2559         ``FM_OPT_IS_LCD``               0x1 Use this style if your computer uses a LCD screen
2560         ``FM_OPT_MINIBAR``              0x2 Use this if you plan to use toolbar only
2561         ``FM_OPT_SHOW_CUSTOMIZE``       0x4 Show "customize link" in more menus, you will need to write your own handler. See demo.
2562         ``FM_OPT_SHOW_TOOLBAR``         0x8 Set this option is you are planing to use the toolbar
2563         ========================= ========= =============================
2564
2565        """
2566
2567        self._options = options
2568
2569        self._showToolbar = options & FM_OPT_SHOW_TOOLBAR
2570        self._showCustomize = options & FM_OPT_SHOW_CUSTOMIZE
2571        self._isLCD = options & FM_OPT_IS_LCD
2572        self._isMinibar = options & FM_OPT_MINIBAR
2573
2574        self.SetBarHeight()
2575
2576        self.Refresh()
2577        self.Update()
2578
2579
2580    def GetOptions(self):
2581        """
2582        Returns the :class:`FlatMenuBar` options, whether to show a toolbar, to use LCD screen settings etc...
2583
2584        :see: :meth:`~FlatMenuBar.SetOptions` for a list of valid options.
2585        """
2586
2587        return self._options
2588
2589
2590    def GetRendererManager(self):
2591        """
2592        Returns the :class:`FlatMenuBar` renderer manager.
2593        """
2594
2595        return self._rendererMgr
2596
2597
2598    def GetRenderer(self):
2599        """
2600        Returns the renderer associated with this instance.
2601        """
2602
2603        return self._rendererMgr.GetRenderer()
2604
2605
2606    def UpdateItem(self, item):
2607        """
2608        An item was modified. This function is called by :class:`FlatMenu` in case
2609        an item was modified directly and not via a :class:`UpdateUIEvent` event.
2610
2611        :param `item`: an instance of :class:`FlatMenu`.
2612        """
2613
2614        if not self._showToolbar:
2615            return
2616
2617        # search for a tool bar with id
2618        refresh = False
2619
2620        for but in self._tbButtons:
2621            if but._tbItem.GetId() == item.GetId():
2622                if but._tbItem.IsEnabled() != item.IsEnabled():
2623                    refresh = True
2624
2625                but._tbItem.Enable(item.IsEnabled())
2626                break
2627
2628        if refresh:
2629            self.Refresh()
2630
2631
2632    def OnPaint(self, event):
2633        """
2634        Handles the ``wx.EVT_PAINT`` event for :class:`FlatMenuBar`.
2635
2636        :param `event`: a :class:`PaintEvent` event to be processed.
2637        """
2638
2639        # on GTK, dont use the bitmap for drawing,
2640        # draw directly on the DC
2641
2642        if "__WXGTK__" in wx.Platform and not self._isLCD:
2643            self.ClearBitmaps(0)
2644
2645        dc = wx.BufferedPaintDC(self)
2646        self.GetRenderer().DrawMenuBar(self, dc)
2647
2648
2649    def DrawToolbar(self, dc, rect):
2650        """
2651        Draws the toolbar (if present).
2652
2653        :param `dc`: an instance of :class:`wx.DC`;
2654        :param `rect`: the toolbar client rectangle, an instance of :class:`wx.Rect`.
2655        """
2656
2657        highlight_width = self._tbIconSize + self._toolbarSpacer
2658        highlight_height = self._tbIconSize + self._toolbarMargin
2659
2660        xx = rect.x + self._toolbarMargin
2661        #yy = rect.y #+ self._toolbarMargin #+ (rect.height - height)/2
2662
2663        # by default set all toolbar items as invisible
2664        for but in self._tbButtons:
2665            but._visible = False
2666
2667        counter = 0
2668        # Get all the toolbar items
2669        for i in range(len(self._tbButtons)):
2670
2671            xx += self._toolbarSpacer
2672
2673            tbItem = self._tbButtons[i]._tbItem
2674            # the button width depends on its type
2675            if tbItem.IsSeparator():
2676                hightlight_width = SEPARATOR_WIDTH
2677            elif tbItem.IsCustomControl():
2678                control = tbItem.GetCustomControl()
2679                hightlight_width = control.GetSize().x + self._toolbarSpacer
2680            else:
2681                hightlight_width = self._tbIconSize + self._toolbarSpacer   # normal bitmap's width
2682
2683            # can we keep drawing?
2684            if xx + highlight_width >= rect.width:
2685                break
2686
2687            counter += 1
2688
2689            # mark this item as visible
2690            self._tbButtons[i]._visible = True
2691
2692            bmp = wx.NullBitmap
2693
2694            #------------------------------------------
2695            # special handling for separator
2696            #------------------------------------------
2697            if tbItem.IsSeparator():
2698
2699                # draw the separator
2700                buttonRect = wx.Rect(xx, rect.y+1, SEPARATOR_WIDTH, rect.height-2)
2701                self.GetRenderer().DrawToolbarSeparator(dc, buttonRect)
2702
2703                xx += buttonRect.width
2704                self._tbButtons[i]._rect = buttonRect
2705                continue
2706
2707            elif tbItem.IsCustomControl():
2708                control = tbItem.GetCustomControl()
2709                ctrlSize = control.GetSize()
2710                ctrlPos = wx.Point(xx, rect.y + (rect.height - ctrlSize.y)/2)
2711                if control.GetPosition() != ctrlPos:
2712                    control.SetPosition(ctrlPos)
2713
2714                if not control.IsShown():
2715                    control.Show()
2716
2717                buttonRect = wx.Rect(ctrlPos, ctrlSize)
2718                xx += buttonRect.width
2719                self._tbButtons[i]._rect = buttonRect
2720                continue
2721            else:
2722                if tbItem.IsEnabled():
2723                    bmp = tbItem.GetBitmap()
2724                else:
2725                    bmp = tbItem.GetDisabledBitmap()
2726
2727            # Draw the toolbar image
2728            if bmp.IsOk():
2729
2730                x = xx - self._toolbarSpacer/2
2731                #y = rect.y + (rect.height - bmp.GetHeight())/2 - 1
2732                y = rect.y + self._toolbarMargin/2
2733
2734                buttonRect = wx.Rect(x, y, highlight_width, highlight_height)
2735
2736                if i < len(self._tbButtons) and i >= 0:
2737
2738                    if self._tbButtons[i]._tbItem.IsSelected():
2739                        tmpState = ControlPressed
2740                    else:
2741                        tmpState = ControlFocus
2742
2743                    if self._tbButtons[i]._state == ControlFocus or self._tbButtons[i]._tbItem.IsSelected():
2744                        self.GetRenderer().DrawMenuBarButton(dc, buttonRect, tmpState) # TODO DrawToolbarButton? With separate toolbar colors
2745                    else:
2746                        self._tbButtons[i]._state = ControlNormal
2747
2748                imgx = buttonRect.x + (buttonRect.width - bmp.GetWidth())/2
2749                imgy = buttonRect.y + (buttonRect.height - bmp.GetHeight())/2
2750
2751                if self._tbButtons[i]._state == ControlFocus and not self._tbButtons[i]._tbItem.IsSelected():
2752
2753                    # in case we the button is in focus, place it
2754                    # once pixle up and left
2755                    # place a dark image under the original image to provide it
2756                    # with some shadow
2757                    # shadow = ConvertToMonochrome(bmp)
2758                    # dc.DrawBitmap(shadow, imgx, imgy, True)
2759
2760                    imgx -= 1
2761                    imgy -= 1
2762
2763                dc.DrawBitmap(bmp, imgx, imgy, True)
2764                xx += buttonRect.width
2765
2766                self._tbButtons[i]._rect = buttonRect
2767                #Edited by P.Kort
2768
2769                if self._showTooltip == -1:
2770                    self.RemoveHelp()
2771                else:
2772                    try:
2773                        self.DoGiveHelp(self._tbButtons[self._showTooltip]._tbItem)
2774                    except:
2775                        if _debug:
2776                            print("FlatMenu.py; fn : DrawToolbar; Can't create Tooltip ")
2777                        pass
2778
2779        for j in range(counter, len(self._tbButtons)):
2780            if self._tbButtons[j]._tbItem.IsCustomControl():
2781                control = self._tbButtons[j]._tbItem.GetCustomControl()
2782                control.Hide()
2783
2784
2785    def GetMoreMenuButtonRect(self):
2786        """ Returns a rectangle region, as an instance of :class:`wx.Rect`, surrounding the menu button. """
2787
2788        clientRect = self.GetClientRect()
2789        rect = wx.Rect(*clientRect)
2790        rect.SetWidth(DROP_DOWN_ARROW_WIDTH)
2791        rect.SetX(clientRect.GetWidth() + rect.GetX() - DROP_DOWN_ARROW_WIDTH - 3)
2792        rect.SetY(2)
2793        rect.SetHeight(rect.GetHeight() - self._spacer)
2794
2795        return rect
2796
2797
2798    def DrawMoreButton(self, dc, state):
2799        """
2800        Draws 'more' button to the right side of the menu bar.
2801
2802        :param `dc`: an instance of :class:`wx.DC`;
2803        :param integer `state`: the 'more' button state.
2804
2805        :see: :meth:`wx.MenuEntryInfo.SetState() <MenuEntryInfo.SetState>` for a list of valid menu states.
2806        """
2807
2808        if (not self._showCustomize) and self.GetInvisibleMenuItemCount() < 1 and  self.GetInvisibleToolbarItemCount() < 1:
2809            return
2810
2811        # Draw a drop down menu at the right position of the menu bar
2812        # we use xpm file with 16x16 size, another 4 pixels we take as spacer
2813        # from the right side of the frame, this will create a DROP_DOWN_ARROW_WIDTH  pixels width
2814        # of unwanted zone on the right side
2815
2816        rect = self.GetMoreMenuButtonRect()
2817
2818        # Draw the bitmap
2819        if state != ControlNormal:
2820            # Draw background according to state
2821            self.GetRenderer().DrawButton(dc, rect, state)
2822        else:
2823            # Delete current image
2824            if self._moreMenuBgBmp.IsOk():
2825                dc.DrawBitmap(self._moreMenuBgBmp, rect.x, rect.y, True)
2826
2827        dropArrowBmp = self.GetRenderer()._bitmaps["arrow_down"]
2828
2829        # Calc the image coordinates
2830        xx = rect.x + (DROP_DOWN_ARROW_WIDTH - dropArrowBmp.GetWidth())/2
2831        yy = rect.y + (rect.height - dropArrowBmp.GetHeight())/2
2832
2833        dc.DrawBitmap(dropArrowBmp, xx, yy + self._spacer, True)
2834        self._dropDownButtonState = state
2835
2836
2837    def HitTest(self, pt):
2838        """
2839        HitTest method for :class:`FlatMenuBar`.
2840
2841        :param `pt`: an instance of :class:`wx.Point`, specifying the hit test position.
2842
2843        :return: A tuple representing one of the following combinations:
2844
2845         ========================= ==================================================
2846         Return Tuple              Description
2847         ========================= ==================================================
2848         (-1, 0)                   The :meth:`~FlatMenuBar.HitTest` method didn't find any item with the specified input point `pt` (``NoWhere`` = 0)
2849         (`integer`, 1)            A menu item has been hit, its position specified by the tuple item `integer` (``MenuItem`` = 1)
2850         (`integer`, 2)            A toolbar item has ben hit, its position specified by the tuple item `integer` (``ToolbarItem`` = 2)
2851         (-1, 3)                   The drop-down area button has been hit (``DropDownArrowButton`` = 3)
2852         ========================= ==================================================
2853
2854        """
2855
2856        if self._dropDownButtonArea.Contains(pt):
2857            return -1, DropDownArrowButton
2858
2859        for ii, item in enumerate(self._items):
2860            if item.GetRect().Contains(pt):
2861                return ii, MenuItem
2862
2863        # check for tool bar items
2864        if self._showToolbar:
2865            for ii, but in enumerate(self._tbButtons):
2866                if but._rect.Contains(pt):
2867                    # locate the corresponded menu item
2868                    enabled  = but._tbItem.IsEnabled()
2869                    separator = but._tbItem.IsSeparator()
2870                    visible  = but._visible
2871                    if enabled and not separator and visible:
2872                        self._showTooltip = ii
2873                        return ii, ToolbarItem
2874
2875        self._showTooltip = -1
2876        return -1, NoWhere
2877
2878
2879    def FindMenuItem(self, id):
2880        """
2881        Finds the menu item object associated with the given menu item identifier.
2882
2883        :param integer `id`: the identifier for the sought :class:`FlatMenuItem`.
2884
2885        :return: The found menu item object, or ``None`` if one was not found.
2886        """
2887
2888        for item in self._items:
2889            mi = item.GetMenu().FindItem(id)
2890            if mi:
2891                return mi
2892        return None
2893
2894
2895    def OnSize(self, event):
2896        """
2897        Handles the ``wx.EVT_SIZE`` event for :class:`FlatMenuBar`.
2898
2899        :param `event`: a :class:`wx.SizeEvent` event to be processed.
2900        """
2901
2902        self.ClearBitmaps(0)
2903        self.Refresh()
2904
2905
2906    def OnEraseBackground(self, event):
2907        """
2908        Handles the ``wx.EVT_ERASE_BACKGROUND`` event for :class:`FlatMenuBar`.
2909
2910        :param `event`: a :class:`EraseEvent` event to be processed.
2911
2912        :note: This method is intentionally empty to reduce flicker.
2913        """
2914
2915        pass
2916
2917
2918    def ShowCustomize(self, show=True):
2919        """
2920        Shows/hides the drop-down arrow which allows customization of :class:`FlatMenu`.
2921
2922        :param bool `show`: ``True`` to show the customize menu, ``False`` to hide it.
2923        """
2924
2925        if self._showCustomize == show:
2926            return
2927
2928        self._showCustomize = show
2929        self.Refresh()
2930
2931
2932    def SetMargin(self, margin):
2933        """
2934        Sets the margin above and below the menu bar text.
2935
2936        :param integer `margin`: height in pixels of the margin.
2937        """
2938
2939        self._margin = margin
2940
2941
2942    def SetSpacing(self, spacer):
2943        """
2944        Sets the spacing between the menubar items.
2945
2946        :param integer `spacer`: number of pixels between each menu item.
2947        """
2948
2949        self._spacer = spacer
2950
2951
2952    def SetToolbarMargin(self, margin):
2953        """
2954        Sets the margin around the toolbar.
2955
2956        :param integer `margin`: width in pixels of the margin around the tools in the toolbar.
2957        """
2958
2959        self._toolbarMargin = margin
2960
2961
2962    def SetToolbarSpacing(self, spacer):
2963        """
2964        Sets the spacing between the toolbar tools.
2965
2966        :param integer `spacer`: number of pixels between each tool in the toolbar.
2967        """
2968
2969        self._toolbarSpacer = spacer
2970
2971
2972    def SetLCDMonitor(self, lcd=True):
2973        """
2974        Sets whether the PC monitor is an LCD or not.
2975
2976        :param bool `lcd`: ``True`` to use the settings appropriate for a LCD monitor,
2977         ``False`` otherwise.
2978        """
2979
2980        if self._isLCD == lcd:
2981            return
2982
2983        self._isLCD = lcd
2984        self.Refresh()
2985
2986
2987    def ProcessMouseMoveFromMenu(self, pt):
2988        """
2989        This function is called from child menus, this allow a child menu to
2990        pass the mouse movement event to the menu bar.
2991
2992        :param `pt`: an instance of :class:`wx.Point`.
2993        """
2994
2995        idx, where = self.HitTest(pt)
2996        if where == MenuItem:
2997            self.ActivateMenu(self._items[idx])
2998
2999
3000    def DoMouseMove(self, pt, leftIsDown):
3001        """
3002        Handles mouse move event.
3003
3004        :param `pt`: an instance of :class:`wx.Point`;
3005        :param bool `leftIsDown`: ``True`` is the left mouse button is down, ``False`` otherwise.
3006        """
3007
3008        # Reset items state
3009        for item in self._items:
3010            item.SetState(ControlNormal)
3011
3012        idx, where = self.HitTest(pt)
3013
3014        if where == DropDownArrowButton:
3015            self.RemoveHelp()
3016            if self._dropDownButtonState != ControlFocus and not leftIsDown:
3017                dc = wx.ClientDC(self)
3018                self.DrawMoreButton(dc, ControlFocus)
3019
3020        elif where == MenuItem:
3021            self._dropDownButtonState = ControlNormal
3022            # On Item
3023            self._items[idx].SetState(ControlFocus)
3024
3025            # If this item is already selected, dont draw it again
3026            if self._curretHiliteItem == idx:
3027                return
3028
3029            self._curretHiliteItem = idx
3030            if self._showToolbar:
3031
3032                # mark all toolbar items as non-hilited
3033                for but in self._tbButtons:
3034                    but._state = ControlNormal
3035
3036            self.Refresh()
3037
3038        elif where == ToolbarItem:
3039
3040            if self._showToolbar:
3041                if idx < len(self._tbButtons) and idx >= 0:
3042                    if self._tbButtons[idx]._state == ControlFocus:
3043                        return
3044
3045                    # we need to refresh the toolbar
3046                    active = self.GetActiveToolbarItem()
3047                    if active != wx.NOT_FOUND:
3048                        self._tbButtons[active]._state = ControlNormal
3049
3050                    for but in self._tbButtons:
3051                        but._state = ControlNormal
3052
3053                    self._tbButtons[idx]._state = ControlFocus
3054                    self.DoGiveHelp(self._tbButtons[idx]._tbItem)
3055                    self.Refresh()
3056
3057        elif where == NoWhere:
3058
3059            refresh = False
3060            self.RemoveHelp()
3061
3062            if self._dropDownButtonState != ControlNormal:
3063                refresh = True
3064                self._dropDownButtonState = ControlNormal
3065
3066            if self._showToolbar:
3067                tbActiveItem = self.GetActiveToolbarItem()
3068                if tbActiveItem != wx.NOT_FOUND:
3069                    self._tbButtons[tbActiveItem]._state = ControlNormal
3070                    refresh = True
3071
3072            if self._curretHiliteItem != -1:
3073
3074                self._items[self._curretHiliteItem].SetState(ControlNormal)
3075                self._curretHiliteItem = -1
3076                self.Refresh()
3077
3078            if refresh:
3079                self.Refresh()
3080
3081
3082    def OnMouseMove(self, event):
3083        """
3084        Handles the ``wx.EVT_MOTION`` event for :class:`FlatMenuBar`.
3085
3086        :param `event`: a :class:`MouseEvent` event to be processed.
3087        """
3088
3089        pt = event.GetPosition()
3090        self.DoMouseMove(pt, event.LeftIsDown())
3091
3092
3093    def OnLeaveMenuBar(self, event):
3094        """
3095        Handles the ``wx.EVT_LEAVE_WINDOW`` event for :class:`FlatMenuBar`.
3096
3097        :param `event`: a :class:`MouseEvent` event to be processed.
3098
3099        :note: This method is for MSW only.
3100        """
3101
3102        pt = event.GetPosition()
3103        self.DoMouseMove(pt, event.LeftIsDown())
3104
3105
3106    def ResetToolbarItems(self):
3107        """ Used internally. """
3108
3109        for but in self._tbButtons:
3110            but._state = ControlNormal
3111
3112
3113    def GetActiveToolbarItem(self):
3114        """ Returns the active toolbar item. """
3115
3116        for but in self._tbButtons:
3117
3118            if but._state == ControlFocus or but._state == ControlPressed:
3119                return self._tbButtons.index(but)
3120
3121        return wx.NOT_FOUND
3122
3123
3124    def GetBackgroundColour(self):
3125        """ Returns the menu bar background colour. """
3126
3127        return self.GetRenderer().menuBarFaceColour
3128
3129
3130    def SetBackgroundColour(self, colour):
3131        """
3132        Sets the menu bar background colour.
3133
3134        :param `colour`: a valid :class:`wx.Colour`.
3135        """
3136
3137        self.GetRenderer().menuBarFaceColour = colour
3138
3139
3140    def OnLeaveWindow(self, event):
3141        """
3142        Handles the ``wx.EVT_LEAVE_WINDOW`` event for :class:`FlatMenuBar`.
3143
3144        :param `event`: a :class:`MouseEvent` event to be processed.
3145
3146        :note: This method is for GTK only.
3147        """
3148
3149        self._curretHiliteItem = -1
3150        self._dropDownButtonState = ControlNormal
3151
3152        # Reset items state
3153        for item in self._items:
3154            item.SetState(ControlNormal)
3155
3156        for but in self._tbButtons:
3157            but._state = ControlNormal
3158
3159        self.Refresh()
3160
3161
3162    def OnMenuDismissed(self, event):
3163        """
3164        Handles the ``EVT_FLAT_MENU_DISMISSED`` event for :class:`FlatMenuBar`.
3165
3166        :param `event`: a :class:`FlatMenuEvent` event to be processed.
3167        """
3168
3169        pt = wx.GetMousePosition()
3170        pt = self.ScreenToClient(pt)
3171
3172        idx, where = self.HitTest(pt)
3173        self.RemoveHelp()
3174
3175        if where not in [MenuItem, DropDownArrowButton]:
3176            self._dropDownButtonState = ControlNormal
3177            self._curretHiliteItem = -1
3178            for item in self._items:
3179                item.SetState(ControlNormal)
3180
3181            self.Refresh()
3182
3183
3184    def OnLeftDown(self, event):
3185        """
3186        Handles the ``wx.EVT_LEFT_DOWN`` event for :class:`FlatMenuBar`.
3187
3188        :param `event`: a :class:`MouseEvent` event to be processed.
3189        """
3190
3191        pt = event.GetPosition()
3192        idx, where = self.HitTest(pt)
3193
3194        if where == DropDownArrowButton:
3195            dc = wx.ClientDC(self)
3196            self.DrawMoreButton(dc, ControlPressed)
3197            self.PopupMoreMenu()
3198
3199        elif where == MenuItem:
3200            # Position the menu, the GetPosition() return the coords
3201            # of the button relative to its parent, we need to translate
3202            # them into the screen coords
3203            self.ActivateMenu(self._items[idx])
3204
3205        elif where == ToolbarItem:
3206            redrawAll = False
3207            item = self._tbButtons[idx]._tbItem
3208            # try to toggle if its a check item:
3209            item.Toggle()
3210            # switch is if its a unselected radio item
3211            if not item.IsSelected() and item.IsRadioItem():
3212                group = item.GetGroup()
3213                for i in range(len(self._tbButtons)):
3214                    if self._tbButtons[i]._tbItem.GetGroup() == group and \
3215                      i != idx and self._tbButtons[i]._tbItem.IsSelected():
3216                        self._tbButtons[i]._state = ControlNormal
3217                        self._tbButtons[i]._tbItem.Select(False)
3218                        redrawAll = True
3219                item.Select(True)
3220            # Over a toolbar item
3221            if redrawAll:
3222                self.Refresh()
3223                if "__WXMSW__" in wx.Platform:
3224                    dc = wx.BufferedDC(wx.ClientDC(self))
3225                else:
3226                    dc = wx.ClientDC(self)
3227            else:
3228                dc = wx.ClientDC(self)
3229                self.DrawToolbarItem(dc, idx, ControlPressed)
3230
3231            # TODO:: Do the action specified in this button
3232            self.DoToolbarAction(idx)
3233
3234
3235    def OnLeftUp(self, event):
3236        """
3237        Handles the ``wx.EVT_LEFT_UP`` event for :class:`FlatMenuBar`.
3238
3239        :param `event`: a :class:`MouseEvent` event to be processed.
3240        """
3241
3242        pt = event.GetPosition()
3243        idx, where = self.HitTest(pt)
3244
3245        if where == ToolbarItem:
3246            # Over a toolbar item
3247            dc = wx.ClientDC(self)
3248            self.DrawToolbarItem(dc, idx, ControlFocus)
3249
3250
3251    def DrawToolbarItem(self, dc, idx, state):
3252        """
3253        Draws a toolbar item button.
3254
3255        :param `dc`: an instance of :class:`wx.DC`;
3256        :param integer `idx`: the tool index in the toolbar;
3257        :param integer `state`: the button state.
3258
3259        :see: :meth:`wx.MenuEntryInfo.SetState() <MenuEntryInfo.SetState>` for a list of valid menu states.
3260        """
3261
3262        if idx >= len(self._tbButtons) or idx < 0:
3263            return
3264
3265        if self._tbButtons[idx]._tbItem.IsSelected():
3266            state = ControlPressed
3267        rect = self._tbButtons[idx]._rect
3268        self.GetRenderer().DrawButton(dc, rect, state)
3269
3270        # draw the bitmap over the highlight
3271        buttonRect = wx.Rect(*rect)
3272        x = rect.x + (buttonRect.width - self._tbButtons[idx]._tbItem.GetBitmap().GetWidth())/2
3273        y = rect.y + (buttonRect.height - self._tbButtons[idx]._tbItem.GetBitmap().GetHeight())/2
3274
3275        if state == ControlFocus:
3276
3277            # place a dark image under the original image to provide it
3278            # with some shadow
3279            # shadow = ConvertToMonochrome(self._tbButtons[idx]._tbItem.GetBitmap())
3280            # dc.DrawBitmap(shadow, x, y, True)
3281
3282            # in case we the button is in focus, place it
3283            # once pixle up and left
3284            x -= 1
3285            y -= 1
3286        dc.DrawBitmap(self._tbButtons[idx]._tbItem.GetBitmap(), x, y, True)
3287
3288
3289    def ActivateMenu(self, menuInfo):
3290        """
3291        Activates a menu.
3292
3293        :param `menuInfo`: an instance of :class:`wx.MenuEntryInfo`.
3294        """
3295
3296        # first make sure all other menus are not popedup
3297        if menuInfo.GetMenu().IsShown():
3298            return
3299
3300        idx = wx.NOT_FOUND
3301
3302        for item in self._items:
3303            item.GetMenu().Dismiss(False, True)
3304            if item.GetMenu() == menuInfo.GetMenu():
3305                idx = self._items.index(item)
3306
3307        # Remove the popup menu as well
3308        if self._moreMenu and self._moreMenu.IsShown():
3309            self._moreMenu.Dismiss(False, True)
3310
3311        # make sure that the menu item button is highlited
3312        if idx != wx.NOT_FOUND:
3313            self._dropDownButtonState = ControlNormal
3314            self._curretHiliteItem = idx
3315            for item in self._items:
3316                item.SetState(ControlNormal)
3317
3318            self._items[idx].SetState(ControlFocus)
3319            self.Refresh()
3320
3321        rect = menuInfo.GetRect()
3322        menuPt = self.ClientToScreen(wx.Point(rect.x, rect.y))
3323        menuInfo.GetMenu().SetOwnerHeight(rect.height)
3324        menuInfo.GetMenu().Popup(wx.Point(menuPt.x, menuPt.y), self)
3325
3326
3327    def DoToolbarAction(self, idx):
3328        """
3329        Performs a toolbar button pressed action.
3330
3331        :param integer `idx`: the tool index in the toolbar.
3332        """
3333
3334        # we handle only button clicks
3335        tbItem = self._tbButtons[idx]._tbItem
3336        if tbItem.IsRegularItem() or tbItem.IsCheckItem() or tbItem.IsRadioItem():
3337            # Create the event
3338            event = wx.CommandEvent(wxEVT_FLAT_MENU_SELECTED, tbItem.GetId())
3339            event.SetEventObject(self)
3340
3341            # all events are handled by this control and its parents
3342            self.GetEventHandler().ProcessEvent(event)
3343
3344
3345    def FindMenu(self, title):
3346        """
3347        Returns the index of the menu with the given title or ``wx.NOT_FOUND`` if
3348        no such menu exists in this menubar.
3349
3350        :param string `title`: may specify either the menu title (with accelerator characters,
3351         i.e. "&File") or just the menu label ("File") indifferently.
3352        """
3353
3354        for ii, item in enumerate(self._items):
3355            accelIdx, labelOnly = GetAccelIndex(item.GetTitle())
3356
3357            if labelOnly == title or item.GetTitle() == title:
3358                return ii
3359
3360        return wx.NOT_FOUND
3361
3362
3363    def GetMenu(self, menuIdx):
3364        """
3365        Returns the menu at the specified index `menuIdx` (zero-based).
3366
3367        :param integer `menuIdx`: the index of the sought menu.
3368
3369        :return: The found menu item object, or ``None`` if one was not found.
3370        """
3371
3372        if menuIdx >= len(self._items) or menuIdx < 0:
3373            return None
3374
3375        return self._items[menuIdx].GetMenu()
3376
3377
3378    def GetMenuCount(self):
3379        """ Returns the number of menus in the menubar. """
3380
3381        return len(self._items)
3382
3383
3384    def Insert(self, pos, menu, title):
3385        """
3386        Inserts the menu at the given position into the menu bar.
3387
3388        :param integer `pos`: the position of the new menu in the menu bar;
3389        :param `menu`: the menu to add, an instance of :class:`FlatMenu`. :class:`FlatMenuBar` owns the menu and will free it;
3390        :param string `title`: the title of the menu.
3391
3392        :note: Inserting menu at position 0 will insert it in the very beginning of it,
3393         inserting at position :meth:`~FlatMenuBar.GetMenuCount` is the same as calling :meth:`~FlatMenuBar.Append`.
3394        """
3395
3396        menu.SetMenuBar(self)
3397        self._items.insert(pos, MenuEntryInfo(title, menu))
3398        self.UpdateAcceleratorTable()
3399
3400        self.ClearBitmaps(pos)
3401        self.Refresh()
3402        return True
3403
3404
3405    def Remove(self, pos):
3406        """
3407        Removes the menu from the menu bar and returns the menu object - the
3408        caller is responsible for deleting it.
3409
3410        :param integer `pos`: the position of the menu in the menu bar.
3411
3412        :note: This function may be used together with :meth:`~FlatMenuBar.Insert` to change the menubar
3413         dynamically.
3414        """
3415
3416        if pos >= len(self._items):
3417            return None
3418
3419        menu = self._items[pos].GetMenu()
3420        self._items.pop(pos)
3421        self.UpdateAcceleratorTable()
3422
3423        # Since we use bitmaps to optimize our drawings, we need
3424        # to reset all bitmaps from pos and until end of vector
3425        # to force size/position changes to the menu bar
3426        self.ClearBitmaps(pos)
3427        self.Refresh()
3428
3429        # remove the connection to this menubar
3430        menu.SetMenuBar(None)
3431        return menu
3432
3433
3434    def UpdateAcceleratorTable(self):
3435        """ Updates the parent accelerator table. """
3436
3437        # first get the number of items we have
3438        updatedTable = []
3439        parent = self.GetParent()
3440
3441        for item in self._items:
3442
3443            updatedTable = item.GetMenu().GetAccelArray() + updatedTable
3444
3445            # create accelerator for every menu (if it exist)
3446            title = item.GetTitle()
3447            mnemonic, labelOnly = GetAccelIndex(title)
3448
3449            if mnemonic != wx.NOT_FOUND:
3450
3451                # Get the accelrator character
3452                accelChar = labelOnly[mnemonic]
3453                accelString = "\tAlt+" + accelChar
3454                title += accelString
3455
3456                accel = wx.AcceleratorEntry()
3457                accel.FromString(title)
3458                itemId = item.GetCmdId()
3459
3460                if accel:
3461
3462                    # connect an event to this cmd
3463                    parent.Connect(itemId, -1, wxEVT_FLAT_MENU_SELECTED, self.OnAccelCmd)
3464                    accel.Set(accel.GetFlags(), accel.GetKeyCode(), itemId)
3465                    updatedTable.append(accel)
3466
3467        entries = [wx.AcceleratorEntry() for ii in range(len(updatedTable))]
3468
3469        # Add the new menu items
3470        for i in range(len(updatedTable)):
3471            entries[i] = updatedTable[i]
3472
3473        table = wx.AcceleratorTable(entries)
3474        del entries
3475
3476        parent.SetAcceleratorTable(table)
3477
3478
3479    def ClearBitmaps(self, start=0):
3480        """
3481        Restores a :class:`NullBitmap` for all the items in the menu.
3482
3483        :param integer `start`: the index at which to start resetting the bitmaps.
3484        """
3485
3486        if self._isLCD:
3487            return
3488
3489        for item in self._items[start:]:
3490            item.SetTextBitmap(wx.NullBitmap)
3491            item.SetSelectedTextBitmap(wx.NullBitmap)
3492
3493
3494    def OnAccelCmd(self, event):
3495        """
3496        Single function to handle any accelerator key used inside the menubar.
3497
3498        :param `event`: a :class:`FlatMenuEvent` event to be processed.
3499        """
3500
3501        for item in self._items:
3502            if item.GetCmdId() == event.GetId():
3503                self.ActivateMenu(item)
3504
3505
3506    def ActivateNextMenu(self):
3507        """ Activates next menu and make sure all others are non-active. """
3508
3509        last_item = self.GetLastVisibleMenu()
3510        # find the current active menu
3511        for i in range(last_item+1):
3512            if self._items[i].GetMenu().IsShown():
3513                nextMenu = i + 1
3514                if nextMenu >= last_item:
3515                    nextMenu = 0
3516                self.ActivateMenu(self._items[nextMenu])
3517                return
3518
3519
3520    def GetLastVisibleMenu(self):
3521        """ Returns the index of the last visible menu on the menu bar. """
3522
3523        last_item = 0
3524
3525        # find the last visible item
3526        rect = wx.Rect()
3527
3528        for item in self._items:
3529
3530            if item.GetRect() == rect:
3531                break
3532
3533            last_item += 1
3534
3535        return last_item
3536
3537
3538    def ActivatePreviousMenu(self):
3539        """ Activates previous menu and make sure all others are non-active. """
3540
3541        # find the current active menu
3542        last_item = self.GetLastVisibleMenu()
3543
3544        for i in range(last_item):
3545            if self._items[i].GetMenu().IsShown():
3546                prevMenu = i - 1
3547                if prevMenu < 0:
3548                    prevMenu = last_item - 1
3549
3550                if prevMenu < 0:
3551                    return
3552
3553                self.ActivateMenu(self._items[prevMenu])
3554                return
3555
3556
3557    def CreateMoreMenu(self):
3558        """ Creates the drop down menu and populate it. """
3559
3560        if not self._moreMenu:
3561            # first time
3562            self._moreMenu = FlatMenu(self)
3563            self._popupDlgCmdId = wx.NewIdRef()
3564
3565            # Connect an event handler for this event
3566            self.Connect(self._popupDlgCmdId, -1, wxEVT_FLAT_MENU_SELECTED, self.OnCustomizeDlg)
3567
3568        # Remove all items from the popup menu
3569        self._moreMenu.Clear()
3570
3571        invM = self.GetInvisibleMenuItemCount()
3572
3573        for i in range(len(self._items) - invM, len(self._items)):
3574            item = FlatMenuItem(self._moreMenu, wx.ID_ANY, self._items[i].GetTitle(),
3575                                "", wx.ITEM_NORMAL, self._items[i].GetMenu())
3576            self._moreMenu.AppendItem(item)
3577
3578        # Add invisible toolbar items
3579        invT = self.GetInvisibleToolbarItemCount()
3580
3581        if self._showToolbar and invT > 0:
3582            if self.GetInvisibleMenuItemCount() > 0:
3583                self._moreMenu.AppendSeparator()
3584
3585            for i in range(len(self._tbButtons) - invT, len(self._tbButtons)):
3586                if self._tbButtons[i]._tbItem.IsSeparator():
3587                    self._moreMenu.AppendSeparator()
3588                elif not self._tbButtons[i]._tbItem.IsCustomControl():
3589                    tbitem = self._tbButtons[i]._tbItem
3590                    item = FlatMenuItem(self._tbMenu, tbitem.GetId(), tbitem.GetLabel(), "", wx.ITEM_NORMAL, None, tbitem.GetBitmap(), tbitem.GetDisabledBitmap())
3591                    item.Enable(tbitem.IsEnabled())
3592                    self._moreMenu.AppendItem(item)
3593
3594
3595        if self._showCustomize:
3596            if invT + invM > 0:
3597                self._moreMenu.AppendSeparator()
3598            item = FlatMenuItem(self._moreMenu, self._popupDlgCmdId, _(six.u("Customize...")))
3599            self._moreMenu.AppendItem(item)
3600
3601
3602    def GetInvisibleMenuItemCount(self):
3603        """
3604        Returns the number of invisible menu items.
3605
3606        :note: Valid only after the :class:`PaintEvent` has been processed after a resize.
3607        """
3608
3609        return len(self._items) - self.GetLastVisibleMenu()
3610
3611
3612    def GetInvisibleToolbarItemCount(self):
3613        """
3614        Returns the number of invisible toolbar items.
3615
3616        :note: Valid only after the :class:`PaintEvent` has been processed after a resize.
3617        """
3618
3619        count = 0
3620        for i in range(len(self._tbButtons)):
3621            if self._tbButtons[i]._visible == False:
3622                break
3623            count = i
3624
3625        return len(self._tbButtons) - count - 1
3626
3627
3628    def PopupMoreMenu(self):
3629        """ Pops up the 'more' menu. """
3630
3631        if (not self._showCustomize) and self.GetInvisibleMenuItemCount() + self.GetInvisibleToolbarItemCount() < 1:
3632            return
3633
3634        self.CreateMoreMenu()
3635
3636        pt = self._dropDownButtonArea.GetTopLeft()
3637        pt = self.ClientToScreen(pt)
3638        pt.y += self._dropDownButtonArea.GetHeight()
3639        self._moreMenu.Popup(pt, self)
3640
3641
3642    def OnCustomizeDlg(self, event):
3643        """
3644        Handles the customize dialog here.
3645
3646        :param `event`: a :class:`FlatMenuEvent` event to be processed.
3647        """
3648
3649        if not self._dlg:
3650            self._dlg = FMCustomizeDlg(self)
3651        else:
3652            # intialize the dialog
3653            self._dlg.Initialise()
3654
3655        if self._dlg.ShowModal() == wx.ID_OK:
3656            # Handle customize requests here
3657            pass
3658
3659        if "__WXGTK__" in wx.Platform:
3660            # Reset the more button
3661            dc = wx.ClientDC(self)
3662            self.DrawMoreButton(dc, ControlNormal)
3663
3664
3665    def AppendToolbarItem(self, item):
3666        """
3667        Appends a tool to the :class:`FlatMenuBar`.
3668
3669        .. deprecated:: 0.9.5
3670           This method is now deprecated.
3671
3672        :see: :meth:`~FlatMenuBar.AddTool`
3673        """
3674
3675        newItem = ToolBarItem(item, wx.Rect(), ControlNormal)
3676        self._tbButtons.append(newItem)
3677
3678
3679    def AddTool(self, toolId, label="", bitmap1=wx.NullBitmap, bitmap2=wx.NullBitmap,
3680                kind=wx.ITEM_NORMAL, shortHelp="", longHelp=""):
3681        """
3682        Adds a tool to the toolbar.
3683
3684        :param integer `toolId`: an integer by which the tool may be identified in subsequent
3685         operations;
3686        :param string `label`: the tool label string;
3687        :param integer `kind`: may be ``wx.ITEM_NORMAL`` for a normal button (default),
3688         ``wx.ITEM_CHECK`` for a checkable tool (such tool stays pressed after it had been
3689         toggled) or ``wx.ITEM_RADIO`` for a checkable tool which makes part of a radio
3690         group of tools each of which is automatically unchecked whenever another button
3691         in the group is checked;
3692        :param `bitmap1`: the primary tool bitmap, an instance of :class:`wx.Bitmap`;
3693        :param `bitmap2`: the bitmap used when the tool is disabled. If it is equal to
3694         :class:`NullBitmap`, the disabled bitmap is automatically generated by greing out
3695         the normal one;
3696        :param string `shortHelp`: a string used for the tools tooltip;
3697        :param string `longHelp`: this string is shown in the :class:`StatusBar` (if any) of the
3698         parent frame when the mouse pointer is inside the tool.
3699        """
3700
3701        self._tbButtons.append(ToolBarItem(FlatToolbarItem(bitmap1, toolId, label, bitmap2, kind, shortHelp, longHelp), wx.Rect(), ControlNormal))
3702
3703
3704    def AddSeparator(self):
3705        """ Adds a separator for spacing groups of tools in toolbar. """
3706
3707        if len(self._tbButtons) > 0 and not self._tbButtons[len(self._tbButtons)-1]._tbItem.IsSeparator():
3708            self._tbButtons.append(ToolBarItem(FlatToolbarItem(), wx.Rect(), ControlNormal))
3709
3710
3711    def AddControl(self, control):
3712        """
3713        Adds any control to the toolbar, typically e.g. a combobox.
3714
3715        :param `control`: the control to be added, a subclass of :class:`wx.Window` (but no :class:`TopLevelWindow`).
3716        """
3717
3718        self._tbButtons.append(ToolBarItem(FlatToolbarItem(control), wx.Rect(), ControlNormal))
3719
3720
3721    def AddCheckTool(self, toolId, label="", bitmap1=wx.NullBitmap, bitmap2=wx.NullBitmap, shortHelp="", longHelp=""):
3722        """
3723        Adds a new check (or toggle) tool to the toolbar.
3724
3725        :see: :meth:`~FlatMenuBar.AddTool` for parameter descriptions.
3726        """
3727
3728        self.AddTool(toolId, label, bitmap1, bitmap2, kind=wx.ITEM_CHECK, shortHelp=shortHelp, longHelp=longHelp)
3729
3730
3731    def AddRadioTool(self, toolId, label= "", bitmap1=wx.NullBitmap, bitmap2=wx.NullBitmap, shortHelp="", longHelp=""):
3732        """
3733        Adds a new radio tool to the toolbar.
3734
3735        Consecutive radio tools form a radio group such that exactly one button in the
3736        group is pressed at any moment, in other words whenever a button in the group is
3737        pressed the previously pressed button is automatically released.
3738
3739        You should avoid having the radio groups of only one element as it would be
3740        impossible for the user to use such button.
3741
3742        By default, the first button in the radio group is initially pressed, the others are not.
3743
3744        :see: :meth:`~FlatMenuBar.AddTool` for parameter descriptions.
3745        """
3746
3747        self.AddTool(toolId, label, bitmap1, bitmap2, kind=wx.ITEM_RADIO, shortHelp=shortHelp, longHelp=longHelp)
3748
3749        if len(self._tbButtons)<1 or not self._tbButtons[len(self._tbButtons)-2]._tbItem.IsRadioItem():
3750            self._tbButtons[len(self._tbButtons)-1]._tbItem.Select(True)
3751            self._lastRadioGroup += 1
3752
3753        self._tbButtons[len(self._tbButtons)-1]._tbItem.SetGroup(self._lastRadioGroup)
3754
3755
3756    def SetUpdateInterval(self, interval):
3757        """
3758        Sets the UpdateUI interval for toolbar items. All UpdateUI events are
3759        sent from within :meth:`~FlatMenuBar.OnIdle` handler, the default is 20 milliseconds.
3760
3761        :param integer `interval`: the updateUI interval in milliseconds.
3762        """
3763
3764        self._interval = interval
3765
3766
3767    def PositionAUI(self, mgr, fixToolbar=True):
3768        """
3769        Positions the control inside a wxAUI / PyAUI frame manager.
3770
3771        :param `mgr`: an instance of :class:`~wx.lib.agw.aui.framemanager.AuiManager` or :class:`framemanager`;
3772        :param bool `fixToolbar`: ``True`` if :class:`FlatMenuBar` can not be floated.
3773        """
3774
3775        if CPP_AUI and isinstance(mgr, wx.aui.AuiManager):
3776            pn = AuiPaneInfo()
3777        else:
3778            pn = PyAuiPaneInfo()
3779
3780        xx = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X)
3781
3782        # We add our menu bar as a toolbar, with the following settings
3783
3784        pn.Name("flat_menu_bar")
3785        pn.Caption("Menu Bar")
3786        pn.Top()
3787        pn.MinSize(wx.Size(xx/2, self._barHeight))
3788        pn.LeftDockable(False)
3789        pn.RightDockable(False)
3790        pn.ToolbarPane()
3791
3792        if not fixToolbar:
3793            # We add our menu bar as a toolbar, with the following settings
3794            pn.BestSize(wx.Size(xx, self._barHeight))
3795            pn.FloatingSize(wx.Size(300, self._barHeight))
3796            pn.Floatable(True)
3797            pn.MaxSize(wx.Size(xx, self._barHeight))
3798            pn.Gripper(True)
3799
3800        else:
3801            pn.BestSize(wx.Size(xx, self._barHeight))
3802            pn.Gripper(False)
3803
3804        pn.Resizable(False)
3805        pn.PaneBorder(False)
3806        mgr.AddPane(self, pn)
3807
3808        self._mgr = mgr
3809
3810
3811    def DoGiveHelp(self, hit):
3812        """
3813        Gives tooltips and help in :class:`StatusBar`.
3814
3815        :param `hit`: the toolbar tool currently hovered by the mouse.
3816        """
3817
3818        shortHelp = hit.GetShortHelp()
3819        if shortHelp:
3820            self.SetToolTip(shortHelp)
3821            self._haveTip = True
3822
3823        longHelp = hit.GetLongHelp()
3824        if not longHelp:
3825            return
3826
3827        topLevel = wx.GetTopLevelParent(self)
3828
3829        if isinstance(topLevel, wx.Frame) and topLevel.GetStatusBar():
3830            statusBar = topLevel.GetStatusBar()
3831
3832            if self._statusTimer and self._statusTimer.IsRunning():
3833                self._statusTimer.Stop()
3834                statusBar.PopStatusText(0)
3835
3836            statusBar.PushStatusText(longHelp, 0)
3837            self._statusTimer = StatusBarTimer(self)
3838            self._statusTimer.Start(_DELAY, wx.TIMER_ONE_SHOT)
3839
3840
3841    def RemoveHelp(self):
3842        """ Removes the tooltips and statusbar help (if any) for a button. """
3843
3844        if self._haveTip:
3845            self.SetToolTip("")
3846            self._haveTip = False
3847
3848        if self._statusTimer and self._statusTimer.IsRunning():
3849            topLevel = wx.GetTopLevelParent(self)
3850            statusBar = topLevel.GetStatusBar()
3851            self._statusTimer.Stop()
3852            statusBar.PopStatusText(0)
3853            self._statusTimer = None
3854
3855
3856    def OnStatusBarTimer(self):
3857        """ Handles the timer expiring to delete the `longHelp` string in the :class:`StatusBar`. """
3858
3859        topLevel = wx.GetTopLevelParent(self)
3860        statusBar = topLevel.GetStatusBar()
3861        statusBar.PopStatusText(0)
3862
3863
3864
3865class mcPopupWindow(wx.MiniFrame):
3866    """ Since Max OS does not support :class:`PopupWindow`, this is an alternative. """
3867
3868    def __init__(self, parent):
3869        """
3870        Default class constructor.
3871
3872        :param `parent`: the :class:`mcPopupWindow` parent window.
3873        """
3874
3875        wx.MiniFrame.__init__(self, parent, style = wx.POPUP_WINDOW)
3876        self.SetExtraStyle(wx.WS_EX_TRANSIENT)
3877        self._parent = parent
3878        self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)
3879
3880
3881    def OnLeaveWindow(self, event):
3882        """
3883        Handles the ``wx.EVT_LEAVE_WINDOW`` event for :class:`mcPopupWindow`.
3884
3885        :param `event`: a :class:`MouseEvent` event to be processed.
3886        """
3887
3888        event.Skip()
3889
3890
3891havePopupWindow = 1
3892""" Flag used to indicate whether the platform supports the native :class:`PopupWindow`. """
3893
3894if wx.Platform == '__WXMAC__':
3895    havePopupWindow = 0
3896    wx.PopupWindow = mcPopupWindow
3897
3898
3899# ---------------------------------------------------------------------------- #
3900# Class ShadowPopupWindow
3901# ---------------------------------------------------------------------------- #
3902
3903class ShadowPopupWindow(wx.PopupWindow):
3904    """ Base class for generic :class:`FlatMenu` derived from :class:`PopupWindow`. """
3905
3906    def __init__(self, parent=None):
3907        """
3908        Default class constructor.
3909
3910        :param `parent`: the :class:`ShadowPopupWindow` parent (tipically your main frame).
3911        """
3912
3913        if not parent:
3914            parent = wx.GetApp().GetTopWindow()
3915
3916        if not parent:
3917            raise Exception("Can't create menu without parent!")
3918
3919        wx.PopupWindow.__init__(self, parent)
3920
3921        if "__WXMSW__" in wx.Platform and _libimported == "MH":
3922
3923            GCL_STYLE= -26
3924            cstyle= win32gui.GetClassLong(self.GetHandle(), GCL_STYLE)
3925            if cstyle & CS_DROPSHADOW == 0:
3926                win32api.SetClassLong(self.GetHandle(),
3927                                      GCL_STYLE, cstyle | CS_DROPSHADOW)
3928
3929        # popup windows are created hidden by default
3930        self.Hide()
3931
3932
3933#--------------------------------------------------------
3934# Class FlatMenuButton
3935#--------------------------------------------------------
3936
3937class FlatMenuButton(object):
3938    """
3939    A nice small class that functions like :class:`wx.BitmapButton`, the reason I did
3940    not used :class:`wx.BitmapButton` is that on Linux, it has some extra margins that
3941    I can't seem to be able to remove.
3942    """
3943
3944    def __init__(self, menu, up, normalBmp, disabledBmp=wx.NullBitmap, scrollOnHover=False):
3945        """
3946        Default class constructor.
3947
3948        :param `menu`: the parent menu associated with this button, an instance of :class:`FlatMenu`;
3949        :param bool `up`: ``True`` for up arrow or ``False`` for down arrow;
3950        :param `normalBmp`: normal state bitmap, an instance of :class:`wx.Bitmap`;
3951        :param `disabledBmp`: disabled state bitmap, an instance of :class:`wx.Bitmap`.
3952        """
3953
3954        self._normalBmp = normalBmp
3955        self._up = up
3956        self._parent = menu
3957        self._pos = wx.Point()
3958        self._size = wx.Size()
3959        self._timerID = wx.NewIdRef()
3960        self._scrollOnHover = scrollOnHover
3961
3962        if not disabledBmp.IsOk():
3963            self._disabledBmp = wx.Bitmap(self._normalBmp.ConvertToImage().ConvertToGreyscale())
3964        else:
3965            self._disabledBmp = disabledBmp
3966
3967        self._state = ControlNormal
3968        self._timer = wx.Timer(self._parent, self._timerID)
3969        self._timer.Stop()
3970
3971
3972    def __del__(self):
3973        """ Used internally. """
3974
3975        if self._timer:
3976            if self._timer.IsRunning():
3977                self._timer.Stop()
3978
3979            del self._timer
3980
3981
3982    def Contains(self, pt):
3983        """ Used internally. """
3984
3985        rect = wx.Rect(self._pos, self._size)
3986        if not rect.Contains(pt):
3987            return False
3988
3989        return True
3990
3991
3992    def Draw(self, dc):
3993        """
3994        Draws self at rect using dc.
3995
3996        :param `dc`: an instance of :class:`wx.DC`.
3997        """
3998
3999        rect = wx.Rect(self._pos, self._size)
4000        xx = rect.x + (rect.width - self._normalBmp.GetWidth())/2
4001        yy = rect.y + (rect.height - self._normalBmp.GetHeight())/2
4002
4003        self._parent.GetRenderer().DrawScrollButton(dc, rect, self._state)
4004        dc.DrawBitmap(self._normalBmp, xx, yy, True)
4005
4006
4007    def ProcessLeftDown(self, pt):
4008        """
4009        Handles left down mouse events.
4010
4011        :param `pt`: an instance of :class:`wx.Point` where the left mouse button was pressed.
4012        """
4013
4014        if not self.Contains(pt):
4015            return False
4016
4017        self._state = ControlPressed
4018        self._parent.Refresh()
4019
4020        if self._up:
4021            self._parent.ScrollUp()
4022        else:
4023            self._parent.ScrollDown()
4024
4025        self._timer.Start(100)
4026        return True
4027
4028
4029    def ProcessLeftUp(self, pt):
4030        """
4031        Handles left up mouse events.
4032
4033        :param `pt`: an instance of :class:`wx.Point` where the left mouse button was released.
4034        """
4035
4036        # always stop the timer
4037        self._timer.Stop()
4038
4039        if not self.Contains(pt):
4040            return False
4041
4042        self._state = ControlFocus
4043        self._parent.Refresh()
4044
4045        return True
4046
4047
4048    def ProcessMouseMove(self, pt):
4049        """
4050        Handles mouse motion events. This is called any time the mouse moves in the parent menu,
4051        so we must check to see if the mouse is over the button.
4052
4053        :param `pt`: an instance of :class:`wx.Point` where the mouse pointer was moved.
4054        """
4055
4056        if not self.Contains(pt):
4057
4058            self._timer.Stop()
4059            if self._state != ControlNormal:
4060
4061                self._state = ControlNormal
4062                self._parent.Refresh()
4063
4064            return False
4065
4066        if self._scrollOnHover and not self._timer.IsRunning():
4067            self._timer.Start(100)
4068
4069        # Process mouse move event
4070        if self._state != ControlFocus:
4071            if self._state != ControlPressed:
4072                self._state = ControlFocus
4073                self._parent.Refresh()
4074
4075        return True
4076
4077
4078    def GetTimerId(self):
4079        """ Returns the timer object identifier. """
4080
4081        return self._timerID
4082
4083
4084    def GetTimer(self):
4085        """ Returns the timer object. """
4086
4087        return self._timer
4088
4089
4090    def Move(self, input1, input2=None):
4091        """
4092        Moves :class:`FlatMenuButton` to the specified position.
4093
4094        :param `input1`: if it is an instance of :class:`wx.Point`, it represents the :class:`FlatMenuButton`
4095         position and the `input2` parameter is not used. Otherwise it is an integer representing
4096         the button `x` position;
4097        :param `input2`: if not ``None``, it is an integer representing the button `y` position.
4098        """
4099
4100        if type(input) == type(1):
4101            self._pos = wx.Point(input1, input2)
4102        else:
4103            self._pos = input1
4104
4105
4106    def SetSize(self, input1, input2=None):
4107        """
4108        Sets the size for :class:`FlatMenuButton`.
4109
4110        :param `input1`: if it is an instance of :class:`wx.Size`, it represents the :class:`FlatMenuButton`
4111         size and the `input2` parameter is not used. Otherwise it is an integer representing
4112         the button width;
4113        :param `input2`: if not ``None``, it is an integer representing the button height.
4114        """
4115
4116        if type(input) == type(1):
4117            self._size = wx.Size(input1, input2)
4118        else:
4119            self._size = input1
4120
4121
4122    def GetClientRect(self):
4123        """ Returns the client rectangle for :class:`FlatMenuButton`. """
4124
4125        return wx.Rect(self._pos, self._size)
4126
4127
4128#--------------------------------------------------------
4129# Class FlatMenuItemGroup
4130#--------------------------------------------------------
4131
4132class FlatMenuItemGroup(object):
4133    """
4134    A class that manages a group of radio menu items.
4135    """
4136
4137    def __init__(self):
4138        """ Default class constructor. """
4139
4140        self._items = []
4141
4142
4143    def GetSelectedItem(self):
4144        """ Returns the selected item. """
4145
4146        for item in self._items:
4147            if item.IsChecked():
4148                return item
4149
4150        return None
4151
4152
4153    def Add(self, item):
4154        """
4155        Adds a new item to the group.
4156
4157        :param `item`: an instance of :class:`FlatMenu`.
4158        """
4159
4160        if item.IsChecked():
4161            # uncheck all other items
4162            for exitem in self._items:
4163                exitem._bIsChecked = False
4164
4165        self._items.append(item)
4166
4167
4168    def Exist(self, item):
4169        """
4170        Checks if an item is in the group.
4171
4172        :param `item`: an instance of :class:`FlatMenu`.
4173        """
4174
4175        if item in self._items:
4176            return True
4177
4178        return False
4179
4180
4181    def SetSelection(self, item):
4182        """
4183        Selects a particular item.
4184
4185        :param `item`: an instance of :class:`FlatMenu`.
4186        """
4187
4188        # make sure this item exist in our group
4189        if not self.Exist(item):
4190            return
4191
4192        # uncheck all other items
4193        for exitem in self._items:
4194            exitem._bIsChecked = False
4195
4196        item._bIsChecked = True
4197
4198
4199    def Remove(self, item):
4200        """
4201        Removes a particular item.
4202
4203        :param `item`: an instance of :class:`FlatMenu`.
4204        """
4205
4206        if item not in self._items:
4207            return
4208
4209        self._items.remove(item)
4210
4211        if item.IsChecked() and len(self._items) > 0:
4212            #if the removed item was the selected one,
4213            # select the first one in the group
4214            self._items[0]._bIsChecked = True
4215
4216
4217#--------------------------------------------------------
4218# Class FlatMenuBase
4219#--------------------------------------------------------
4220
4221class FlatMenuBase(ShadowPopupWindow):
4222    """
4223    Base class for generic flat menu derived from :class:`PopupWindow`.
4224    """
4225
4226    def __init__(self, parent=None):
4227        """
4228        Default class constructor.
4229
4230        :param `parent`: the :class:`ShadowPopupWindow` parent window.
4231        """
4232
4233        self._rendererMgr = FMRendererMgr()
4234        self._parentMenu = parent
4235        self._openedSubMenu = None
4236        self._owner = None
4237        self._popupPtOffset = 0
4238        self._showScrollButtons = False
4239        self._upButton = None
4240        self._downButton = None
4241        self._is_dismiss = False
4242
4243        ShadowPopupWindow.__init__(self, parent)
4244
4245
4246    def OnDismiss(self):
4247        """ Fires an event ``EVT_FLAT_MENU_DISMISSED`` and handle menu dismiss. """
4248
4249        # Release mouse capture if needed
4250        if self.HasCapture():
4251            self.ReleaseMouse()
4252
4253        self._is_dismiss = True
4254
4255        # send an event about our dismissal to the parent (unless we are a sub menu)
4256        if self.IsShown() and not self._parentMenu:
4257
4258            event = FlatMenuEvent(wxEVT_FLAT_MENU_DISMISSED, self.GetId())
4259            event.SetEventObject(self)
4260
4261            # Send it
4262            if self.GetMenuOwner():
4263                self.GetMenuOwner().GetEventHandler().ProcessEvent(event)
4264            else:
4265                self.GetEventHandler().ProcessEvent(event)
4266
4267
4268    def Popup(self, pt, parent):
4269        """
4270        Popups menu at the specified point.
4271
4272        :param `pt`: an instance of :class:`wx.Point`, assumed to be in screen coordinates. However,
4273         if `parent` is not ``None``, `pt` is translated into the screen coordinates using
4274         `parent.ClientToScreen()`;
4275        :param `parent`: if not ``None``, an instance of :class:`wx.Window`.
4276        """
4277
4278        # some controls update themselves from OnIdle() call - let them do it
4279        if wx.GetApp().GetMainLoop():
4280            wx.GetApp().GetMainLoop().ProcessIdle()
4281
4282        # The mouse was pressed in the parent coordinates,
4283        # e.g. pressing on the left top of a text ctrl
4284        # will result in (1, 1), these coordinates needs
4285        # to be converted into screen coords
4286        self._parentMenu = parent
4287
4288        # If we are topmost menu, we use the given pt
4289        # else we use the logical
4290        # parent (second argument provided to this function)
4291
4292        if self._parentMenu:
4293            pos = self._parentMenu.ClientToScreen(pt)
4294        else:
4295            pos = pt
4296
4297        # Fit the menu into screen
4298        pos = self.AdjustPosition(pos)
4299        if self._showScrollButtons:
4300
4301            sz = self.GetSize()
4302            # Get the screen height
4303            scrHeight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
4304
4305
4306            # position the scrollbar - If we are doing scroll bar buttons put them in the top right and
4307            # bottom right or else place them as menu items at the top and bottom.
4308            if self.GetRenderer().scrollBarButtons:
4309                if not self._upButton:
4310                    self._upButton = FlatMenuButton(self, True, ArtManager.Get().GetStockBitmap("arrow_up"))
4311
4312                if not self._downButton:
4313                    self._downButton = FlatMenuButton(self, False, ArtManager.Get().GetStockBitmap("arrow_down"))
4314
4315                self._upButton.SetSize((SCROLL_BTN_HEIGHT, SCROLL_BTN_HEIGHT))
4316                self._downButton.SetSize((SCROLL_BTN_HEIGHT, SCROLL_BTN_HEIGHT))
4317
4318                self._upButton.Move((sz.x - SCROLL_BTN_HEIGHT - 4, 4))
4319                self._downButton.Move((sz.x - SCROLL_BTN_HEIGHT - 4, scrHeight - pos.y - 2 - SCROLL_BTN_HEIGHT))
4320            else:
4321                if not self._upButton:
4322                    self._upButton = FlatMenuButton(self, True, getMenuUpArrowBitmap(), scrollOnHover=True)
4323
4324                if not self._downButton:
4325                    self._downButton = FlatMenuButton(self, False, getMenuDownArrowBitmap(), scrollOnHover=True)
4326
4327                self._upButton.SetSize((sz.x-2, self.GetItemHeight()))
4328                self._downButton.SetSize((sz.x-2, self.GetItemHeight()))
4329
4330                self._upButton.Move((1, 3))
4331                self._downButton.Move((1, scrHeight - pos.y - 3 - self.GetItemHeight()))
4332
4333        self.Move(pos)
4334        self.Show()
4335
4336        # Capture mouse event and direct them to us
4337        if not self.HasCapture():
4338            self.CaptureMouse()
4339
4340        self._is_dismiss = False
4341
4342
4343    def AdjustPosition(self, pos):
4344        """
4345        Adjusts position so the menu will be fully visible on screen.
4346
4347        :param `pos`: an instance of :class:`wx.Point` specifying the menu position.
4348        """
4349
4350        # Check that the menu can fully appear in the screen
4351        scrWidth  = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X)
4352        scrHeight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
4353
4354        scrollBarButtons = self.GetRenderer().scrollBarButtons
4355        scrollBarMenuItems = not scrollBarButtons
4356
4357        size = self.GetSize()
4358        if scrollBarMenuItems:
4359            size.y += self.GetItemHeight()*2
4360
4361        # always assume that we have scrollbuttons on
4362        self._showScrollButtons = False
4363        pos.y += self._popupPtOffset
4364
4365        if size.y + pos.y > scrHeight:
4366            # the menu will be truncated
4367            if self._parentMenu is None:
4368                # try to flip the menu
4369                flippedPosy = pos.y - size.y
4370                flippedPosy -= self._popupPtOffset
4371
4372                if flippedPosy >= 0 and flippedPosy + size.y < scrHeight:
4373                    pos.y = flippedPosy
4374                    return pos
4375                else:
4376                    # We need to popup scrollbuttons!
4377                    self._showScrollButtons = True
4378
4379            else:
4380                # we are a submenu
4381                # try to decrease the y value of the menu position
4382                newy = pos.y
4383                newy -= (size.y + pos.y) - scrHeight
4384
4385                if newy + size.y > scrHeight:
4386                    # probably the menu size is too high to fit
4387                    # the screen, we need scrollbuttons
4388                    self._showScrollButtons = True
4389                else:
4390                    pos.y = newy
4391
4392        menuMaxX = pos.x + size.x
4393
4394        if menuMaxX > scrWidth and pos.x < scrWidth:
4395
4396            if self._parentMenu:
4397
4398                # We are submenu
4399                self._shiftePos = (size.x + self._parentMenu.GetSize().x)
4400                pos.x -= self._shiftePos
4401                pos.x += 10
4402
4403            else:
4404
4405                self._shiftePos  = ((size.x + pos.x) - scrWidth)
4406                pos.x -= self._shiftePos
4407
4408        else:
4409
4410            if self._parentMenu:
4411                pos.x += 5
4412
4413        return pos
4414
4415
4416    def Dismiss(self, dismissParent, resetOwner):
4417        """
4418        Dismisses the popup window.
4419
4420        :param bool `dismissParent`: whether to dismiss the parent menu or not;
4421        :param bool `resetOwner`: ``True`` to delete the link between this menu and the
4422         owner menu, ``False`` otherwise.
4423        """
4424
4425        # Check if child menu is poped, if so, dismiss it
4426        if self._openedSubMenu:
4427            self._openedSubMenu.Dismiss(False, resetOwner)
4428
4429        self.OnDismiss()
4430
4431        # Reset menu owner
4432        if resetOwner:
4433            self._owner = None
4434
4435        self.Show(False)
4436
4437        if self._parentMenu and dismissParent:
4438
4439            self._parentMenu.OnChildDismiss()
4440            self._parentMenu.Dismiss(dismissParent, resetOwner)
4441
4442        self._parentMenu = None
4443
4444
4445    def OnChildDismiss(self):
4446        """ Handles children dismiss. """
4447
4448        self._openedSubMenu = None
4449
4450
4451    def GetRenderer(self):
4452        """ Returns the renderer for this class. """
4453
4454        return self._rendererMgr.GetRenderer()
4455
4456
4457    def GetRootMenu(self):
4458        """ Returns the top level menu. """
4459
4460        root = self
4461        while root._parentMenu:
4462            root = root._parentMenu
4463
4464        return root
4465
4466
4467    def SetOwnerHeight(self, height):
4468        """
4469        Sets the menu owner height, this will be used to position the menu below
4470        or above the owner.
4471
4472        :param integer `height`: an integer representing the menu owner height.
4473        """
4474
4475        self._popupPtOffset = height
4476
4477
4478    # by default do nothing
4479    def ScrollDown(self):
4480        """
4481        Scroll one unit down.
4482        By default this function is empty, let derived class do something.
4483        """
4484
4485        pass
4486
4487
4488    # by default do nothing
4489    def ScrollUp(self):
4490        """
4491        Scroll one unit up.
4492        By default this function is empty, let derived class do something.
4493        """
4494
4495        pass
4496
4497
4498    def GetMenuOwner(self):
4499        """
4500        Returns the menu logical owner, the owner does not necessarly mean the
4501        menu parent, it can also be the window that popped up it.
4502        """
4503
4504        return self._owner
4505
4506
4507#--------------------------------------------------------
4508# Class ToolBarItem
4509#--------------------------------------------------------
4510
4511class ToolBarItem(object):
4512    """
4513    A simple class that holds information about a toolbar item.
4514    """
4515
4516    def __init__(self, tbItem, rect, state):
4517        """
4518        Default class constructor.
4519
4520        :param `tbItem`: an instance of :class:`FlatToolbarItem`;
4521        :param `rect`: the client rectangle for the toolbar item, an instance of :class:`wx.Rect`;
4522        :param integer `state`: the toolbar item state.
4523
4524        :see: :meth:`wx.MenuEntryInfo.SetState() <MenuEntryInfo.SetState>` for a list of valid item states.
4525        """
4526
4527        self._tbItem = tbItem
4528        self._rect = rect
4529        self._state = state
4530        self._visible = True
4531
4532
4533#--------------------------------------------------------
4534# Class FlatToolBarItem
4535#--------------------------------------------------------
4536
4537class FlatToolbarItem(object):
4538    """
4539    This class represents a toolbar item.
4540    """
4541
4542    def __init__(self, controlType=None, id=wx.ID_ANY, label="", disabledBmp=wx.NullBitmap, kind=wx.ITEM_NORMAL,
4543                 shortHelp="", longHelp=""):
4544        """
4545        Default class constructor.
4546
4547        :param `controlType`: can be ``None`` for a toolbar separator, an instance
4548         of :class:`wx.Window` for a control or an instance of :class:`wx.Bitmap` for a standard
4549         toolbar tool;
4550        :param integer `id`: the toolbar tool id. If set to ``wx.ID_ANY``, a new id is
4551         automatically assigned;
4552        :param string `label`: the toolbar tool label;
4553        :param `disabledBmp`: the bitmap used when the tool is disabled. If the tool
4554         is a standard one (i.e., not a control or a separator), and `disabledBmp`
4555         is equal to :class:`NullBitmap`, the disabled bitmap is automatically generated
4556         by greing the normal one;
4557        :param integer `kind`: may be ``wx.ITEM_NORMAL`` for a normal button (default),
4558         ``wx.ITEM_CHECK`` for a checkable tool (such tool stays pressed after it had been
4559         toggled) or ``wx.ITEM_RADIO`` for a checkable tool which makes part of a radio
4560         group of tools each of which is automatically unchecked whenever another button
4561         in the group is checked;
4562        :param string `shortHelp`: a string used for the tool's tooltip;
4563        :param string `longHelp`: this string is shown in the :class:`StatusBar` (if any) of the
4564         parent frame when the mouse pointer is inside the tool.
4565        """
4566
4567        if id == wx.ID_ANY:
4568            id = wx.NewIdRef()
4569
4570        if controlType is None:    # Is a separator
4571            self._normalBmp = wx.NullBitmap
4572            self._id = wx.NewIdRef()
4573            self._label = ""
4574            self._disabledImg = wx.NullBitmap
4575            self._customCtrl = None
4576            kind = wx.ITEM_SEPARATOR
4577
4578        elif isinstance(controlType, wx.Window): # is a wxControl
4579            self._normalBmp = wx.NullBitmap
4580            self._id = id
4581            self._label = ""
4582            self._disabledImg = wx.NullBitmap
4583            self._customCtrl = controlType
4584            kind = FTB_ITEM_CUSTOM
4585
4586        elif isinstance(controlType, wx.Bitmap):   # Bitmap construction, simple tool
4587            self._normalBmp = controlType
4588            self._id = id
4589            self._label = label
4590            self._disabledImg = disabledBmp
4591            self._customCtrl = None
4592
4593            if not self._disabledImg.IsOk():
4594                # Create a grey bitmap from the normal bitmap
4595                self._disabledImg = wx.Bitmap(self._normalBmp.ConvertToImage().ConvertToGreyscale())
4596
4597        self._kind = kind
4598        self._enabled = True
4599        self._selected = False
4600        self._group = -1 # group id for radio items
4601
4602        if not shortHelp:
4603            shortHelp = label
4604
4605        self._shortHelp = shortHelp
4606        self._longHelp = longHelp
4607
4608
4609    def GetLabel(self):
4610        """ Returns the tool label. """
4611
4612        return self._label
4613
4614
4615    def SetLabel(self, label):
4616        """
4617        Sets the tool label.
4618
4619        :param string `label`: the new tool string.
4620        """
4621
4622        self._label = label
4623
4624
4625    def GetBitmap(self):
4626        """ Returns the tool bitmap. """
4627
4628        return self._normalBmp
4629
4630
4631    def SetBitmap(self, bmp):
4632        """
4633        Sets the tool bitmap.
4634
4635        :param `bmp`: the new tool bitmap, a valid :class:`wx.Bitmap` object.
4636        """
4637
4638        self._normalBmp = bmp
4639
4640
4641    def GetDisabledBitmap(self):
4642        """ Returns the tool disabled bitmap. """
4643
4644        return self._disabledImg
4645
4646
4647    def SetDisabledBitmap(self, bmp):
4648        """
4649        Sets the tool disabled bitmap.
4650
4651        :param `bmp`: the new tool disabled bitmap, a valid :class:`wx.Bitmap` object.
4652        """
4653
4654        self._disabledImg = bmp
4655
4656
4657    def GetId(self):
4658        """ Gets the tool id. """
4659
4660        return self._id
4661
4662
4663    def IsSeparator(self):
4664        """ Returns whether the tool is a separator or not. """
4665
4666        return self._kind == wx.ITEM_SEPARATOR
4667
4668
4669    def IsRadioItem(self):
4670        """ Returns ``True`` if the item is a radio item. """
4671
4672        return self._kind == wx.ITEM_RADIO
4673
4674
4675    def IsCheckItem(self):
4676        """ Returns ``True`` if the item is a radio item. """
4677
4678        return self._kind == wx.ITEM_CHECK
4679
4680
4681    def IsCustomControl(self):
4682        """ Returns whether the tool is a custom control or not. """
4683
4684        return self._kind == FTB_ITEM_CUSTOM
4685
4686
4687    def IsRegularItem(self):
4688        """ Returns whether the tool is a standard tool or not. """
4689
4690        return self._kind == wx.ITEM_NORMAL
4691
4692
4693    def GetCustomControl(self):
4694        """ Returns the associated custom control. """
4695
4696        return self._customCtrl
4697
4698
4699    def IsSelected(self):
4700        """ Returns whether the tool is selected or checked."""
4701
4702        return self._selected
4703
4704
4705    def IsChecked(self):
4706        """ Same as :meth:`~FlatToolbarItem.IsSelected`. More intuitive for check items though. """
4707
4708        return self._selected
4709
4710
4711    def Select(self, select=True):
4712        """
4713        Selects or checks a radio or check item.
4714
4715        :param bool `select`: ``True`` to select or check a tool, ``False`` to unselect
4716         or uncheck it.
4717        """
4718
4719        self._selected = select
4720
4721
4722    def Toggle(self):
4723        """ Toggles a check item. """
4724
4725        if self.IsCheckItem():
4726            self._selected = not self._selected
4727
4728
4729    def SetGroup(self, group):
4730        """
4731        Sets group id for a radio item, for other items does nothing.
4732
4733        :param `group`: an instance of :class:`FlatMenuItemGroup`.
4734        """
4735
4736        if self.IsRadioItem():
4737            self._group = group
4738
4739
4740    def GetGroup(self):
4741        """ Returns group id for radio item, or -1 for other item types. """
4742
4743        return self._group
4744
4745
4746    def IsEnabled(self):
4747        """ Returns whether the tool is enabled or not. """
4748
4749        return self._enabled
4750
4751
4752    def Enable(self, enable=True):
4753        """
4754        Enables or disables the tool.
4755
4756        :param bool `enable`: ``True`` to enable the tool, ``False`` to disable it.
4757        """
4758
4759        self._enabled = enable
4760
4761
4762    def GetShortHelp(self):
4763        """ Returns the tool short help string (displayed in the tool's tooltip). """
4764
4765        if self._kind == wx.ITEM_NORMAL:
4766            return self._shortHelp
4767
4768        return ""
4769
4770
4771    def SetShortHelp(self, help):
4772        """
4773        Sets the tool short help string (displayed in the tool's tooltip).
4774
4775        :param string `help`: the new tool short help string.
4776        """
4777
4778        if self._kind == wx.ITEM_NORMAL:
4779            self._shortHelp = help
4780
4781
4782    def SetLongHelp(self, help):
4783        """
4784        Sets the tool long help string (displayed in the parent frame :class:`StatusBar`).
4785
4786        :param string `help`: the new tool long help string.
4787        """
4788
4789        if self._kind == wx.ITEM_NORMAL:
4790            self._longHelp = help
4791
4792
4793    def GetLongHelp(self):
4794        """ Returns the tool long help string (displayed in the parent frame :class:`StatusBar`). """
4795
4796        if self._kind == wx.ITEM_NORMAL:
4797            return self._longHelp
4798
4799        return ""
4800
4801
4802#--------------------------------------------------------
4803# Class FlatMenuItem
4804#--------------------------------------------------------
4805
4806class FlatMenuItem(object):
4807    """
4808    A class that represents an item in a menu.
4809    """
4810
4811    def __init__(self, parent, id=wx.ID_SEPARATOR, label="", helpString="",
4812                 kind=wx.ITEM_NORMAL, subMenu=None, normalBmp=wx.NullBitmap,
4813                 disabledBmp=wx.NullBitmap,
4814                 hotBmp=wx.NullBitmap):
4815        """
4816        Default class constructor.
4817
4818        :param `parent`: menu that the menu item belongs to, an instance of :class:`FlatMenu`;
4819        :param integer `id`: the menu item identifier;
4820        :param string `label`: text for the menu item, as shown on the menu. An accelerator
4821         key can be specified using the ampersand '&' character. In order to embed
4822         an ampersand character in the menu item text, the ampersand must be doubled;
4823        :param string `helpString`: optional help string that will be shown on the status bar;
4824        :param integer `kind`: may be ``wx.ITEM_SEPARATOR``, ``wx.ITEM_NORMAL``, ``wx.ITEM_CHECK``
4825         or ``wx.ITEM_RADIO``;
4826        :param `subMenu`: if not ``None``, the sub menu this item belongs to (an instance of :class:`FlatMenu`);
4827        :param `normalBmp`: normal bitmap to draw to the side of the text, this bitmap
4828         is used when the menu is enabled (an instance of :class:`wx.Bitmap`);
4829        :param `disabledBmp`: 'greyed' bitmap to draw to the side of the text, this
4830         bitmap is used when the menu is disabled, if none supplied normal is used (an instance of :class:`wx.Bitmap`);
4831        :param `hotBmp`: hot bitmap to draw to the side of the text, this bitmap is
4832         used when the menu is hovered, if non supplied, normal is used (an instance of :class:`wx.Bitmap`).
4833        """
4834
4835        self._text = label
4836        self._kind = kind
4837        self._helpString = helpString
4838
4839        if id == wx.ID_ANY:
4840            id = wx.NewIdRef()
4841
4842        self._id = id
4843        self._parentMenu = parent
4844        self._subMenu = subMenu
4845        self._normalBmp = normalBmp
4846        self._disabledBmp = disabledBmp
4847        self._hotBmp = hotBmp
4848        self._bIsChecked = False
4849        self._bIsEnabled = True
4850        self._mnemonicIdx = wx.NOT_FOUND
4851        self._isAttachedToMenu = False
4852        self._accelStr = ""
4853        self._rect = wx.Rect()
4854        self._groupPtr = None
4855        self._visible = False
4856        self._contextMenu = None
4857        self._font = None
4858        self._textColour = None
4859
4860        self.SetLabel(self._text)
4861        self.SetMenuBar()
4862
4863        self._checkMarkBmp = wx.Bitmap(check_mark_xpm)
4864        self._checkMarkBmp.SetMask(wx.Mask(self._checkMarkBmp, wx.WHITE))
4865        self._radioMarkBmp = wx.Bitmap(radio_item_xpm)
4866        self._radioMarkBmp.SetMask(wx.Mask(self._radioMarkBmp, wx.WHITE))
4867
4868
4869    def SetLongHelp(self, help):
4870        """
4871        Sets the item long help string (displayed in the parent frame :class:`StatusBar`).
4872
4873        :param string `help`: the new item long help string.
4874        """
4875
4876        self._helpString = help
4877
4878
4879    def GetLongHelp(self):
4880        """ Returns the item long help string (displayed in the parent frame :class:`StatusBar`). """
4881
4882        return self._helpString
4883
4884
4885    def GetShortHelp(self):
4886        """ Returns the item short help string (displayed in the tool's tooltip). """
4887
4888        return ""
4889
4890
4891    def Enable(self, enable=True):
4892        """
4893        Enables or disables a menu item.
4894
4895        :param bool `enable`: ``True`` to enable the menu item, ``False`` to disable it.
4896        """
4897
4898        self._bIsEnabled = enable
4899        if self._parentMenu:
4900            self._parentMenu.UpdateItem(self)
4901
4902
4903    def GetBitmap(self):
4904        """
4905        Returns the normal bitmap associated to the menu item or :class:`NullBitmap` if
4906        none has been supplied.
4907        """
4908
4909        return self._normalBmp
4910
4911
4912    def GetDisabledBitmap(self):
4913        """
4914        Returns the disabled bitmap associated to the menu item or :class:`NullBitmap`
4915        if none has been supplied.
4916        """
4917
4918        return self._disabledBmp
4919
4920
4921    def GetHotBitmap(self):
4922        """
4923        Returns the hot bitmap associated to the menu item or :class:`NullBitmap` if
4924        none has been supplied.
4925        """
4926
4927        return self._hotBmp
4928
4929
4930    def GetHelp(self):
4931        """ Returns the item help string. """
4932
4933        return self._helpString
4934
4935
4936    def GetId(self):
4937        """ Returns the item id. """
4938
4939        return self._id
4940
4941
4942    def GetKind(self):
4943        """
4944        Returns the menu item kind, can be one of ``wx.ITEM_SEPARATOR``, ``wx.ITEM_NORMAL``,
4945        ``wx.ITEM_CHECK`` or ``wx.ITEM_RADIO``.
4946        """
4947
4948        return self._kind
4949
4950
4951    def GetLabel(self):
4952        """ Returns the menu item label (without the accelerator if it is part of the string). """
4953
4954        return self._label
4955
4956
4957    def GetMenu(self):
4958        """ Returns the parent menu. """
4959
4960        return self._parentMenu
4961
4962
4963    def GetContextMenu(self):
4964        """ Returns the context menu associated with this item (if any). """
4965
4966        return self._contextMenu
4967
4968
4969    def SetContextMenu(self, context_menu):
4970        """
4971        Assigns a context menu to this item.
4972
4973        :param `context_menu`: an instance of :class:`FlatMenu`.
4974        """
4975
4976        self._contextMenu = context_menu
4977
4978
4979    def GetText(self):
4980        """ Returns the text associated with the menu item including the accelerator. """
4981
4982        return self._text
4983
4984
4985    def GetSubMenu(self):
4986        """ Returns the sub-menu of this menu item (if any). """
4987
4988        return self._subMenu
4989
4990
4991    def IsCheckable(self):
4992        """ Returns ``True`` if this item is of type ``wx.ITEM_CHECK``, ``False`` otherwise. """
4993
4994        return self._kind == wx.ITEM_CHECK
4995
4996
4997    def IsChecked(self):
4998        """
4999        Returns whether an item is checked or not.
5000
5001        :note: This method is meaningful only for items of kind ``wx.ITEM_CHECK`` or
5002         ``wx.ITEM_RADIO``.
5003        """
5004
5005        return self._bIsChecked
5006
5007
5008    def IsRadioItem(self):
5009        """ Returns ``True`` if this item is of type ``wx.ITEM_RADIO``, ``False`` otherwise. """
5010
5011        return self._kind == wx.ITEM_RADIO
5012
5013
5014    def IsEnabled(self):
5015        """ Returns whether an item is enabled or not. """
5016
5017        return self._bIsEnabled
5018
5019
5020    def IsSeparator(self):
5021        """ Returns ``True`` if this item is of type ``wx.ITEM_SEPARATOR``, ``False`` otherwise. """
5022
5023        return self._id == wx.ID_SEPARATOR
5024
5025
5026    def IsSubMenu(self):
5027        """ Returns whether an item is a sub-menu or not. """
5028
5029        return self._subMenu != None
5030
5031
5032    def SetNormalBitmap(self, bmp):
5033        """
5034        Sets the menu item normal bitmap.
5035
5036        :param `bmp`: an instance of :class:`wx.Bitmap`.
5037        """
5038
5039        self._normalBmp = bmp
5040
5041
5042    def SetDisabledBitmap(self, bmp):
5043        """
5044        Sets the menu item disabled bitmap.
5045
5046        :param `bmp`: an instance of :class:`wx.Bitmap`.
5047        """
5048
5049        self._disabledBmp = bmp
5050
5051
5052    def SetHotBitmap(self, bmp):
5053        """
5054        Sets the menu item hot bitmap.
5055
5056        :param `bmp`: an instance of :class:`wx.Bitmap`.
5057        """
5058
5059        self._hotBmp = bmp
5060
5061
5062    def SetHelp(self, helpString):
5063        """
5064        Sets the menu item help string.
5065
5066        :param string `helpString`: the new menu item help string.
5067        """
5068
5069        self._helpString = helpString
5070
5071
5072    def SetMenu(self, menu):
5073        """
5074        Sets the menu item parent menu.
5075
5076        :param `menu`: an instance of :class:`FlatMenu`.
5077        """
5078
5079        self._parentMenu = menu
5080
5081
5082    def SetSubMenu(self, menu):
5083        """
5084        Sets the menu item sub-menu.
5085
5086        :param `menu`: an instance of :class:`FlatMenu`.
5087        """
5088
5089        self._subMenu = menu
5090
5091        # Fix toolbar update
5092        self.SetMenuBar()
5093
5094
5095    def GetAccelString(self):
5096        """ Returns the accelerator string. """
5097
5098        return self._accelStr
5099
5100
5101    def SetRect(self, rect):
5102        """
5103        Sets the menu item client rectangle.
5104
5105        :param `rect`: the menu item client rectangle, an instance of :class:`wx.Rect`.
5106        """
5107
5108        self._rect = rect
5109
5110
5111    def GetRect(self):
5112        """ Returns the menu item client rectangle. """
5113
5114        return self._rect
5115
5116
5117    def IsShown(self):
5118        """ Returns whether an item is shown or not. """
5119
5120        return self._visible
5121
5122
5123    def Show(self, show=True):
5124        """
5125        Actually shows/hides the menu item.
5126
5127        :param bool `show`: ``True`` to show the menu item, ``False`` to hide it.
5128        """
5129
5130        self._visible = show
5131
5132
5133    def GetHeight(self):
5134        """ Returns the menu item height, in pixels. """
5135
5136        if self.IsSeparator():
5137            return self._parentMenu.GetRenderer().separatorHeight
5138        else:
5139            return self._parentMenu._itemHeight
5140
5141
5142    def GetSuitableBitmap(self, selected):
5143        """
5144        Gets the bitmap that should be used based on the item state.
5145
5146        :param bool `selected`: ``True`` if this menu item is currently hovered by the mouse,
5147         ``False`` otherwise.
5148        """
5149
5150        normalBmp = self._normalBmp
5151        gBmp = (self._disabledBmp.IsOk() and [self._disabledBmp] or [self._normalBmp])[0]
5152        hotBmp = (self._hotBmp.IsOk() and [self._hotBmp] or [self._normalBmp])[0]
5153
5154        if not self.IsEnabled():
5155            return gBmp
5156        elif selected:
5157            return hotBmp
5158        else:
5159            return normalBmp
5160
5161
5162    def SetLabel(self, text):
5163        """
5164        Sets the label text for this item from the text (excluding the accelerator).
5165
5166        :param string `text`: the new item label (excluding the accelerator).
5167        """
5168
5169        if text:
5170
5171            indx = text.find("\t")
5172            if indx >= 0:
5173                self._accelStr = text[indx+1:]
5174                label = text[0:indx]
5175            else:
5176                self._accelStr = ""
5177                label = text
5178
5179            self._mnemonicIdx, self._label = GetAccelIndex(label)
5180
5181        else:
5182
5183            self._mnemonicIdx = wx.NOT_FOUND
5184            self._label = ""
5185
5186        if self._parentMenu:
5187            self._parentMenu.UpdateItem(self)
5188
5189
5190    def SetText(self, text):
5191        """
5192        Sets the text for this menu item (including accelerators).
5193
5194        :param string `text`: the new item label (including the accelerator).
5195        """
5196
5197        self._text = text
5198        self.SetLabel(self._text)
5199
5200
5201    def SetMenuBar(self):
5202        """ Links the current menu item with the main :class:`FlatMenuBar`. """
5203
5204        # Fix toolbar update
5205        if self._subMenu and self._parentMenu:
5206            self._subMenu.SetSubMenuBar(self._parentMenu.GetMenuBarForSubMenu())
5207
5208
5209    def GetAcceleratorEntry(self):
5210        """ Returns the accelerator entry associated to this menu item. """
5211
5212        if '\t' in self.GetText():
5213            accel = wx.AcceleratorEntry()
5214            accel.FromString(self.GetText())
5215            return accel
5216        elif self.GetAccelString():
5217            accel = wx.AcceleratorEntry()
5218            accel.FromString(self.GetAccelString())
5219            return accel
5220        else:
5221            return None
5222
5223
5224    def GetMnemonicChar(self):
5225        """ Returns the shortcut char for this menu item. """
5226
5227        if self._mnemonicIdx == wx.NOT_FOUND:
5228            return 0
5229
5230        mnemonic = self._label[self._mnemonicIdx]
5231        return mnemonic.lower()
5232
5233
5234    def Check(self, check=True):
5235        """
5236        Checks or unchecks the menu item.
5237
5238        :param bool `check`: ``True`` to check the menu item, ``False`` to uncheck it.
5239
5240        :note: This method is meaningful only for menu items of ``wx.ITEM_CHECK``
5241         or ``wx.ITEM_RADIO`` kind.
5242        """
5243
5244        if self.IsRadioItem() and not self._isAttachedToMenu:
5245
5246            # radio items can be checked only after they are attached to menu
5247            return
5248
5249        self._bIsChecked = check
5250
5251        # update group
5252        if self.IsRadioItem() and check:
5253            self._groupPtr.SetSelection(self)
5254
5255        # Our parent menu might want to do something with this change
5256        if self._parentMenu:
5257            self._parentMenu.UpdateItem(self)
5258
5259
5260    def SetFont(self, font=None):
5261        """
5262        Sets the :class:`FlatMenuItem` font.
5263
5264        :param `font`: an instance of a valid :class:`wx.Font`.
5265        """
5266
5267        self._font = font
5268
5269        if self._parentMenu:
5270            self._parentMenu.UpdateItem(self)
5271
5272
5273    def GetFont(self):
5274        """ Returns this :class:`FlatMenuItem` font. """
5275
5276        return self._font
5277
5278
5279    def SetTextColour(self, colour=None):
5280        """
5281        Sets the :class:`FlatMenuItem` foreground colour for the menu label.
5282
5283        :param `colour`: an instance of a valid :class:`wx.Colour`.
5284        """
5285
5286        self._textColour = colour
5287
5288
5289    def GetTextColour(self):
5290        """ Returns this :class:`FlatMenuItem` foreground text colour. """
5291
5292        return self._textColour
5293
5294
5295#--------------------------------------------------------
5296# Class FlatMenu
5297#--------------------------------------------------------
5298
5299class FlatMenu(FlatMenuBase):
5300    """
5301    A Flat popup menu generic implementation.
5302    """
5303
5304    def __init__(self, parent=None):
5305        """
5306        Default class constructor.
5307
5308        :param `parent`: the :class:`FlatMenu` parent window (used to initialize the
5309         underlying :class:`ShadowPopupWindow`).
5310        """
5311
5312        self._menuWidth = 2*26
5313        self._leftMarginWidth = 26
5314        self._rightMarginWidth = 30
5315        self._borderXWidth = 1
5316        self._borderYWidth = 2
5317        self._activeWin = None
5318        self._focusWin = None
5319        self._imgMarginX = 0
5320        self._markerMarginX = 0
5321        self._textX = 26
5322        self._rightMarginPosX = -1
5323        self._itemHeight = 20
5324        self._selectedItem = -1
5325        self._clearCurrentSelection = True
5326        self._textPadding = 8
5327        self._marginHeight = 20
5328        self._marginWidth = 26
5329        self._accelWidth = 0
5330        self._mb = None
5331        self._itemsArr = []
5332        self._accelArray = []
5333        self._ptLast = wx.Point()
5334        self._resizeMenu = True
5335        self._shiftePos = 0
5336        self._first = 0
5337        self._mb_submenu = 0
5338        self._is_dismiss = False
5339        self._numCols = 1
5340        self._backgroundImage = None
5341        self._originalBackgroundImage = None
5342
5343        FlatMenuBase.__init__(self, parent)
5344
5345        self.SetSize(wx.Size(self._menuWidth, self._itemHeight+4))
5346
5347        self.Bind(wx.EVT_PAINT, self.OnPaint)
5348        self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
5349        self.Bind(wx.EVT_MOTION, self.OnMouseMove)
5350        self.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnterWindow)
5351        self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeaveWindow)
5352        self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
5353        self.Bind(wx.EVT_LEFT_UP, self.OnMouseLeftUp)
5354        self.Bind(wx.EVT_LEFT_DCLICK, self.OnMouseLeftDown)
5355        self.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseRightDown)
5356        self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
5357        self.Bind(wx.EVT_TIMER, self.OnTimer)
5358
5359
5360    def SetMenuBar(self, mb):
5361        """
5362        Attaches this menu to a menubar.
5363
5364        :param `mb`: an instance of :class:`FlatMenuBar`.
5365        """
5366
5367        self._mb = mb
5368
5369
5370    def SetSubMenuBar(self, mb):
5371        """
5372        Attaches this menu to a menubar.
5373
5374        :param `mb`: an instance of :class:`FlatMenuBar`.
5375        """
5376
5377        self._mb_submenu = mb
5378
5379
5380    def GetMenuBar(self):
5381        """ Returns the menubar associated with this menu item. """
5382
5383        if self._mb_submenu:
5384            return self._mb_submenu
5385
5386        return self._mb
5387
5388
5389    def GetMenuBarForSubMenu(self):
5390        """ Returns the menubar associated with this menu item. """
5391
5392        return self._mb
5393
5394
5395    def Popup(self, pt, owner=None, parent=None):
5396        """
5397        Pops up the menu.
5398
5399        :param `pt`: the point at which the menu should be popped up (an instance
5400         of :class:`wx.Point`);
5401        :param `owner`: the owner of the menu. The owner does not necessarly mean the
5402         menu parent, it can also be the window that popped up it;
5403        :param `parent`: the menu parent window.
5404        """
5405
5406        if "__WXMSW__" in wx.Platform:
5407            self._mousePtAtStartup = wx.GetMousePosition()
5408
5409        # each time we popup, need to reset the starting index
5410        self._first = 0
5411
5412        # Loop over self menu and send update UI event for
5413        # every item in the menu
5414        numEvents = len(self._itemsArr)
5415        cc = 0
5416        self._shiftePos = 0
5417
5418        # Set the owner of the menu. All events will be directed to it.
5419        # If owner is None, the Default GetParent() is used as the owner
5420        self._owner = owner
5421
5422        for cc in range(numEvents):
5423            self.SendUIEvent(cc)
5424
5425        # Adjust menu position and show it
5426        FlatMenuBase.Popup(self, pt, parent)
5427
5428        artMgr = ArtManager.Get()
5429        artMgr.MakeWindowTransparent(self, artMgr.GetTransparency())
5430
5431        # Replace the event handler of the active window to direct
5432        # all keyboard events to us and the focused window to direct char events to us
5433        self._activeWin = wx.GetActiveWindow()
5434        if self._activeWin:
5435
5436            oldHandler = self._activeWin.GetEventHandler()
5437            newEvtHandler = MenuKbdRedirector(self, oldHandler)
5438            self._activeWin.PushEventHandler(newEvtHandler)
5439
5440        if "__WXMSW__" in wx.Platform:
5441            self._focusWin = wx.Window.FindFocus()
5442        elif "__WXGTK__" in wx.Platform:
5443            self._focusWin = self
5444        else:
5445            self._focusWin = None
5446
5447        if self._focusWin:
5448            newEvtHandler = FocusHandler(self)
5449            self._focusWin.PushEventHandler(newEvtHandler)
5450
5451
5452    def Append(self, id, item, helpString="", kind=wx.ITEM_NORMAL):
5453        """
5454        Appends an item to this menu.
5455
5456        :param integer `id`: the menu item identifier;
5457        :param string `item`: the string to appear on the menu item;
5458        :param string `helpString`: an optional help string associated with the item. By default,
5459         the handler for the ``EVT_FLAT_MENU_ITEM_MOUSE_OVER`` event displays this string
5460         in the status line;
5461        :param integer `kind`: may be ``wx.ITEM_NORMAL`` for a normal button (default),
5462         ``wx.ITEM_CHECK`` for a checkable tool (such tool stays pressed after it had been
5463         toggled) or ``wx.ITEM_RADIO`` for a checkable tool which makes part of a radio
5464         group of tools each of which is automatically unchecked whenever another button
5465         in the group is checked;
5466        """
5467
5468        newItem = FlatMenuItem(self, id, item, helpString, kind)
5469        return self.AppendItem(newItem)
5470
5471
5472    def Prepend(self, id, item, helpString="", kind=wx.ITEM_NORMAL):
5473        """
5474        Prepends an item to this menu.
5475
5476        :param integer `id`: the menu item identifier;
5477        :param string `item`: the string to appear on the menu item;
5478        :param string `helpString`: an optional help string associated with the item. By default,
5479         the handler for the ``EVT_FLAT_MENU_ITEM_MOUSE_OVER`` event displays this string
5480         in the status line;
5481        :param integer `kind`: may be ``wx.ITEM_NORMAL`` for a normal button (default),
5482         ``wx.ITEM_CHECK`` for a checkable tool (such tool stays pressed after it had been
5483         toggled) or ``wx.ITEM_RADIO`` for a checkable tool which makes part of a radio
5484         group of tools each of which is automatically unchecked whenever another button
5485         in the group is checked;
5486        """
5487
5488        newItem = FlatMenuItem(self, id, item, helpString, kind)
5489        return self.PrependItem(newItem)
5490
5491
5492    def AppendSubMenu(self, subMenu, item, helpString=""):
5493        """
5494        Adds a pull-right submenu to the end of the menu.
5495
5496        This function is added to duplicate the API of :class:`wx.Menu`.
5497
5498        :see: :meth:`~FlatMenu.AppendMenu` for an explanation of the input parameters.
5499        """
5500
5501        return self.AppendMenu(wx.ID_ANY, item, subMenu, helpString)
5502
5503
5504    def AppendMenu(self, id, item, subMenu, helpString=""):
5505        """
5506        Adds a pull-right submenu to the end of the menu.
5507
5508        :param integer `id`: the menu item identifier;
5509        :param string `item`: the string to appear on the menu item;
5510        :param `subMenu`: an instance of :class:`FlatMenu`, the submenu to append;
5511        :param string `helpString`: an optional help string associated with the item. By default,
5512         the handler for the ``EVT_FLAT_MENU_ITEM_MOUSE_OVER`` event displays this string
5513         in the status line.
5514        """
5515
5516        newItem = FlatMenuItem(self, id, item, helpString, wx.ITEM_NORMAL, subMenu)
5517        return self.AppendItem(newItem)
5518
5519
5520    def AppendItem(self, menuItem):
5521        """
5522        Appends an item to this menu.
5523
5524        :param `menuItem`: an instance of :class:`FlatMenuItem`.
5525        """
5526
5527        self._itemsArr.append(menuItem)
5528        return self.AddItem(menuItem)
5529
5530
5531    def PrependItem(self, menuItem):
5532        """
5533        Prepends an item to this menu.
5534
5535        :param `menuItem`: an instance of :class:`FlatMenuItem`.
5536        """
5537
5538        self._itemsArr.insert(0,menuItem)
5539        return self.AddItem(menuItem)
5540
5541
5542    def AddItem(self, menuItem):
5543        """
5544        Internal function to add the item to this menu. The item must
5545        already be in the `self._itemsArr` attribute.
5546
5547        :param `menuItem`: an instance of :class:`FlatMenuItem`.
5548        """
5549
5550        if not menuItem:
5551            raise Exception("Adding None item?")
5552
5553        # Reparent to us
5554        menuItem.SetMenu(self)
5555        menuItem._isAttachedToMenu = True
5556
5557        # Update the menu width if necessary
5558        menuItemWidth = self.GetMenuItemWidth(menuItem)
5559        self._menuWidth = (self._menuWidth > menuItemWidth + self._accelWidth and \
5560                           [self._menuWidth] or [menuItemWidth + self._accelWidth])[0]
5561
5562        menuHeight = 0
5563        switch = 1e6
5564
5565        if self._numCols > 1:
5566            nItems = len(self._itemsArr)
5567            switch = int(math.ceil((nItems - self._first)/float(self._numCols)))
5568
5569        for indx, item in enumerate(self._itemsArr):
5570
5571            if indx >= switch:
5572                break
5573
5574            if item.IsSeparator():
5575                menuHeight += self.GetRenderer().separatorHeight
5576            else:
5577                menuHeight += self._itemHeight
5578
5579        self.SetSize(wx.Size(self._menuWidth*self._numCols, menuHeight+4))
5580
5581        if self._originalBackgroundImage:
5582            img = self._originalBackgroundImage.ConvertToImage()
5583            img = img.Scale(self._menuWidth*self._numCols-2-self._leftMarginWidth, menuHeight, wx.IMAGE_QUALITY_HIGH)
5584            self._backgroundImage = img.ConvertToBitmap()
5585
5586        # Add accelerator entry to the menu if needed
5587        accel = menuItem.GetAcceleratorEntry()
5588
5589        if accel:
5590            accel.Set(accel.GetFlags(), accel.GetKeyCode(), menuItem.GetId())
5591            self._accelArray.append(accel)
5592
5593        self.UpdateRadioGroup(menuItem)
5594
5595        return menuItem
5596
5597
5598    def GetMenuItems(self):
5599        """ Returns the list of menu items in the menu. """
5600
5601        return self._itemsArr
5602
5603
5604    def GetMenuItemWidth(self, menuItem):
5605        """
5606        Returns the width of a particular item.
5607
5608        :param `menuItem`: an instance of :class:`FlatMenuItem`.
5609        """
5610
5611        menuItemWidth = 0
5612        text = menuItem.GetLabel() # Without accelerator
5613        accel = menuItem.GetAccelString()
5614
5615        dc = wx.ClientDC(self)
5616
5617        font = menuItem.GetFont()
5618        if font is None:
5619            font = ArtManager.Get().GetFont()
5620
5621        dc.SetFont(font)
5622
5623        accelFiller = "XXXX"     # 4 spaces betweem text and accel column
5624
5625        # Calc text length/height
5626        dummy, itemHeight = dc.GetTextExtent("Tp")
5627        width, height = dc.GetTextExtent(text)
5628        accelWidth, accelHeight = dc.GetTextExtent(accel)
5629        filler, dummy = dc.GetTextExtent(accelFiller)
5630
5631        bmpHeight = bmpWidth = 0
5632
5633        if menuItem.GetBitmap().IsOk():
5634            bmpHeight = menuItem.GetBitmap().GetHeight()
5635            bmpWidth  = menuItem.GetBitmap().GetWidth()
5636
5637        if itemHeight < self._marginHeight:
5638            itemHeight = self._marginHeight
5639
5640        itemHeight = (bmpHeight > self._itemHeight and [bmpHeight] or [itemHeight])[0]
5641        itemHeight += 2*self._borderYWidth
5642
5643        # Update the global menu item height if needed
5644        self._itemHeight = (self._itemHeight > itemHeight and [self._itemHeight] or [itemHeight])[0]
5645        self._marginWidth = (self._marginWidth > bmpWidth and [self._marginWidth] or [bmpWidth])[0]
5646
5647        # Update the accel width
5648        accelWidth += filler
5649        if accel:
5650            self._accelWidth = (self._accelWidth > accelWidth and [self._accelWidth] or [accelWidth])[0]
5651
5652        # In case the item has image & is type radio or check, we need double size
5653        # left margin
5654        factor = (((menuItem.GetBitmap() != wx.NullBitmap) and \
5655                   (menuItem.IsCheckable() or (menuItem.GetKind() == wx.ITEM_RADIO))) and [2] or [1])[0]
5656
5657        if factor == 2:
5658
5659            self._imgMarginX = self._marginWidth + 2*self._borderXWidth
5660            self._leftMarginWidth = 2 * self._marginWidth + 2*self._borderXWidth
5661
5662        else:
5663
5664            self._leftMarginWidth = ((self._leftMarginWidth > self._marginWidth + 2*self._borderXWidth) and \
5665                                    [self._leftMarginWidth] or [self._marginWidth + 2*self._borderXWidth])[0]
5666
5667        menuItemWidth = self.GetLeftMarginWidth() + 2*self.GetBorderXWidth() + width + self.GetRightMarginWidth()
5668        self._textX = self._imgMarginX + self._marginWidth + self._textPadding
5669
5670        # update the rightMargin X position
5671        self._rightMarginPosX = ((self._textX + width + self._accelWidth> self._rightMarginPosX) and \
5672                                 [self._textX + width + self._accelWidth] or [self._rightMarginPosX])[0]
5673
5674        return menuItemWidth
5675
5676
5677    def GetMenuWidth(self):
5678        """ Returns the menu width in pixels. """
5679
5680        return self._menuWidth
5681
5682
5683    def GetLeftMarginWidth(self):
5684        """ Returns the menu left margin width, in pixels. """
5685
5686        return self._leftMarginWidth
5687
5688
5689    def GetRightMarginWidth(self):
5690        """ Returns the menu right margin width, in pixels. """
5691
5692        return self._rightMarginWidth
5693
5694
5695    def GetBorderXWidth(self):
5696        """ Returns the menu border x-width, in pixels. """
5697
5698        return self._borderXWidth
5699
5700
5701    def GetBorderYWidth(self):
5702        """ Returns the menu border y-width, in pixels. """
5703
5704        return self._borderYWidth
5705
5706
5707    def GetItemHeight(self):
5708        """ Returns the height of a particular item, in pixels. """
5709
5710        return self._itemHeight
5711
5712
5713    def AppendCheckItem(self, id, item, helpString=""):
5714        """
5715        Adds a checkable item to the end of the menu.
5716
5717        :see: :meth:`~FlatMenu.Append` for the explanation of the input parameters.
5718        """
5719
5720        newItem = FlatMenuItem(self, id, item, helpString, wx.ITEM_CHECK)
5721        return self.AppendItem(newItem)
5722
5723
5724    def AppendRadioItem(self, id, item, helpString=""):
5725        """
5726        Adds a radio item to the end of the menu.
5727
5728        All consequent radio items form a group and when an item in the group is
5729        checked, all the others are automatically unchecked.
5730
5731        :see: :meth:`~FlatMenu.Append` for the explanation of the input parameters.
5732        """
5733
5734        newItem = FlatMenuItem(self, id, item, helpString, wx.ITEM_RADIO)
5735        return self.AppendItem(newItem)
5736
5737
5738    def AppendSeparator(self):
5739        """ Appends a separator item to the end of this menu. """
5740
5741        newItem = FlatMenuItem(self)
5742        return self.AppendItem(newItem)
5743
5744
5745    def InsertSeparator(self, pos):
5746        """
5747        Inserts a separator at the given position.
5748
5749        :param integer `pos`: the index at which we want to insert the separator.
5750        """
5751
5752        newItem = FlatMenuItem(self)
5753        return self.InsertItem(pos, newItem)
5754
5755
5756    def Dismiss(self, dismissParent, resetOwner):
5757        """
5758        Dismisses the popup window.
5759
5760        :param bool `dismissParent`: whether to dismiss the parent menu or not;
5761        :param bool `resetOwner`: ``True`` to delete the link between this menu and the
5762         owner menu, ``False`` otherwise.
5763        """
5764
5765        if self._activeWin:
5766
5767            self._activeWin.PopEventHandler(True)
5768            self._activeWin = None
5769
5770        if self._focusWin:
5771
5772            self._focusWin.PopEventHandler(True)
5773            self._focusWin = None
5774
5775        self._selectedItem = -1
5776
5777        if self._mb:
5778            self._mb.RemoveHelp()
5779
5780        FlatMenuBase.Dismiss(self, dismissParent, resetOwner)
5781
5782
5783    def OnPaint(self, event):
5784        """
5785        Handles the ``wx.EVT_PAINT`` event for :class:`FlatMenu`.
5786
5787        :param `event`: a :class:`PaintEvent` event to be processed.
5788        """
5789
5790        dc = wx.PaintDC(self)
5791        self.GetRenderer().DrawMenu(self, dc)
5792
5793        # We need to redraw all our child menus
5794        self.RefreshChilds()
5795
5796
5797    def UpdateItem(self, item):
5798        """
5799        Updates an item.
5800
5801        :param `item`: an instance of :class:`FlatMenuItem`.
5802        """
5803
5804        # notify menu bar that an item was modified directly
5805        if item and self._mb:
5806            self._mb.UpdateItem(item)
5807
5808
5809    def OnEraseBackground(self, event):
5810        """
5811        Handles the ``wx.EVT_ERASE_BACKGROUND`` event for :class:`FlatMenu`.
5812
5813        :param `event`: a :class:`EraseEvent` event to be processed.
5814
5815        :note: This method is intentionally empty to avoid flicker.
5816        """
5817
5818        pass
5819
5820
5821    def DrawSelection(self, dc, oldSelection=-1):
5822        """
5823        Redraws the menu.
5824
5825        :param `dc`: an instance of :class:`wx.DC`;
5826        :param integer `oldSelection`: if non-negative, the index representing the previous selected
5827         menu item.
5828        """
5829
5830        self.Refresh()
5831
5832
5833    def RefreshChilds(self):
5834        """
5835        In some cases, we need to perform a recursive refresh for all opened submenu
5836        from this.
5837        """
5838
5839        # Draw all childs menus of self menu as well
5840        child = self._openedSubMenu
5841        while child:
5842            dc = wx.ClientDC(child)
5843            self.GetRenderer().DrawMenu(child, dc)
5844            child = child._openedSubMenu
5845
5846
5847    def GetMenuRect(self):
5848        """ Returns the menu client rectangle. """
5849
5850        clientRect = self.GetClientRect()
5851        return wx.Rect(clientRect.x, clientRect.y, clientRect.width, clientRect.height)
5852
5853
5854    def OnKeyDown(self, event):
5855        """
5856        Handles the ``wx.EVT_KEY_DOWN`` event for :class:`FlatMenu`.
5857
5858        :param `event`: a :class:`KeyEvent` event to be processed.
5859        """
5860
5861        self.OnChar(event.GetKeyCode())
5862
5863
5864    def OnChar(self, key):
5865        """
5866        Handles key events for :class:`FlatMenu`.
5867
5868        :param `key`: the keyboard key integer code.
5869        """
5870
5871        processed = True
5872
5873        if key == wx.WXK_ESCAPE:
5874
5875            if self._parentMenu:
5876                self._parentMenu.CloseSubMenu(-1)
5877            else:
5878                self.Dismiss(True, True)
5879
5880        elif key == wx.WXK_LEFT:
5881
5882            if self._parentMenu:
5883                # We are a submenu, dismiss us.
5884                self._parentMenu.CloseSubMenu(-1)
5885            else:
5886                # try to find our root menu, if we are attached to menubar,
5887                # let it try and open the previous menu
5888                root = self.GetRootMenu()
5889                if root:
5890                    if root._mb:
5891                        root._mb.ActivatePreviousMenu()
5892
5893        elif key == wx.WXK_RIGHT:
5894
5895            if not self.TryOpenSubMenu(self._selectedItem, True):
5896                # try to find our root menu, if we are attached to menubar,
5897                # let it try and open the previous menu
5898                root = self.GetRootMenu()
5899                if root:
5900                    if root._mb:
5901                        root._mb.ActivateNextMenu()
5902
5903        elif key == wx.WXK_UP:
5904            self.AdvanceSelection(False)
5905
5906        elif key == wx.WXK_DOWN:
5907
5908            self.AdvanceSelection()
5909
5910        elif key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
5911            self.DoAction(self._selectedItem)
5912
5913        elif key == wx.WXK_HOME:
5914
5915            # Select first item of the menu
5916            if self._selectedItem != 0:
5917                oldSel = self._selectedItem
5918                self._selectedItem = 0
5919                dc = wx.ClientDC(self)
5920                self.DrawSelection(dc, oldSel)
5921
5922        elif key == wx.WXK_END:
5923
5924            # Select last item of the menu
5925            if self._selectedItem != len(self._itemsArr)-1:
5926                oldSel = self._selectedItem
5927                self._selectedItem = len(self._itemsArr)-1
5928                dc = wx.ClientDC(self)
5929                self.DrawSelection(dc, oldSel)
5930
5931        elif key in [wx.WXK_CONTROL, wx.WXK_ALT]:
5932            # Alt was pressed
5933            root = self.GetRootMenu()
5934            root.Dismiss(False, True)
5935
5936        else:
5937            try:
5938                chrkey = chr(key)
5939            except:
5940                return processed
5941
5942            if chrkey.isalnum():
5943
5944                ch = chrkey.lower()
5945
5946                # Iterate over all the menu items
5947                itemIdx = -1
5948                occur = 0
5949
5950                for i in range(len(self._itemsArr)):
5951
5952                    item = self._itemsArr[i]
5953                    mnemonic = item.GetMnemonicChar()
5954
5955                    if mnemonic == ch:
5956
5957                        if itemIdx == -1:
5958
5959                            itemIdx = i
5960                            # We keep the index of only
5961                            # the first occurence
5962
5963                        occur += 1
5964
5965                        # Keep on looping until no more items for self menu
5966
5967                if itemIdx != -1:
5968
5969                    if occur > 1:
5970
5971                        # We select the first item
5972                        if self._selectedItem == itemIdx:
5973                            return processed
5974
5975                        oldSel = self._selectedItem
5976                        self._selectedItem = itemIdx
5977                        dc = wx.ClientDC(self)
5978                        self.DrawSelection(dc, oldSel)
5979
5980                    elif occur == 1:
5981
5982                        # Activate the item, if self is a submenu item we first select it
5983                        item = self._itemsArr[itemIdx]
5984                        if item.IsSubMenu() and self._selectedItem != itemIdx:
5985
5986                            oldSel = self._selectedItem
5987                            self._selectedItem = itemIdx
5988                            dc = wx.ClientDC(self)
5989                            self.DrawSelection(dc, oldSel)
5990
5991                        self.DoAction(itemIdx)
5992
5993                else:
5994
5995                    processed = False
5996
5997        return processed
5998
5999
6000    def AdvanceSelection(self, down=True):
6001        """
6002        Advance forward or backward the current selection.
6003
6004        :param bool `down`: ``True`` to advance the selection forward, ``False`` otherwise.
6005        """
6006
6007        # make sure we have at least two items in the menu (which are not
6008        # separators)
6009        num=0
6010        singleItemIdx = -1
6011
6012        for i in range(len(self._itemsArr)):
6013
6014            item = self._itemsArr[i]
6015            if item.IsSeparator():
6016                continue
6017            num += 1
6018            singleItemIdx = i
6019
6020        if num < 1:
6021            return
6022
6023        if num == 1:
6024            # Select the current one
6025            self._selectedItem = singleItemIdx
6026            dc = wx.ClientDC(self)
6027            self.DrawSelection(dc, -1)
6028            return
6029
6030        oldSelection = self._selectedItem
6031
6032        if not down:
6033
6034            # find the next valid item
6035            while 1:
6036
6037                self._selectedItem -= 1
6038                if self._selectedItem < 0:
6039                    self._selectedItem = len(self._itemsArr)-1
6040                if not self._itemsArr[self._selectedItem].IsSeparator():
6041                    break
6042
6043        else:
6044
6045            # find the next valid item
6046            while 1:
6047
6048                self._selectedItem += 1
6049                if self._selectedItem > len(self._itemsArr)-1:
6050                    self._selectedItem = 0
6051                if not self._itemsArr[self._selectedItem].IsSeparator():
6052                    break
6053
6054        dc = wx.ClientDC(self)
6055        self.DrawSelection(dc, oldSelection)
6056
6057
6058    def HitTest(self, pos):
6059        """
6060        HitTest method for :class:`FlatMenu`.
6061
6062        :param `pos`: an instance of :class:`wx.Point`, a point to test for hits.
6063
6064        :return: A tuple representing one of the following combinations:
6065
6066         ========================= ==================================================
6067         Return Tuple              Description
6068         ========================= ==================================================
6069         (0, -1)                   The :meth:`~FlatMenu.HitTest` method didn't find any item with the specified input point `pt` (``MENU_HT_NONE`` = 0)
6070         (1, `integer`)            A menu item has been hit (``MENU_HT_ITEM`` = 1)
6071         (2, -1)                   The `Scroll Up` button has been hit (``MENU_HT_SCROLL_UP`` = 2)
6072         (3, -1)                   The `Scroll Down` button has been hit (``MENU_HT_SCROLL_DOWN`` = 3)
6073         ========================= ==================================================
6074
6075        """
6076
6077        if self._showScrollButtons:
6078
6079            if self._upButton and self._upButton.GetClientRect().Contains(pos):
6080                return MENU_HT_SCROLL_UP, -1
6081
6082            if self._downButton and self._downButton.GetClientRect().Contains(pos):
6083                return MENU_HT_SCROLL_DOWN, -1
6084
6085        for ii, item in enumerate(self._itemsArr):
6086
6087            if item.GetRect().Contains(pos) and item.IsEnabled() and item.IsShown():
6088                return MENU_HT_ITEM, ii
6089
6090        return MENU_HT_NONE, -1
6091
6092
6093    def OnMouseMove(self, event):
6094        """
6095        Handles the ``wx.EVT_MOTION`` event for :class:`FlatMenu`.
6096
6097        :param `event`: a :class:`MouseEvent` event to be processed.
6098        """
6099
6100        if "__WXMSW__" in wx.Platform:
6101            # Ignore dummy mouse move events
6102            pt = wx.GetMousePosition()
6103            if self._mousePtAtStartup == pt:
6104                return
6105
6106        pos = event.GetPosition()
6107
6108        # we need to ignore extra mouse events: example when this happens is when
6109        # the mouse is on the menu and we open a submenu from keyboard - Windows
6110        # then sends us a dummy mouse move event, we (correctly) determine that it
6111        # happens in the parent menu and so immediately close the just opened
6112        # submenunot
6113
6114        if "__WXMSW__" in wx.Platform:
6115
6116            ptCur = self.ClientToScreen(pos)
6117            if ptCur == self._ptLast:
6118                return
6119
6120            self._ptLast = ptCur
6121
6122        # first let the scrollbar handle it
6123        self.TryScrollButtons(event)
6124        self.ProcessMouseMove(pos)
6125
6126
6127    def OnMouseLeftDown(self, event):
6128        """
6129        Handles the ``wx.EVT_LEFT_DOWN`` event for :class:`FlatMenu`.
6130
6131        :param `event`: a :class:`MouseEvent` event to be processed.
6132        """
6133
6134        if self.TryScrollButtons(event):
6135            return
6136
6137        pos = event.GetPosition()
6138        self.ProcessMouseLClick(pos)
6139
6140
6141    def OnMouseLeftUp(self, event):
6142        """
6143        Handles the ``wx.EVT_LEFT_UP`` event for :class:`FlatMenu`.
6144
6145        :param `event`: a :class:`MouseEvent` event to be processed.
6146        """
6147
6148        if self.TryScrollButtons(event):
6149            return
6150
6151        pos = event.GetPosition()
6152        rect = self.GetClientRect()
6153
6154        if not rect.Contains(pos):
6155
6156            # The event is not in our coords,
6157            # so we try our parent
6158            win = self._parentMenu
6159
6160            while win:
6161
6162                # we need to translate our client coords to the client coords of the
6163                # window we forward this event to
6164                ptScreen = self.ClientToScreen(pos)
6165                p = win.ScreenToClient(ptScreen)
6166
6167                if win.GetClientRect().Contains(p):
6168
6169                    event.SetX(p.x)
6170                    event.SetY(p.y)
6171                    win.OnMouseLeftUp(event)
6172                    return
6173
6174                else:
6175                    # try the grandparent
6176                    win = win._parentMenu
6177
6178        else:
6179            self.ProcessMouseLClickEnd(pos)
6180
6181        if self._showScrollButtons:
6182
6183            if self._upButton:
6184                self._upButton.ProcessLeftUp(pos)
6185            if self._downButton:
6186                self._downButton.ProcessLeftUp(pos)
6187
6188
6189    def OnMouseRightDown(self, event):
6190        """
6191        Handles the ``wx.EVT_RIGHT_DOWN`` event for :class:`FlatMenu`.
6192
6193        :param `event`: a :class:`MouseEvent` event to be processed.
6194        """
6195
6196        if self.TryScrollButtons(event):
6197            return
6198
6199        pos = event.GetPosition()
6200        self.ProcessMouseRClick(pos)
6201
6202
6203    def ProcessMouseRClick(self, pos):
6204        """
6205        Processes mouse right clicks.
6206
6207        :param `pos`: the position at which the mouse right button was pressed,
6208         an instance of :class:`wx.Point`.
6209        """
6210
6211        rect = self.GetClientRect()
6212
6213        if not rect.Contains(pos):
6214
6215            # The event is not in our coords,
6216            # so we try our parent
6217
6218            win = self._parentMenu
6219            while win:
6220
6221                # we need to translate our client coords to the client coords of the
6222                # window we forward self event to
6223                ptScreen = self.ClientToScreen(pos)
6224                p = win.ScreenToClient(ptScreen)
6225
6226                if win.GetClientRect().Contains(p):
6227                    win.ProcessMouseRClick(p)
6228                    return
6229
6230                else:
6231                    # try the grandparent
6232                    win = win._parentMenu
6233
6234            # At this point we can assume that the event was not
6235            # processed, so we dismiss the menu and its children
6236            self.Dismiss(True, True)
6237            return
6238
6239        # test if we are on a menu item
6240        res, itemIdx = self.HitTest(pos)
6241        if res == MENU_HT_ITEM:
6242            self.OpenItemContextMenu(itemIdx)
6243
6244
6245    def OpenItemContextMenu(self, itemIdx):
6246        """
6247        Open an item's context menu (if any).
6248
6249        :param integer `itemIdx`: the index of the item for which we want to open the context menu.
6250        """
6251
6252        item = self._itemsArr[itemIdx]
6253        context_menu = item.GetContextMenu()
6254
6255        # If we have a context menu, close any opened submenu
6256        if context_menu:
6257            self.CloseSubMenu(itemIdx, True)
6258
6259        if context_menu and not context_menu.IsShown():
6260            # Popup child menu
6261            pos = wx.Point()
6262            pos.x = item.GetRect().GetWidth() + item.GetRect().GetX() - 5
6263            pos.y = item.GetRect().GetY()
6264            self._clearCurrentSelection = False
6265            self._openedSubMenu = context_menu
6266            context_menu.Popup(self.ScreenToClient(wx.GetMousePosition()), self._owner, self)
6267            return True
6268
6269        return False
6270
6271
6272    def ProcessMouseLClick(self, pos):
6273        """
6274        Processes mouse left clicks.
6275
6276        :param `pos`: the position at which the mouse left button was pressed,
6277         an instance of :class:`wx.Point`.
6278        """
6279
6280        rect = self.GetClientRect()
6281
6282        if not rect.Contains(pos):
6283
6284            # The event is not in our coords,
6285            # so we try our parent
6286
6287            win = self._parentMenu
6288            while win:
6289
6290                # we need to translate our client coords to the client coords of the
6291                # window we forward self event to
6292                ptScreen = self.ClientToScreen(pos)
6293                p = win.ScreenToClient(ptScreen)
6294
6295                if win.GetClientRect().Contains(p):
6296                    win.ProcessMouseLClick(p)
6297                    return
6298
6299                else:
6300                    # try the grandparent
6301                    win = win._parentMenu
6302
6303            # At this point we can assume that the event was not
6304            # processed, so we dismiss the menu and its children
6305            self.Dismiss(True, True)
6306            return
6307
6308
6309    def ProcessMouseLClickEnd(self, pos):
6310        """
6311        Processes mouse left clicks.
6312
6313        :param `pos`: the position at which the mouse left button was pressed,
6314         an instance of :class:`wx.Point`.
6315        """
6316
6317        self.ProcessMouseLClick(pos)
6318
6319        # test if we are on a menu item
6320        res, itemIdx = self.HitTest(pos)
6321
6322        if res == MENU_HT_ITEM:
6323            self.DoAction(itemIdx)
6324
6325        elif res == MENU_HT_SCROLL_UP:
6326            if self._upButton:
6327                self._upButton.ProcessLeftDown(pos)
6328
6329        elif res == MENU_HT_SCROLL_DOWN:
6330            if self._downButton:
6331                self._downButton.ProcessLeftDown(pos)
6332
6333        else:
6334            self._selectedItem = -1
6335
6336
6337    def ProcessMouseMove(self, pos):
6338        """
6339        Processes mouse movements.
6340
6341        :param `pos`: the position at which the mouse was moved, an instance of :class:`wx.Point`.
6342        """
6343
6344        rect = self.GetClientRect()
6345
6346        if not rect.Contains(pos):
6347
6348            # The event is not in our coords,
6349            # so we try our parent
6350
6351            win = self._parentMenu
6352            while win:
6353
6354                # we need to translate our client coords to the client coords of the
6355                # window we forward self event to
6356                ptScreen = self.ClientToScreen(pos)
6357                p = win.ScreenToClient(ptScreen)
6358
6359                if win.GetClientRect().Contains(p):
6360                    win.ProcessMouseMove(p)
6361                    return
6362
6363                else:
6364                    # try the grandparent
6365                    win = win._parentMenu
6366
6367            # If we are attached to a menu bar,
6368            # let him process the event as well
6369            if self._mb:
6370
6371                ptScreen = self.ClientToScreen(pos)
6372                p = self._mb.ScreenToClient(ptScreen)
6373
6374                if self._mb.GetClientRect().Contains(p):
6375
6376                    # let the menu bar process it
6377                    self._mb.ProcessMouseMoveFromMenu(p)
6378                    return
6379
6380            if self._mb_submenu:
6381                ptScreen = self.ClientToScreen(pos)
6382                p = self._mb_submenu.ScreenToClient(ptScreen)
6383                if self._mb_submenu.GetClientRect().Contains(p):
6384                    # let the menu bar process it
6385                    self._mb_submenu.ProcessMouseMoveFromMenu(p)
6386                    return
6387
6388            return
6389
6390        # test if we are on a menu item
6391        res, itemIdx = self.HitTest(pos)
6392
6393        if res == MENU_HT_SCROLL_DOWN:
6394
6395            if self._downButton:
6396                self._downButton.ProcessMouseMove(pos)
6397
6398        elif res == MENU_HT_SCROLL_UP:
6399
6400            if self._upButton:
6401                self._upButton.ProcessMouseMove(pos)
6402
6403        elif res == MENU_HT_ITEM:
6404
6405            if self._downButton:
6406                self._downButton.ProcessMouseMove(pos)
6407
6408            if self._upButton:
6409                self._upButton.ProcessMouseMove(pos)
6410
6411            if self._selectedItem == itemIdx:
6412                return
6413
6414            # Message to send when out of last selected item
6415            if self._selectedItem != -1:
6416                self.SendOverItem(self._selectedItem, False)
6417            self.SendOverItem(itemIdx, True)   # Message to send when over an item
6418
6419            oldSelection = self._selectedItem
6420            self._selectedItem = itemIdx
6421            self.CloseSubMenu(self._selectedItem)
6422
6423            dc = wx.ClientDC(self)
6424            self.DrawSelection(dc, oldSelection)
6425
6426            self.TryOpenSubMenu(self._selectedItem)
6427
6428            if self._mb:
6429                self._mb.RemoveHelp()
6430                if itemIdx >= 0:
6431                    self._mb.DoGiveHelp(self._itemsArr[itemIdx])
6432
6433        else:
6434
6435            # Message to send when out of last selected item
6436            if self._selectedItem != -1:
6437                item = self._itemsArr[self._selectedItem]
6438                if item.IsSubMenu() and item.GetSubMenu().IsShown():
6439                    return
6440
6441                # Message to send when out of last selected item
6442                if self._selectedItem != -1:
6443                    self.SendOverItem(self._selectedItem, False)
6444
6445            oldSelection = self._selectedItem
6446            self._selectedItem = -1
6447            dc = wx.ClientDC(self)
6448            self.DrawSelection(dc, oldSelection)
6449
6450
6451    def OnMouseLeaveWindow(self, event):
6452        """
6453        Handles the ``wx.EVT_LEAVE_WINDOW`` event for :class:`FlatMenu`.
6454
6455        :param `event`: a :class:`MouseEvent` event to be processed.
6456        """
6457
6458        if self._mb:
6459            self._mb.RemoveHelp()
6460
6461        if self._clearCurrentSelection:
6462
6463            # Message to send when out of last selected item
6464            if self._selectedItem != -1:
6465                item = self._itemsArr[self._selectedItem]
6466                if item.IsSubMenu() and item.GetSubMenu().IsShown():
6467                    return
6468
6469                # Message to send when out of last selected item
6470                if self._selectedItem != -1:
6471                    self.SendOverItem(self._selectedItem, False)
6472
6473            oldSelection = self._selectedItem
6474            self._selectedItem = -1
6475            dc = wx.ClientDC(self)
6476            self.DrawSelection(dc, oldSelection)
6477
6478        self._clearCurrentSelection = True
6479
6480        if "__WXMSW__" in wx.Platform:
6481            self.SetCursor(self._oldCur)
6482
6483
6484    def OnMouseEnterWindow(self, event):
6485        """
6486        Handles the ``wx.EVT_ENTER_WINDOW`` event for :class:`FlatMenu`.
6487
6488        :param `event`: a :class:`MouseEvent` event to be processed.
6489        """
6490
6491        if "__WXMSW__" in wx.Platform:
6492            self._oldCur = self.GetCursor()
6493            self.SetCursor(wx.Cursor(wx.CURSOR_ARROW))
6494
6495        event.Skip()
6496
6497
6498    def OnKillFocus(self, event):
6499        """
6500        Handles the ``wx.EVT_KILL_FOCUS`` event for :class:`FlatMenu`.
6501
6502        :param `event`: a :class:`FocusEvent` event to be processed.
6503        """
6504
6505        self.Dismiss(True, True)
6506
6507
6508    def CloseSubMenu(self, itemIdx, alwaysClose=False):
6509        """
6510        Closes a child sub-menu.
6511
6512        :param integer `itemIdx`: the index of the item for which we want to close the submenu;
6513        :param bool `alwaysClose`: if ``True``, always close the submenu irrespectively of
6514         other conditions.
6515        """
6516
6517        item = None
6518        subMenu = None
6519
6520        if itemIdx >= 0 and itemIdx < len(self._itemsArr):
6521            item = self._itemsArr[itemIdx]
6522
6523        # Close sub-menu first
6524        if item:
6525            subMenu = item.GetSubMenu()
6526
6527        if self._openedSubMenu:
6528            if self._openedSubMenu != subMenu or alwaysClose:
6529                # We have another sub-menu open, close it
6530                self._openedSubMenu.Dismiss(False, True)
6531                self._openedSubMenu = None
6532
6533
6534    def DoAction(self, itemIdx):
6535        """
6536        Performs an action based on user selection.
6537
6538        :param integer `itemIdx`: the index of the item for which we want to perform the action.
6539        """
6540
6541        if itemIdx < 0 or itemIdx >= len(self._itemsArr):
6542            raise Exception("Invalid menu item")
6543            return
6544
6545        item = self._itemsArr[itemIdx]
6546
6547        if not item.IsEnabled() or item.IsSeparator():
6548            return
6549
6550        # Close sub-menu if needed
6551        self.CloseSubMenu(itemIdx)
6552
6553        if item.IsSubMenu() and not item.GetSubMenu().IsShown():
6554
6555            # Popup child menu
6556            self.TryOpenSubMenu(itemIdx)
6557            return
6558
6559        if item.IsRadioItem():
6560            # if the radio item is already checked,
6561            # just send command event. Else, check it, uncheck the current
6562            # checked item in the radio item group, and send command event
6563            if not item.IsChecked():
6564                item._groupPtr.SetSelection(item)
6565
6566        elif item.IsCheckable():
6567
6568            item.Check(not item.IsChecked())
6569            dc = wx.ClientDC(self)
6570            self.DrawSelection(dc)
6571
6572        if not item.IsSubMenu():
6573
6574            self.Dismiss(True, False)
6575
6576            # Send command event
6577            self.SendCmdEvent(itemIdx)
6578
6579
6580    def TryOpenSubMenu(self, itemIdx, selectFirst=False):
6581        """
6582        If `itemIdx` is an item with submenu, open it.
6583
6584        :param integer `itemIdx`: the index of the item for which we want to open the submenu;
6585        :param bool `selectFirst`: if ``True``, the first item of the submenu will be shown
6586         as selected.
6587        """
6588
6589        if itemIdx < 0 or itemIdx >= len(self._itemsArr):
6590            return False
6591
6592        item = self._itemsArr[itemIdx]
6593        if item.IsSubMenu() and not item.GetSubMenu().IsShown():
6594
6595            pos = wx.Point()
6596
6597            # Popup child menu
6598            pos.x = item.GetRect().GetWidth()+ item.GetRect().GetX()-5
6599            pos.y = item.GetRect().GetY()
6600            self._clearCurrentSelection = False
6601            self._openedSubMenu = item.GetSubMenu()
6602            item.GetSubMenu().Popup(pos, self._owner, self)
6603
6604            # Select the first child
6605            if selectFirst:
6606
6607                dc = wx.ClientDC(item.GetSubMenu())
6608                item.GetSubMenu()._selectedItem = 0
6609                item.GetSubMenu().DrawSelection(dc)
6610
6611            return True
6612
6613        return False
6614
6615
6616    def _RemoveById(self, id):
6617        """ Used internally. """
6618
6619        # First we search for the menu item (recursively)
6620        menuParent = None
6621        item = None
6622        idx = wx.NOT_FOUND
6623        idx, menuParent = self.FindMenuItemPos(id)
6624
6625        if idx != wx.NOT_FOUND:
6626
6627            # Remove the menu item
6628            item = menuParent._itemsArr[idx]
6629            menuParent._itemsArr.pop(idx)
6630
6631            # update group
6632            if item._groupPtr and item.IsRadioItem():
6633                item._groupPtr.Remove(item)
6634
6635            # Resize the menu
6636            menuParent.ResizeMenu()
6637
6638        return item
6639
6640
6641    def Remove(self, item):
6642        """
6643        Removes the menu item from the menu but doesn't delete the associated menu
6644        object. This allows to reuse the same item later by adding it back to the
6645        menu (especially useful with submenus).
6646
6647        :param `item`: can be either a menu item identifier or a plain :class:`FlatMenuItem`.
6648        """
6649
6650        if type(item) != type(1):
6651            item = item.GetId()
6652
6653        return self._RemoveById(item)
6654
6655    Delete = Remove
6656
6657
6658    def _DestroyById(self, id):
6659        """ Used internally. """
6660
6661        item = None
6662        item = self.Remove(id)
6663
6664        if item:
6665            del item
6666
6667
6668    def DestroyItem(self, item):
6669        """
6670        Deletes the menu item from the menu. If the item is a submenu, it will be
6671        deleted. Use :meth:`~FlatMenu.Remove` if you want to keep the submenu (for example, to reuse
6672        it later).
6673
6674        :param `item`: can be either a menu item identifier or a plain :class:`FlatMenuItem`.
6675        """
6676
6677        if type(item) != type(1):
6678            item = item.GetId()
6679
6680        self._DestroyById(item)
6681
6682
6683    def Insert(self, pos, id, item, helpString="", kind=wx.ITEM_NORMAL):
6684        """
6685        Inserts the given `item` before the position `pos`.
6686
6687        :param integer `pos`: the position at which to insert the new menu item;
6688        :param integer `id`: the menu item identifier;
6689        :param string `item`: the string to appear on the menu item;
6690        :param string `helpString`: an optional help string associated with the item. By default,
6691         the handler for the ``EVT_FLAT_MENU_ITEM_MOUSE_OVER`` event displays this string
6692         in the status line;
6693        :param integer `kind`: may be ``wx.ITEM_NORMAL`` for a normal button (default),
6694         ``wx.ITEM_CHECK`` for a checkable tool (such tool stays pressed after it had been
6695         toggled) or ``wx.ITEM_RADIO`` for a checkable tool which makes part of a radio
6696         group of tools each of which is automatically unchecked whenever another button
6697         in the group is checked;
6698        """
6699
6700        newitem = FlatMenuItem(self, id, item, helpString, kind)
6701        return self.InsertItem(pos, newitem)
6702
6703
6704    def InsertItem(self, pos, item):
6705        """
6706        Inserts an item into the menu.
6707
6708        :param integer `pos`: the position at which to insert the new menu item;
6709        :param `item`: an instance of :class:`FlatMenuItem`.
6710        """
6711
6712        if pos == len(self._itemsArr):
6713            # Append it
6714            return self.AppendItem(item)
6715
6716        # Insert the menu item
6717        self._itemsArr.insert(pos, item)
6718        item._isAttachedToMenu = True
6719
6720        # Recalculate the menu geometry
6721        self.ResizeMenu()
6722
6723        # Update radio groups
6724        self.UpdateRadioGroup(item)
6725
6726        return item
6727
6728
6729    def UpdateRadioGroup(self, item):
6730        """
6731        Updates a group of radio items.
6732
6733        :param `item`: an instance of :class:`FlatMenuItem`.
6734        """
6735
6736        if item.IsRadioItem():
6737
6738            # Udpate radio groups in case this item is a radio item
6739            sibling = self.GetSiblingGroupItem(item)
6740            if sibling:
6741
6742                item._groupPtr = sibling._groupPtr
6743                item._groupPtr.Add(item)
6744
6745                if item.IsChecked():
6746
6747                    item._groupPtr.SetSelection(item)
6748
6749            else:
6750
6751                # first item in group
6752                item._groupPtr = FlatMenuItemGroup()
6753                item._groupPtr.Add(item)
6754                item._groupPtr.SetSelection(item)
6755
6756
6757    def ResizeMenu(self):
6758        """ Resizes the menu to the correct size. """
6759
6760        # can we do the resize?
6761        if not self._resizeMenu:
6762            return
6763
6764        items = self._itemsArr
6765        self._itemsArr = []
6766
6767        # Clear accelerator table
6768        self._accelArray = []
6769
6770        # Reset parameters and menu size
6771        self._menuWidth =  2*self._marginWidth
6772        self._imgMarginX = 0
6773        self._markerMarginX = 0
6774        self._textX = self._marginWidth
6775        self._rightMarginPosX = -1
6776        self._itemHeight = self._marginHeight
6777        self.SetSize(wx.Size(self._menuWidth*self._numCols, self._itemHeight+4))
6778
6779        # Now we simply add the items
6780        for item in items:
6781            self.AppendItem(item)
6782
6783
6784    def SetNumberColumns(self, numCols):
6785        """
6786        Sets the number of columns for a menu window.
6787
6788        :param integer `numCols`: the number of columns for this :class:`FlatMenu` window.
6789        """
6790
6791        if self._numCols == numCols:
6792            return
6793
6794        self._numCols = numCols
6795        self.ResizeMenu()
6796        self.Refresh()
6797
6798
6799    def GetNumberColumns(self):
6800        """ Returns the number of columns for a menu window. """
6801
6802        return self._numCols
6803
6804
6805    def FindItem(self, itemId, menu=None):
6806        """
6807        Finds the menu item object associated with the given menu item identifier and,
6808        optionally, the (sub)menu it belongs to.
6809
6810        :param integer `itemId`: menu item identifier;
6811        :param `menu`: if not ``None``, it will be filled with the item's parent menu
6812         (if the item was found).
6813
6814        :return: The found menu item object, or ``None`` if one was not found.
6815        """
6816
6817        idx = wx.NOT_FOUND
6818
6819        if menu:
6820
6821            idx, menu = self.FindMenuItemPos(itemId, menu)
6822            if idx != wx.NOT_FOUND:
6823                return menu._itemsArr[idx]
6824            else:
6825                return None
6826
6827        else:
6828
6829            idx, parentMenu = self.FindMenuItemPos(itemId, None)
6830            if idx != wx.NOT_FOUND:
6831                return parentMenu._itemsArr[idx]
6832            else:
6833                return None
6834
6835
6836    def SetItemFont(self, itemId, font=None):
6837        """
6838        Sets the :class:`FlatMenuItem` font.
6839
6840        :param integer `itemId`: the menu item identifier;
6841        :param `font`: an instance of a valid :class:`wx.Font`.
6842        """
6843
6844        item = self.FindItem(itemId)
6845        item.SetFont(font)
6846
6847
6848    def GetItemFont(self, itemId):
6849        """
6850        Returns this :class:`FlatMenuItem` font.
6851
6852        :param integer `itemId`: the menu item identifier.
6853        """
6854
6855        item = self.FindItem(itemId)
6856        return item.GetFont()
6857
6858
6859    def SetItemTextColour(self, itemId, colour=None):
6860        """
6861        Sets the :class:`FlatMenuItem` foreground text colour.
6862
6863        :param integer `itemId`: the menu item identifier;
6864        :param `colour`: an instance of a valid :class:`wx.Colour`.
6865        """
6866
6867        item = self.FindItem(itemId)
6868        item.SetTextColour(colour)
6869
6870
6871    def GetItemTextColour(self, itemId):
6872        """
6873        Returns this :class:`FlatMenuItem` foreground text colour.
6874
6875        :param integer `itemId`: the menu item identifier.
6876        """
6877
6878        item = self.FindItem(itemId)
6879        return item.GetTextColour()
6880
6881
6882    def SetLabel(self, itemId, label):
6883        """
6884        Sets the label of a :class:`FlatMenuItem`.
6885
6886        :param integer `itemId`: the menu item identifier;
6887        :param string `label`: the menu item label to set.
6888
6889        :see: :meth:`~FlatMenu.GetLabel`.
6890        """
6891
6892        item = self.FindItem(itemId)
6893        item.SetLabel(label)
6894        item.SetText(label)
6895
6896        self.ResizeMenu()
6897
6898
6899    def GetLabel(self, itemId):
6900        """
6901        Returns the label of a :class:`FlatMenuItem`.
6902
6903        :param integer `id`: the menu item identifier;
6904
6905        :see: :meth:`~FlatMenu.SetLabel`.
6906        """
6907
6908        item = self.FindItem(itemId)
6909        return item.GetText()
6910
6911
6912    def FindMenuItemPos(self, itemId, menu=None):
6913        """
6914        Finds an item and its position inside the menu based on its id.
6915
6916        :param integer `itemId`: menu item identifier;
6917        :param `menu`: if not ``None``, it will be filled with the item's parent menu
6918         (if the item was found).
6919
6920        :return: The found menu item object, or ``None`` if one was not found.
6921        """
6922
6923        menu = None
6924        item = None
6925
6926        idx = wx.NOT_FOUND
6927
6928        for i in range(len(self._itemsArr)):
6929
6930            item = self._itemsArr[i]
6931
6932            if item.GetId() == itemId:
6933
6934                menu = self
6935                idx = i
6936                break
6937
6938            elif item.IsSubMenu():
6939
6940                idx, menu = item.GetSubMenu().FindMenuItemPos(itemId, menu)
6941                if idx != wx.NOT_FOUND:
6942                    break
6943
6944            else:
6945
6946                item = None
6947
6948        return idx, menu
6949
6950
6951    def GetAccelTable(self):
6952        """ Returns the menu accelerator table, an instance of :class:`AcceleratorTable`. """
6953
6954        n = len(self._accelArray)
6955        if n == 0:
6956            return wx.NullAcceleratorTable
6957
6958        entries = [wx.AcceleratorEntry() for ii in range(n)]
6959
6960        for counter in len(entries):
6961            entries[counter] = self._accelArray[counter]
6962
6963        table = wx.AcceleratorTable(entries)
6964        del entries
6965
6966        return table
6967
6968
6969    def GetMenuItemCount(self):
6970        """ Returns the number of items in the :class:`FlatMenu`. """
6971
6972        return len(self._itemsArr)
6973
6974
6975    def GetAccelArray(self):
6976        """ Returns a list filled with the accelerator entries for the menu. """
6977
6978        return self._accelArray
6979
6980
6981    # events
6982    def SendCmdEvent(self, itemIdx):
6983        """
6984        Actually sends menu command events.
6985
6986        :param integer `itemIdx`: the menu item index for which we want to send a command event.
6987        """
6988
6989        if itemIdx < 0 or itemIdx >= len(self._itemsArr):
6990            raise Exception("Invalid menu item")
6991            return
6992
6993        item = self._itemsArr[itemIdx]
6994
6995        # Create the event
6996        event = wx.CommandEvent(wxEVT_FLAT_MENU_SELECTED, item.GetId())
6997
6998        # For checkable item, set the IsChecked() value
6999        if item.IsCheckable():
7000            event.SetInt((item.IsChecked() and [1] or [0])[0])
7001
7002        event.SetEventObject(self)
7003
7004        if self._owner:
7005            self._owner.GetEventHandler().ProcessEvent(event)
7006        else:
7007            self.GetEventHandler().ProcessEvent(event)
7008
7009
7010    def SendOverItem(self, itemIdx, over):
7011        """
7012        Sends the ``EVT_FLAT_MENU_ITEM_MOUSE_OVER`` and ``EVT_FLAT_MENU_ITEM_MOUSE_OUT``
7013        events.
7014
7015        :param integer `itemIdx`: the menu item index for which we want to send an event;
7016        :param bool `over`: ``True`` to send a ``EVT_FLAT_MENU_ITEM_MOUSE_OVER`` event, ``False`` to
7017         send a ``EVT_FLAT_MENU_ITEM_MOUSE_OUT`` event.
7018        """
7019
7020        item = self._itemsArr[itemIdx]
7021
7022        # Create the event
7023        event = FlatMenuEvent((over and [wxEVT_FLAT_MENU_ITEM_MOUSE_OVER] or [wxEVT_FLAT_MENU_ITEM_MOUSE_OUT])[0], item.GetId())
7024
7025        # For checkable item, set the IsChecked() value
7026        if item.IsCheckable():
7027            event.SetInt((item.IsChecked() and [1] or [0])[0])
7028
7029        event.SetEventObject(self)
7030
7031        if self._owner:
7032            self._owner.GetEventHandler().ProcessEvent(event)
7033        else:
7034            self.GetEventHandler().ProcessEvent(event)
7035
7036
7037    def SendUIEvent(self, itemIdx):
7038        """
7039        Actually sends menu UI events.
7040
7041        :param integer `itemIdx`: the menu item index for which we want to send a UI event.
7042        """
7043
7044        if itemIdx < 0 or itemIdx >= len(self._itemsArr):
7045            raise Exception("Invalid menu item")
7046            return
7047
7048        item = self._itemsArr[itemIdx]
7049        event = wx.UpdateUIEvent(item.GetId())
7050
7051        event.Check(item.IsChecked())
7052        event.Enable(item.IsEnabled())
7053        event.SetText(item.GetText())
7054        event.SetEventObject(self)
7055
7056        if self._owner:
7057            self._owner.GetEventHandler().ProcessEvent(event)
7058        else:
7059            self.GetEventHandler().ProcessEvent(event)
7060
7061        item.Check(event.GetChecked())
7062        item.SetLabel(event.GetText())
7063        item.Enable(event.GetEnabled())
7064
7065
7066    def Clear(self):
7067        """ Clears the menu items. """
7068
7069        # since Destroy() call ResizeMenu(), we turn this flag on
7070        # to avoid resizing the menu for every item removed
7071        self._resizeMenu = False
7072
7073        lenItems = len(self._itemsArr)
7074        for ii in range(lenItems):
7075            self.DestroyItem(self._itemsArr[0].GetId())
7076
7077        # Now we can resize the menu
7078        self._resizeMenu = True
7079        self.ResizeMenu()
7080
7081
7082    def FindMenuItemPosSimple(self, item):
7083        """
7084        Finds an item and its position inside the menu based on its id.
7085
7086        :param `item`: an instance of :class:`FlatMenuItem`.
7087
7088        :return: An integer specifying the index found menu item object, or
7089         ``wx.NOT_FOUND`` if one was not found.
7090        """
7091
7092        if item == None or len(self._itemsArr) == 0:
7093            return wx.NOT_FOUND
7094
7095        for i in range(len(self._itemsArr)):
7096            if self._itemsArr[i] == item:
7097                return i
7098
7099        return wx.NOT_FOUND
7100
7101
7102    def SetBackgroundBitmap(self, bitmap=None):
7103        """
7104        Sets a background bitmap for this particular :class:`FlatMenu`.
7105
7106        :param `bitmap`: an instance of :class:`wx.Bitmap`. Set `bitmap` to ``None`` if you
7107         wish to remove the background bitmap altogether.
7108
7109        :note: the bitmap is rescaled to fit the menu width and height.
7110        """
7111
7112        self._originalBackgroundImage = bitmap
7113        # Now we can resize the menu
7114        self._resizeMenu = True
7115        self.ResizeMenu()
7116
7117
7118    def GetBackgroundBitmap(self):
7119        """ Returns the background bitmap for this particular :class:`FlatMenu`, if any. """
7120
7121        return self._originalBackgroundImage
7122
7123
7124    def GetAllItems(self, menu=None, items=[]):
7125        """
7126        Internal function to help recurse through all the menu items.
7127
7128        :param `menu`: the menu from which we start accumulating items;
7129        :param list `items`: the array which is recursively filled with menu items.
7130
7131        :return: a list of :class:`FlatMenuItem`.
7132        """
7133
7134        # first copy the current menu items
7135        newitems = [item for item in items]
7136
7137        if not menu:
7138            return newitems
7139
7140        # if any item in this menu has sub-menu, copy them as well
7141        for i in range(len(menu._itemsArr)):
7142            if menu._itemsArr[i].IsSubMenu():
7143                newitems = self.GetAllItems(menu._itemsArr[i].GetSubMenu(), newitems)
7144
7145        return newitems
7146
7147
7148    def GetSiblingGroupItem(self, item):
7149        """
7150        Used internally.
7151
7152        :param `item`: an instance of :class:`FlatMenuItem`.
7153        """
7154
7155        pos = self.FindMenuItemPosSimple(item)
7156        if pos in [wx.NOT_FOUND, 0]:
7157            return None
7158
7159        if self._itemsArr[pos-1].IsRadioItem():
7160            return self._itemsArr[pos-1]
7161
7162        return None
7163
7164
7165    def ScrollDown(self):
7166        """ Scrolls the menu down (for very tall menus). """
7167
7168        # increase the self._from index
7169        if not self._itemsArr[-1].IsShown():
7170            self._first += 1
7171            self.Refresh()
7172
7173            return True
7174
7175        else:
7176            if self._downButton:
7177                self._downButton.GetTimer().Stop()
7178
7179            return False
7180
7181
7182    def ScrollUp(self):
7183        """ Scrolls the menu up (for very tall menus). """
7184
7185        if self._first == 0:
7186            if self._upButton:
7187                self._upButton.GetTimer().Stop()
7188
7189            return False
7190
7191        else:
7192
7193            self._first -= 1
7194            self.Refresh()
7195            return True
7196
7197
7198    # Not used anymore
7199    def TryScrollButtons(self, event):
7200        """ Used internally. """
7201
7202        return False
7203
7204
7205    def OnTimer(self, event):
7206        """
7207        Handles the ``wx.EVT_TIMER`` event for :class:`FlatMenu`.
7208
7209        :param `event`: a :class:`TimerEvent` event to be processed.
7210        """
7211
7212        if self._upButton and self._upButton.GetTimerId() == event.GetId():
7213
7214            self.ScrollUp()
7215
7216        elif self._downButton and self._downButton.GetTimerId() == event.GetId():
7217
7218            self.ScrollDown()
7219
7220        else:
7221
7222            event.Skip()
7223
7224
7225#--------------------------------------------------------
7226# Class MenuKbdRedirector
7227#--------------------------------------------------------
7228
7229class MenuKbdRedirector(wx.EvtHandler):
7230    """ A keyboard event handler. """
7231
7232    def __init__(self, menu, oldHandler):
7233        """
7234        Default class constructor.
7235
7236        :param `menu`: an instance of :class:`FlatMenu` for which we want to redirect
7237         keyboard inputs;
7238        :param `oldHandler`: a previous (if any) :class:`EvtHandler` associated with
7239         the menu.
7240        """
7241
7242        self._oldHandler = oldHandler
7243        self.SetMenu(menu)
7244        wx.EvtHandler.__init__(self)
7245
7246
7247    def SetMenu(self, menu):
7248        """
7249        Sets the listener menu.
7250
7251        :param `menu`: an instance of :class:`FlatMenu`.
7252        """
7253
7254        self._menu = menu
7255
7256
7257    def ProcessEvent(self, event):
7258        """
7259        Processes the inout event.
7260
7261        :param `event`: any kind of keyboard-generated events.
7262        """
7263
7264        if event.GetEventType() in [wx.EVT_KEY_DOWN, wx.EVT_CHAR, wx.EVT_CHAR_HOOK]:
7265            return self._menu.OnChar(event.GetKeyCode())
7266        else:
7267            return self._oldHandler.ProcessEvent(event)
7268
7269
7270#--------------------------------------------------------
7271# Class FocusHandler
7272#--------------------------------------------------------
7273
7274class FocusHandler(wx.EvtHandler):
7275    """ A focus event handler. """
7276
7277    def __init__(self, menu):
7278        """
7279        Default class constructor.
7280
7281        :param `menu`: an instance of :class:`FlatMenu` for which we want to redirect
7282         focus inputs.
7283        """
7284
7285        wx.EvtHandler.__init__(self)
7286        self.SetMenu(menu)
7287
7288        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
7289        self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
7290
7291
7292    def SetMenu(self, menu):
7293        """
7294        Sets the listener menu.
7295
7296        :param `menu`: an instance of :class:`FlatMenu`.
7297        """
7298
7299        self._menu = menu
7300
7301
7302    def OnKeyDown(self, event):
7303        """
7304        Handles the ``wx.EVT_KEY_DOWN`` event for :class:`FocusHandler`.
7305
7306        :param `event`: a :class:`KeyEvent` event to be processed.
7307        """
7308
7309        # Let parent process it
7310        self._menu.OnKeyDown(event)
7311
7312
7313    def OnKillFocus(self, event):
7314        """
7315        Handles the ``wx.EVT_KILL_FOCUS`` event for :class:`FocusHandler`.
7316
7317        :param `event`: a :class:`FocusEvent` event to be processed.
7318        """
7319
7320        wx.PostEvent(self._menu, event)
7321
7322
7323