1###############################################################################
2# Name: platebtn.py                                                           #
3# Purpose: PlateButton is a flat label button with support for bitmaps and    #
4#          drop menu.                                                         #
5# Author: Cody Precord <cprecord@editra.org>                                  #
6# Copyright: (c) 2007 Cody Precord <staff@editra.org>                         #
7# Licence: wxWindows Licence                                                  #
8# Tags: phoenix-port
9###############################################################################
10
11"""
12Editra Control Library: PlateButton
13
14The PlateButton is a custom owner drawn flat button, that in many ways emulates
15the buttons found the bookmark bar of the Safari browser. It can be used as a
16drop in replacement for wx.Button/wx.BitmapButton under most circumstances. It
17also offers a wide range of options for customizing its appearance, a
18description of each of the main style settings is listed below.
19
20Main Button Styles:
21Any combination of the following values may be passed to the constructor's style
22keyword parameter.
23
24PB_STYLE_DEFAULT:
25Creates a flat label button with rounded corners, the highlight for mouse over
26and press states is based off of the hightlight color from the systems current
27theme.
28
29PB_STYLE_GRADIENT:
30The highlight and press states are drawn with gradient using the current
31highlight color.
32
33PB_STYLE_SQUARE:
34Instead of the default rounded shape use a rectangular shaped button with
35square edges.
36
37PB_STYLE_NOBG:
38This style only has an effect on Windows but does not cause harm to use on the
39platforms. It should only be used when the control is shown on a panel or other
40window that has a non solid color for a background. i.e a gradient or image is
41painted on the background of the parent window. If used on a background with
42a solid color it may cause the control to loose its transparent appearance.
43
44PB_STYLE_DROPARROW:
45Add a drop button arrow to the button that will send a separate event when
46clicked on.
47
48Other attributes can be configured after the control has been created. The
49settings that are currently available are as follows:
50
51  - SetBitmap: Change/Add the bitmap at any time and the control will resize and
52               refresh to display it.
53  - SetLabelColor: Explicitly set text colors
54  - SetMenu: Set the button to have a popupmenu. When a menu is set a small drop
55             arrow will be drawn on the button that can then be clicked to show
56             a menu.
57  - SetPressColor: Use a custom highlight color
58
59
60Overridden Methods Inherited from PyControl:
61
62  - SetFont: Changing the font is one way to set the size of the button, by
63             default the control will inherit its font from its parent.
64
65  - SetWindowVariant: Setting the window variant will cause the control to
66                      resize to the corresponding variant size. However if the
67                      button is using a bitmap the bitmap will remain unchanged
68                      and only the font will be adjusted.
69
70Requirements:
71  - python2.4 or higher
72  - wxPython2.8 or higher
73
74"""
75
76__author__ = "Cody Precord <cprecord@editra.org>"
77
78__all__ = ["PlateButton",
79           "PLATE_NORMAL", "PLATE_PRESSED", "PLATE_HIGHLIGHT",
80
81           "PB_STYLE_DEFAULT", "PB_STYLE_GRADIENT", "PB_STYLE_SQUARE",
82           "PB_STYLE_NOBG", "PB_STYLE_DROPARROW", "PB_STYLE_TOGGLE",
83
84           "EVT_PLATEBTN_DROPARROW_PRESSED"]
85
86#-----------------------------------------------------------------------------#
87# Imports
88import wx
89import wx.lib.newevent
90
91# Local Imports
92from wx.lib.colourutils import *
93
94#-----------------------------------------------------------------------------#
95# Button States
96PLATE_NORMAL = 0
97PLATE_PRESSED = 1
98PLATE_HIGHLIGHT = 2
99
100# Button Styles
101PB_STYLE_DEFAULT  = 1   # Normal Flat Background
102PB_STYLE_GRADIENT = 2   # Gradient Filled Background
103PB_STYLE_SQUARE   = 4   # Use square corners instead of rounded
104PB_STYLE_NOBG     = 8   # Useful on Windows to get a transparent appearance
105                        # when the control is shown on a non solid background
106PB_STYLE_DROPARROW = 16 # Draw drop arrow and fire EVT_PLATEBTN_DROPRROW_PRESSED event
107PB_STYLE_TOGGLE   = 32  # Stay pressed until clicked again
108
109#-----------------------------------------------------------------------------#
110
111# EVT_BUTTON used for normal event notification
112# EVT_TOGGLE_BUTTON used for toggle button mode notification
113PlateBtnDropArrowPressed, EVT_PLATEBTN_DROPARROW_PRESSED = wx.lib.newevent.NewEvent()
114
115#-----------------------------------------------------------------------------#
116
117class PlateButton(wx.Control):
118    """PlateButton is a custom type of flat button with support for
119    displaying bitmaps and having an attached dropdown menu.
120
121    """
122    def __init__(self, parent, id=wx.ID_ANY, label='', bmp=None,
123                 pos=wx.DefaultPosition, size=wx.DefaultSize,
124                 style=PB_STYLE_DEFAULT, name=wx.ButtonNameStr):
125        """Create a PlateButton
126
127        :keyword string `label`: Buttons label text
128        :keyword wx.Bitmap `bmp`: Buttons bitmap
129        :keyword `style`: Button style
130
131        """
132        super(PlateButton, self).__init__(parent, id, pos, size,
133                                          wx.BORDER_NONE|wx.TRANSPARENT_WINDOW,
134                                          name=name)
135
136        # Attributes
137        self.InheritAttributes()
138        self._bmp = dict(enable=None, disable=None)
139        if bmp is not None:
140            assert isinstance(bmp, wx.Bitmap) and bmp.IsOk()
141            self._bmp['enable'] = bmp
142            img = bmp.ConvertToImage()
143            img = img.ConvertToGreyscale(.795, .073, .026) #(.634, .224, .143)
144            self._bmp['disable'] = wx.Bitmap(img)
145
146        self._menu = None
147        self.SetLabel(label)
148        self._style = style
149        self._state = dict(pre=PLATE_NORMAL, cur=PLATE_NORMAL)
150        self._color = self.__InitColors()
151        self._pressed = False
152
153        # Setup Initial Size
154        self.SetInitialSize(size)
155
156        # Event Handlers
157        self.Bind(wx.EVT_PAINT, lambda evt: self.__DrawButton())
158        self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnErase)
159        self.Bind(wx.EVT_SET_FOCUS, self.OnFocus)
160        self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
161
162        # Mouse Events
163        self.Bind(wx.EVT_LEFT_DCLICK, lambda evt: self._ToggleState())
164        self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
165        self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
166        self.Bind(wx.EVT_ENTER_WINDOW,
167                  lambda evt: self._SetState(PLATE_HIGHLIGHT))
168        self.Bind(wx.EVT_LEAVE_WINDOW,
169                  lambda evt: wx.CallLater(80, self.__LeaveWindow))
170
171        # Other events
172        self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
173        self.Bind(wx.EVT_CONTEXT_MENU, lambda evt: self.ShowMenu())
174
175
176    def __DrawBitmap(self, gc):
177        """Draw the bitmap if one has been set
178
179        :param wx.GCDC `gc`: :class:`wx.GCDC` to draw with
180        :return: x cordinate to draw text at
181
182        """
183        if self.IsEnabled():
184            bmp = self._bmp['enable']
185        else:
186            bmp = self._bmp['disable']
187
188        if bmp is not None and bmp.IsOk():
189            bw, bh = bmp.GetSize()
190            ypos = (self.GetSize()[1] - bh) // 2
191            gc.DrawBitmap(bmp, 6, ypos, bmp.GetMask() != None)
192            return bw + 6
193        else:
194            return 6
195
196
197    def __DrawDropArrow(self, gc, xpos, ypos):
198        """Draw a drop arrow if needed and restore pen/brush after finished
199
200        :param wx.GCDC `gc`: :class:`wx.GCDC` to draw with
201        :param int `xpos`: x cord to start at
202        :param int `ypos`: y cord to start at
203
204        """
205        if self._menu is not None or self._style & PB_STYLE_DROPARROW:
206            # Positioning needs a little help on Windows
207            if wx.Platform == '__WXMSW__':
208                xpos -= 2
209            tripoints = [(xpos, ypos), (xpos + 6, ypos), (xpos + 3, ypos + 5)]
210            brush_b = gc.GetBrush()
211            pen_b = gc.GetPen()
212            gc.SetPen(wx.TRANSPARENT_PEN)
213            gc.SetBrush(wx.Brush(gc.GetTextForeground()))
214            gc.DrawPolygon(tripoints)
215            gc.SetBrush(brush_b)
216            gc.SetPen(pen_b)
217        else:
218            pass
219
220
221    def __DrawHighlight(self, gc, width, height):
222        """Draw the main highlight/pressed state
223
224        :param wx.GCDC `gc`: :class:`wx.GCDC` to draw with
225        :param int `width`: width of highlight
226        :param int `height`: height of highlight
227
228        """
229        if self._state['cur'] == PLATE_PRESSED:
230            color = self._color['press']
231        else:
232            color = self._color['hlight']
233
234        if self._style & PB_STYLE_SQUARE:
235            rad = 0
236        else:
237            rad = (height - 3) / 2
238
239        if self._style & PB_STYLE_GRADIENT:
240            gc.SetBrush(wx.TRANSPARENT_BRUSH)
241            rgc = gc.GetGraphicsContext()
242            brush = rgc.CreateLinearGradientBrush(0, 1, 0, height,
243                                                  color, AdjustAlpha(color, 55))
244            rgc.SetBrush(brush)
245        else:
246            gc.SetBrush(wx.Brush(color))
247
248        gc.DrawRoundedRectangle(1, 1, width - 2, height - 2, rad)
249
250
251    def __PostEvent(self):
252        """Post a button event to parent of this control"""
253        if self._style & PB_STYLE_TOGGLE:
254            etype = wx.wxEVT_COMMAND_TOGGLEBUTTON_CLICKED
255        else:
256            etype = wx.wxEVT_COMMAND_BUTTON_CLICKED
257        bevt = wx.CommandEvent(etype, self.GetId())
258        bevt.SetEventObject(self)
259        bevt.SetString(self.GetLabel())
260        self.GetEventHandler().ProcessEvent(bevt)
261
262
263    def __DrawButton(self):
264        """Draw the button"""
265        # TODO using a buffered paintdc on windows with the nobg style
266        #      causes lots of weird drawing. So currently the use of a
267        #      buffered dc is dissabled for this style.
268        if PB_STYLE_NOBG & self._style:
269            dc = wx.PaintDC(self)
270        else:
271            dc = wx.AutoBufferedPaintDCFactory(self)
272
273        gc = wx.GCDC(dc)
274
275        # Setup
276        dc.SetBrush(wx.TRANSPARENT_BRUSH)
277        gc.SetBrush(wx.TRANSPARENT_BRUSH)
278        gc.SetFont(self.Font)
279        dc.SetFont(self.Font)
280        gc.SetBackgroundMode(wx.TRANSPARENT)
281
282        # The background needs some help to look transparent on
283        # on Gtk and Windows
284        if wx.Platform in ['__WXGTK__', '__WXMSW__']:
285            gc.SetBackground(self.GetBackgroundBrush(gc))
286            gc.Clear()
287
288        # Calc Object Positions
289        width, height = self.GetSize()
290        if wx.Platform == '__WXGTK__':
291            tw, th = dc.GetTextExtent(self.Label)
292        else:
293            tw, th = gc.GetTextExtent(self.Label)
294        txt_y = max((height - th) // 2, 1)
295
296        if self._state['cur'] == PLATE_HIGHLIGHT:
297            gc.SetTextForeground(self._color['htxt'])
298            gc.SetPen(wx.TRANSPARENT_PEN)
299            self.__DrawHighlight(gc, width, height)
300
301        elif self._state['cur'] == PLATE_PRESSED:
302            gc.SetTextForeground(self._color['htxt'])
303            if wx.Platform == '__WXMAC__':
304                pen = wx.Pen(GetHighlightColour(), 1, wx.PENSTYLE_SOLID)
305            else:
306                pen = wx.Pen(AdjustColour(self._color['press'], -80, 220), 1)
307            gc.SetPen(pen)
308
309            self.__DrawHighlight(gc, width, height)
310            txt_x = self.__DrawBitmap(gc)
311            if wx.Platform == '__WXGTK__':
312                dc.DrawText(self.Label, txt_x + 2, txt_y)
313            else:
314                gc.DrawText(self.Label, txt_x + 2, txt_y)
315            self.__DrawDropArrow(gc, width - 10, (height // 2) - 2)
316
317        else:
318            if self.IsEnabled():
319                gc.SetTextForeground(self.GetForegroundColour())
320            else:
321                txt_c = wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)
322                gc.SetTextForeground(txt_c)
323
324        # Draw bitmap and text
325        if self._state['cur'] != PLATE_PRESSED:
326            txt_x = self.__DrawBitmap(gc)
327            if wx.Platform == '__WXGTK__':
328                dc.DrawText(self.Label, txt_x + 2, txt_y)
329            else:
330                gc.DrawText(self.Label, txt_x + 2, txt_y)
331            self.__DrawDropArrow(gc, width - 10, (height // 2) - 2)
332
333
334    def __InitColors(self):
335        """Initialize the default colors"""
336        color = GetHighlightColour()
337        pcolor = AdjustColour(color, -12)
338        colors = dict(default=True,
339                      hlight=color,
340                      press=pcolor,
341                      htxt=BestLabelColour(self.GetForegroundColour()))
342        return colors
343
344
345    def __LeaveWindow(self):
346        """Handle updating the buttons state when the mouse cursor leaves"""
347        if (self._style & PB_STYLE_TOGGLE) and self._pressed:
348            self._SetState(PLATE_PRESSED)
349        else:
350            self._SetState(PLATE_NORMAL)
351            self._pressed = False
352
353
354    def _SetState(self, state):
355        """Manually set the state of the button
356
357        :param `state`: one of the PLATE_* values
358
359        .. note::
360            the state may be altered by mouse actions
361
362        .. note::
363            Internal use only!
364
365        """
366        self._state['pre'] = self._state['cur']
367        self._state['cur'] = state
368        if wx.Platform == '__WXMSW__':
369            self.Parent.RefreshRect(self.Rect, False)
370        else:
371            self.Refresh()
372
373
374    def _ToggleState(self):
375        """Toggle button state
376
377        ..note::
378            Internal Use Only!
379
380        """
381        if self._state['cur'] != PLATE_PRESSED:
382            self._SetState(PLATE_PRESSED)
383        else:
384            self._SetState(PLATE_HIGHLIGHT)
385
386    #---- End Private Member Function ----#
387
388    #---- Public Member Functions ----#
389
390    BitmapDisabled = property(lambda self: self.GetBitmapDisabled(),
391                              lambda self, bmp: self.SetBitmapDisabled(bmp))
392    BitmapLabel = property(lambda self: self.GetBitmapLabel(),
393                           lambda self, bmp: self.SetBitmap(bmp))
394
395    # Aliases
396    BitmapFocus = BitmapLabel
397    BitmapHover = BitmapLabel
398    BitmapSelected = BitmapLabel
399
400    LabelText = property(lambda self: self.GetLabel(),
401                         lambda self, lbl: self.SetLabel(lbl))
402
403
404    def AcceptsFocus(self):
405        """Can this window have the focus?"""
406        return self.IsEnabled()
407
408
409    def Disable(self):
410        """Disable the control"""
411        super(PlateButton, self).Disable()
412        self.Refresh()
413
414
415    def DoGetBestSize(self):
416        """Calculate the best size of the button
417
418        :return: :class:`wx.Size`
419
420        """
421        width = 4
422        height = 6
423        if self.Label:
424            # NOTE: Should measure with a GraphicsContext to get right
425            #       size, but due to random segfaults on linux special
426            #       handling is done in the drawing instead...
427            lsize = self.GetFullTextExtent(self.Label)
428            width += lsize[0]
429            height += lsize[1]
430
431        if self._bmp['enable'] is not None:
432            bsize = self._bmp['enable'].Size
433            width += (bsize[0] + 10)
434            if height <= bsize[1]:
435                height = bsize[1] + 6
436            else:
437                height += 3
438        else:
439            width += 10
440
441        if self._menu is not None or self._style & PB_STYLE_DROPARROW:
442            width += 12
443
444        best = wx.Size(width, height)
445        self.CacheBestSize(best)
446        return best
447
448
449    def Enable(self, enable=True):
450        """Enable/Disable the control"""
451        super(PlateButton, self).Enable(enable)
452        self.Refresh()
453
454
455    def GetBackgroundBrush(self, dc):
456        """Get the brush for drawing the background of the button
457
458        :return: :class:`wx.Brush`
459
460        ..note::
461            used internally when on gtk
462
463        """
464        if wx.Platform == '__WXMAC__' or self._style & PB_STYLE_NOBG:
465            return wx.TRANSPARENT_BRUSH
466
467        bkgrd = self.GetBackgroundColour()
468        brush = wx.Brush(bkgrd, wx.BRUSHSTYLE_SOLID)
469        my_attr = self.GetDefaultAttributes()
470        p_attr = self.Parent.GetDefaultAttributes()
471        my_def = bkgrd == my_attr.colBg
472        p_def = self.Parent.GetBackgroundColour() == p_attr.colBg
473        if my_def and not p_def:
474            bkgrd = self.Parent.GetBackgroundColour()
475            brush = wx.Brush(bkgrd, wx.BRUSHSTYLE_SOLID)
476        return brush
477
478
479    def GetBitmapDisabled(self):
480        """Get the bitmap of the disable state
481
482        :return: :class:`wx.Bitmap` or None
483
484        """
485        return self.BitmapDisabled
486
487
488    def GetBitmapLabel(self):
489        """Get the label bitmap
490
491        :return: :class:`wx.Bitmap` or None
492
493        """
494        return self.BitmapLabel
495
496    # GetBitmap Aliases for BitmapButton api
497    GetBitmapFocus = GetBitmapLabel
498    GetBitmapHover = GetBitmapLabel
499
500    # Alias for GetLabel
501    GetLabelText = wx.Control.GetLabel
502
503
504    def GetMenu(self):
505        """Return the menu associated with this button or None if no
506        menu is associated with it.
507
508        """
509        return self._menu
510
511
512    def GetState(self):
513        """Get the current state of the button
514
515        :return: int
516
517        .. seeAlso::
518            PLATE_NORMAL, PLATE_HIGHLIGHT, PLATE_PRESSED
519
520        """
521        return self._state['cur']
522
523
524    def HasTransparentBackground(self):
525        """Override setting of background fill"""
526        return True
527
528
529    def IsPressed(self):
530        """Return if button is pressed (PB_STYLE_TOGGLE)
531
532        :return: bool
533
534        """
535        return self._pressed
536
537
538    #---- Event Handlers ----#
539
540    def OnErase(self, evt):
541        """Trap the erase event to keep the background transparent
542        on windows.
543
544        :param `evt`: wx.EVT_ERASE_BACKGROUND
545
546        """
547        pass
548
549
550    def OnFocus(self, evt):
551        """Set the visual focus state if need be"""
552        if self._state['cur'] == PLATE_NORMAL:
553            self._SetState(PLATE_HIGHLIGHT)
554
555
556    def OnKeyUp(self, evt):
557        """Execute a single button press action when the Return key is pressed
558        and this control has the focus.
559
560        :param `evt`: wx.EVT_KEY_UP
561
562        """
563        if evt.GetKeyCode() == wx.WXK_SPACE:
564            self._SetState(PLATE_PRESSED)
565            self.__PostEvent()
566            wx.CallLater(100, self._SetState, PLATE_HIGHLIGHT)
567        else:
568            evt.Skip()
569
570
571    def OnKillFocus(self, evt):
572        """Set the visual state back to normal when focus is lost
573        unless the control is currently in a pressed state.
574
575        """
576        # Note: this delay needs to be at least as much as the on in the KeyUp
577        #       handler to prevent ghost highlighting from happening when
578        #       quickly changing focus and activating buttons
579        if self._state['cur'] != PLATE_PRESSED:
580            self._SetState(PLATE_NORMAL)
581
582
583    def OnLeftDown(self, evt):
584        """Sets the pressed state and depending on the click position will
585        show the popup menu if one has been set.
586
587        """
588        if (self._style & PB_STYLE_TOGGLE):
589            self._pressed = not self._pressed
590
591        pos = evt.GetPosition()
592        self._SetState(PLATE_PRESSED)
593        size = self.GetSize()
594        if pos[0] >= size[0] - 16:
595            if self._menu is not None:
596                self.ShowMenu()
597            elif self._style & PB_STYLE_DROPARROW:
598                event = PlateBtnDropArrowPressed()
599                event.SetEventObject(self)
600                self.EventHandler.ProcessEvent(event)
601
602        self.SetFocus()
603
604
605    def OnLeftUp(self, evt):
606        """Post a button event if the control was previously in a
607        pressed state.
608
609        :param `evt`: :class:`wx.MouseEvent`
610
611        """
612        if self._state['cur'] == PLATE_PRESSED:
613            pos = evt.GetPosition()
614            size = self.GetSize()
615            if not (self._style & PB_STYLE_DROPARROW and pos[0] >= size[0] - 16):
616                self.__PostEvent()
617
618        if self._pressed:
619            self._SetState(PLATE_PRESSED)
620        else:
621            self._SetState(PLATE_HIGHLIGHT)
622
623
624    def OnMenuClose(self, evt):
625        """Refresh the control to a proper state after the menu has been
626        dismissed.
627
628        :param `evt`: wx.EVT_MENU_CLOSE
629
630        """
631        mpos = wx.GetMousePosition()
632        if self.HitTest(self.ScreenToClient(mpos)) != wx.HT_WINDOW_OUTSIDE:
633            self._SetState(PLATE_HIGHLIGHT)
634        else:
635            self._SetState(PLATE_NORMAL)
636        evt.Skip()
637
638    #---- End Event Handlers ----#
639
640
641    def SetBitmap(self, bmp):
642        """Set the bitmap displayed in the button
643
644        :param `bmp`: :class:`wx.Bitmap`
645
646        """
647        self._bmp['enable'] = bmp
648        img = bmp.ConvertToImage()
649        img = img.ConvertToGreyscale(.795, .073, .026) #(.634, .224, .143)
650        self._bmp['disable'] = img.ConvertToBitmap()
651        self.InvalidateBestSize()
652
653
654    def SetBitmapDisabled(self, bmp):
655        """Set the bitmap for the disabled state
656
657        :param `bmp`: :class:`wx.Bitmap`
658
659        """
660        self._bmp['disable'] = bmp
661
662    # Aliases for SetBitmap* functions from BitmapButton
663    SetBitmapFocus = SetBitmap
664    SetBitmapHover = SetBitmap
665    SetBitmapLabel = SetBitmap
666    SetBitmapSelected = SetBitmap
667
668
669    def SetFocus(self):
670        """Set this control to have the focus"""
671        if self._state['cur'] != PLATE_PRESSED:
672            self._SetState(PLATE_HIGHLIGHT)
673        super(PlateButton, self).SetFocus()
674
675
676    def SetFont(self, font):
677        """Adjust size of control when font changes"""
678        super(PlateButton, self).SetFont(font)
679        self.InvalidateBestSize()
680
681
682    def SetLabel(self, label):
683        """Set the label of the button
684
685        :param string `label`: label string
686
687        """
688        super(PlateButton, self).SetLabel(label)
689        self.InvalidateBestSize()
690
691
692    def SetLabelColor(self, normal, hlight=wx.NullColour):
693        """Set the color of the label. The optimal label color is usually
694        automatically selected depending on the button color. In some
695        cases the colors that are chosen may not be optimal.
696
697        The normal state must be specified, if the other two params are left
698        Null they will be automatically guessed based on the normal color. To
699        prevent this automatic color choices from happening either specify
700        a color or None for the other params.
701
702        :param wx.Colour `normal`: Label color for normal state (:class:`wx.Colour`)
703        :keyword wx.Colour `hlight`: Color for when mouse is hovering over
704
705        """
706        assert isinstance(normal, wx.Colour), "Must supply a colour object"
707        self._color['default'] = False
708        self.SetForegroundColour(normal)
709
710        if hlight is not None:
711            if hlight.IsOk():
712                self._color['htxt'] = hlight
713            else:
714                self._color['htxt'] = BestLabelColour(normal)
715
716        if wx.Platform == '__WXMSW__':
717            self.Parent.RefreshRect(self.GetRect(), False)
718        else:
719            self.Refresh()
720
721
722    def SetMenu(self, menu):
723        """Set the menu that can be shown when clicking on the
724        drop arrow of the button.
725
726        :param wx.Menu `menu`: :class:`wx.Menu` to use as a PopupMenu
727
728        .. note::
729            Arrow is not drawn unless a menu is set
730
731        """
732        if self._menu is not None:
733            self.Unbind(wx.EVT_MENU_CLOSE)
734
735        self._menu = menu
736        self.Bind(wx.EVT_MENU_CLOSE, self.OnMenuClose)
737        self.InvalidateBestSize()
738
739
740    def SetPressColor(self, color):
741        """Set the color used for highlighting the pressed state
742
743        :param wx.Colour `color`: :class:`wx.Colour`
744
745        .. note::
746            also resets all text colours as necessary
747
748        """
749        self._color['default'] = False
750        if color.Alpha() == 255:
751            self._color['hlight'] = AdjustAlpha(color, 200)
752        else:
753            self._color['hlight'] = color
754        self._color['press'] = AdjustColour(color, -10, 160)
755        self._color['htxt'] = BestLabelColour(self._color['hlight'])
756        self.Refresh()
757
758
759    def SetWindowStyle(self, style):
760        """Sets the window style bytes, the updates take place
761        immediately no need to call refresh afterwards.
762
763        :param `style`: bitmask of PB_STYLE_* values
764
765        """
766        self._style = style
767        self.Refresh()
768
769
770    def SetWindowVariant(self, variant):
771        """Set the variant/font size of this control"""
772        super(PlateButton, self).SetWindowVariant(variant)
773        self.InvalidateBestSize()
774
775
776    def ShouldInheritColours(self):
777        """Overridden base class virtual. If the parent has non-default
778        colours then we want this control to inherit them.
779
780        """
781        return True
782
783
784    def ShowMenu(self):
785        """Show the dropdown menu if one is associated with this control"""
786        if self._menu is not None:
787            self.PopupMenu(self._menu)
788
789    #---- End Public Member Functions ----#
790