File: checkbox.py

package info (click to toggle)
wxpython4.0 4.2.4%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 218,584 kB
  • sloc: cpp: 962,669; python: 231,226; ansic: 170,755; makefile: 51,757; sh: 9,342; perl: 1,564; javascript: 584; php: 326; xml: 200
file content (837 lines) | stat: -rw-r--r-- 30,285 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
#----------------------------------------------------------------------
# Name:        wx.lib.checkbox
# Purpose:     Various kinds of generic checkbox stuff, (not native controls
#              but self-drawn.)
#
# Author:      wxPython Team and wxPyWiki Contributors
#
# Created:     22-June-2020
# Copyright:   (c) 2020 by Total Control Software
# Licence:     wxWindows license
# Tags:        phoenix-port, py3-port, documented
#----------------------------------------------------------------------


"""
This module implements various forms of generic checkboxes, meaning that
they are not built on native controls but are self-drawn.


Description
===========

This module implements various forms of generic checkboxes, meaning that
they are not built on native controls but are self-drawn.
They should act like normal checkboxes but you are able to better control how they look, etc...


Usage
=====

Sample usage::

    app = wx.App(redirect=False)
    class MyFrame(wx.Frame, DefineNativeCheckBoxBitmapsMixin):
        def __init__(self, parent, id=wx.ID_ANY, title=wx.EmptyString,
                     pos=wx.DefaultPosition, size=wx.DefaultSize,
                     style=wx.DEFAULT_FRAME_STYLE, name='frame'):
            wx.Frame.__init__(self, parent, id, title, pos, size, style, name)
            ## self.DefineNativeCheckBoxBitmaps()
            ## self.checkbox_bitmaps = self.GetNativeCheckBoxBitmaps()
            cb1 = GenCheckBox(self, label="PurePython Checkbox1", pos=(10, 10))
            cb2 = GenCheckBox(self, label="PurePython Checkbox2", pos=(10, 50))
            cb1.Bind(wx.EVT_CHECKBOX, self.OnCheckBox)
            cb2.Bind(wx.EVT_CHECKBOX, self.OnCheckBox)
            cb2.SetForegroundColour(wx.GREEN)
            cb2.SetBackgroundColour(wx.BLACK)
            sizer = wx.BoxSizer()
            sizer.Add(cb1, 0, wx.ALL, 5)
            sizer.Add(cb2, 0, wx.ALL, 5)
            self.SetSizer(sizer)

        def OnCheckBox(self, event):
            evtObj = event.GetEventObject()
            print(evtObj.GetLabel(), evtObj.IsChecked())

    frame = MyFrame(None, wx.ID_ANY, "Test Pure-Py Checkbox")
    frame.Show()
    app.MainLoop()

"""

# Imports.---------------------------------------------------------------------

# -wxPython Imports.
import wx


class GenCheckBox(wx.Control):
    """
    A generic class that replicates some of the functionalities of :class:`wx.Checkbox`,
    while being completely owner-drawn with a nice check bitmaps.
    """

    def __init__(self, parent, id=wx.ID_ANY, label="", pos=wx.DefaultPosition,
                 size=wx.DefaultSize, style=wx.NO_BORDER, validator=wx.DefaultValidator,
                 name="GenCheckBox"):
        """
        Default class constructor.

        :param `parent`: Pointer to a parent window. Must not be ``None``.
        :type `parent`: `wx.Window`
        :param `id`: Window identifier. ``wx.ID_ANY`` indicates a default value.
        :type `id`: int
        :param `label`: Text to be displayed next to the checkbox.
        :type `label`: str
        :param `pos`: Window position. The value ``wx.DefaultPosition`` indicates
         a default position, chosen by either the windowing system or wxWidgets, depending on platform.
        :type `pos`: `wx.Point`
        :param `size`: Window size. The value ``wx.DefaultSize`` indicates a default size,
         chosen by either the windowing system or wxWidgets, depending on platform.
        :type `size`: `wx.Size`
        :param `style`: Window style. Not used in this widget, GenCheckBox has only 2 state.
        :type `style`: long
        :param `validator`: Window validator.
        :type `validator`: `wx.Validator`
        :param `name`: Window name.
        :type `name`: str
        """
        wx.Control.__init__(self, parent, id, pos, size, style, validator, name)

        self.SYS_DEFAULT_GUI_FONT = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)

        # Initialize our cool bitmaps.
        self.InitializeBitmaps()

        # Initialize the focus pen colour/dashes, for faster drawing later.
        self.InitializeColours()

        # By default, we start unchecked.
        self._checked = False

        # Set the spacing between the check bitmap and the label to 3 by default.
        # This can be changed using SetSpacing later.
        self._spacing = 3
        self._hasFocus = False

        # Ok, set the wx.PyControl label, its initial size (formerly known an
        # SetBestFittingSize), and inherit the attributes from the standard
        # wx.CheckBox .
        self.SetLabel(label)
        self.SetInitialSize(size)
        self.InheritAttributes()

        # Bind the events related to our control: first of all, we use a
        # combination of wx.BufferedPaintDC and an empty handler for
        # wx.EVT_ERASE_BACKGROUND (see later) to reduce flicker.
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        # Since the paint event draws the whole widget, we will use
        # SetBackgroundStyle(wx.BG_STYLE_PAINT) and then
        # implementing an erase-background handler is not necessary.
        self.SetBackgroundStyle(wx.BG_STYLE_PAINT)
        # Add a size handler to refresh so the paint won't smear when resizing.
        self.Bind(wx.EVT_SIZE, self.OnSize)

        # Then we want to monitor user clicks, so that we can switch our
        # state between checked and unchecked.
        self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseClick)
        if wx.Platform == '__WXMSW__':
            # MSW Sometimes does strange things...
            self.Bind(wx.EVT_LEFT_DCLICK,  self.OnMouseClick)

        # We want also to react to keyboard keys, namely the space bar that can
        # toggle our checked state. Whether key-up or key-down is used is based
        # on platform.
        if 'wxMSW' in wx.PlatformInfo:
            self.Bind(wx.EVT_KEY_UP, self.OnKeyEvent)
        else:
            self.Bind(wx.EVT_KEY_DOWN, self.OnKeyEvent)

        # Then, we react to focus event, because we want to draw a small
        # dotted rectangle around the text if we have focus.
        # This might be improved!!!
        self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
        self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)


    def InitializeBitmaps(self):
        """ Initializes the check bitmaps. """

        # We keep 4 bitmaps for GenCheckBox, depending on the
        # checking state (Checked/UnChecked) and the control
        # state (Enabled/Disabled).

        self._bitmaps = {
            "CheckedEnable": _GetCheckedBitmap(self),
            "UnCheckedEnable": _GetNotCheckedBitmap(self),
            "CheckedDisable": _GetCheckedImage(self).ConvertToDisabled().ConvertToBitmap(),
            "UnCheckedDisable": _GetNotCheckedImage(self).ConvertToDisabled().ConvertToBitmap()}

    def InitializeColours(self):
        """ Initializes the focus indicator pen. """

        textClr = self.GetForegroundColour()
        self._focusIndPen = wx.Pen(textClr, 1, wx.USER_DASH)
        self._focusIndPen.SetDashes([1, 1])
        self._focusIndPen.SetCap(wx.CAP_BUTT)
        self.SYS_COLOUR_GRAYTEXT = wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)

    def GetBitmap(self):
        """
        Returns the appropriated bitmap depending on the checking state
        (Checked/UnChecked) and the control state (Enabled/Disabled).
        """

        if self.IsEnabled():
            # So we are Enabled.
            if self.IsChecked():
                # We are Checked.
                return self._bitmaps["CheckedEnable"]
            else:
                # We are UnChecked.
                return self._bitmaps["UnCheckedEnable"]
        else:
            # Poor GenCheckBox, Disabled and ignored!
            if self.IsChecked():
                return self._bitmaps["CheckedDisable"]
            else:
                return self._bitmaps["UnCheckedDisable"]

    def SetLabel(self, label):
        """
        Sets the :class:`GenCheckBox` text label and updates the control's
        size to exactly fit the label plus the bitmap.

        :param `label`: Text to be displayed next to the checkbox.
        :type `label`: str
        """

        wx.Control.SetLabel(self, label)

        # The text label has changed, so we must recalculate our best size
        # and refresh ourselves.
        self.InvalidateBestSize()
        self.Refresh()

    def SetFont(self, font):
        """
        Sets the :class:`GenCheckBox` text font and updates the control's
        size to exactly fit the label plus the bitmap.

        :param `font`: Font to be used to render the checkboxs label.
        :type `font`: `wx.Font`
        """

        wx.Control.SetFont(self, font)

        # The font for text label has changed, so we must recalculate our best
        # size and refresh ourselves.
        self.InvalidateBestSize()
        self.Refresh()

    def DoGetBestSize(self):
        """
        Overridden base class virtual.  Determines the best size of the control
        based on the label size, the bitmap size and the current font.
        """

        # Retrieve our properties: the text label, the font and the check
        # bitmap.
        label = self.GetLabel()
        font = self.GetFont()
        bitmap = self.GetBitmap()

        if not font:
            # No font defined? So use the default GUI font provided by the system.
            font = self.SYS_DEFAULT_GUI_FONT

        # Set up a wx.ClientDC. When you don't have a dc available (almost
        # always you don't have it if you are not inside a wx.EVT_PAINT event),
        # use a wx.ClientDC (or a wx.MemoryDC) to measure text extents.
        dc = wx.ClientDC(self)
        dc.SetFont(font)

        # Measure our label.
        textWidth, textHeight = dc.GetTextExtent(label)

        # Retrieve the check bitmap dimensions.
        bitmapWidth, bitmapHeight = bitmap.GetWidth(), bitmap.GetHeight()

        # Get the spacing between the check bitmap and the text.
        spacing = self.GetSpacing()

        # Ok, we're almost done: the total width of the control is simply
        # the sum of the bitmap width, the spacing and the text width,
        # while the height is the maximum value between the text width and
        # the bitmap width.
        totalWidth = bitmapWidth + spacing + textWidth
        totalHeight = max(textHeight, bitmapHeight)

        best = wx.Size(totalWidth, totalHeight)

        # Cache the best size so it doesn't need to be calculated again,
        # at least until some properties of the window change.
        self.CacheBestSize(best)

        return best

    def AcceptsFocusFromKeyboard(self):
        """ Overridden base class virtual. """

        # We can accept focus from keyboard, obviously.
        return True

    def AcceptsFocus(self):
        """ Overridden base class virtual. """

        # If it seems that wx.CheckBox does not accept focus with mouse, It does.
        # You just can't see the focus rectangle until there's
        # another keypress or navigation event (at least on some platforms.)
        return True  # This will draw focus rectangle always on mouse click.

    def HasFocus(self):
        """ Returns whether or not we have the focus. """

        # We just returns the _hasFocus property that has been set in the
        # wx.EVT_SET_FOCUS and wx.EVT_KILL_FOCUS event handlers.
        return self._hasFocus

    def SetForegroundColour(self, colour):
        """
        Overridden base class virtual.

        :param `colour`: Set the foreground colour of the checkboxs label.
        :type `colour`: `wx.Colour`
        """

        wx.Control.SetForegroundColour(self, colour)

        # We have to re-initialize the focus indicator per colour as it should
        # always be the same as the foreground colour.
        self.InitializeColours()
        self.Refresh()

    def SetBackgroundColour(self, colour):
        """
        Overridden base class virtual.

        :param `colour`: Set the background colour of the checkbox.
        :type `colour`: `wx.Colour`
        """

        wx.Control.SetBackgroundColour(self, colour)

        # We have to refresh ourselves.
        self.Refresh()

    def Enable(self, enable=True):
        """
        Enables/Disables :class:`GenCheckBox`.

        :param `enable`: Set the enabled state of the checkbox.
        :type `enable`: bool
        """

        wx.Control.Enable(self, enable)

        # We have to refresh ourselves, as our state changed.
        self.Refresh()

    def GetDefaultAttributes(self):
        """
        Overridden base class virtual.  By default we should use
        the same font/colour attributes as the native wx.CheckBox.
        """

        return wx.CheckBox.GetClassDefaultAttributes()

    def ShouldInheritColours(self):
        """
        Overridden base class virtual.  If the parent has non-default
        colours then we want this control to inherit them.
        """

        return True

    def SetSpacing(self, spacing):
        """
        Sets a new spacing between the check bitmap and the text.

        :param `spacing`: Set the amount of space between the checkboxs bitmap and text.
        :type `spacing`: int
        """

        self._spacing = spacing

        # The spacing between the check bitmap and the text has changed,
        # so we must recalculate our best size and refresh ourselves.
        self.InvalidateBestSize()
        self.Refresh()

    def GetSpacing(self):
        """ Returns the spacing between the check bitmap and the text. """

        return self._spacing

    def GetValue(self):
        """
        Returns the state of :class:`GenCheckBox`, True if checked, False
        otherwise.
        """

        return self._checked

    def IsChecked(self):
        """
        This is just a maybe more readable synonym for GetValue: just as the
        latter, it returns True if the :class:`GenCheckBox` is checked and False
        otherwise.
        """

        return self._checked

    def SetValue(self, state):
        """
        Sets the :class:`GenCheckBox` to the given state. This does not cause a
        ``wx.wxEVT_COMMAND_CHECKBOX_CLICKED`` event to get emitted.

        :param `state`: Set the value of the checkbox. True or False.
        :type `state`: bool
        """

        self._checked = state

        # Refresh ourselves: the bitmap has changed.
        self.Refresh()

    def OnKeyEvent(self, event):
        """
        Handles the ``wx.EVT_KEY_UP`` or ``wx.EVT_KEY_UP`` event (depending on
        platform) for :class:`GenCheckBox`.

        :param `event`: A `wx.KeyEvent` to be processed.
        :type `event`: `wx.KeyEvent`
        """

        if event.GetKeyCode() == wx.WXK_SPACE:
            # The spacebar has been pressed: toggle our state.
            self.SendCheckBoxEvent()
        else:
            event.Skip()

    def OnSetFocus(self, event):
        """
        Handles the ``wx.EVT_SET_FOCUS`` event for :class:`GenCheckBox`.

        :param `event`: A `wx.FocusEvent` to be processed.
        :type `event`: `wx.FocusEvent`
        """

        self._hasFocus = True

        # We got focus, and we want a dotted rectangle to be painted
        # around the checkbox label, so we refresh ourselves.
        self.Refresh()

    def OnKillFocus(self, event):
        """
        Handles the ``wx.EVT_KILL_FOCUS`` event for :class:`GenCheckBox`.

        :param `event`: A `wx.FocusEvent` to be processed.
        :type `event`: `wx.FocusEvent`
        """

        self._hasFocus = False

        # We lost focus, and we want a dotted rectangle to be cleared
        # around the checkbox label, so we refresh ourselves.
        self.Refresh()

    def OnSize(self, event):
        """
        Handles the ``wx.EVT_SIZE`` event for :class:`GenCheckBox`.

        :param `event`: A `wx.SizeEvent` to be processed.
        :type `event`: `wx.SizeEvent`
        """
        self.Refresh()

    def OnPaint(self, event):
        """
        Handles the ``wx.EVT_PAINT`` event for :class:`GenCheckBox`.

        :param `event`: A `wx.PaintEvent` to be processed.
        :type `event`: `wx.PaintEvent`
        """

        # If you want to reduce flicker, a good starting point is to
        # use wx.BufferedPaintDC .
        # wx.AutoBufferedPaintDC would be marginally better.
        dc = wx.AutoBufferedPaintDC(self)

        # Is is advisable that you don't overcrowd the OnPaint event
        # (or any other event) with a lot of code, so let's do the
        # actual drawing in the Draw() method, passing the newly
        # initialized wx.AutoBufferedPaintDC .
        self.Draw(dc)

    def Draw(self, dc):
        """
        Actually performs the drawing operations, for the bitmap and
        for the text, positioning them centered vertically.

        :param `dc`: device context to use.
        :type `dc`: `wx.DC`
        """

        # Get the actual client size of ourselves.
        width, height = self.GetClientSize()

        if not width or not height:
            # Nothing to do, we still don't have dimensions!
            return

        # Initialize the wx.BufferedPaintDC, assigning a background
        # colour and a foreground colour (to draw the text).
        backColour = self.GetBackgroundColour()
        backBrush = wx.Brush(backColour, wx.SOLID)
        dc.SetBackground(backBrush)
        dc.Clear()

        if self.IsEnabled():
            dc.SetTextForeground(self.GetForegroundColour())
        else:
            dc.SetTextForeground(self.SYS_COLOUR_GRAYTEXT)

        dc.SetFont(self.GetFont())

        # Get the text label for the checkbox, the associated check bitmap
        # and the spacing between the check bitmap and the text.
        label = self.GetLabel()
        bitmap = self.GetBitmap()
        spacing = self.GetSpacing()

        # Measure the text extent and get the check bitmap dimensions.
        textWidth, textHeight = dc.GetTextExtent(label)
        bitmapWidth, bitmapHeight = bitmap.GetWidth(), bitmap.GetHeight()

        # Position the bitmap centered vertically.
        bitmapXpos = 0
        bitmapYpos = (height - bitmapHeight) // 2

        # Position the text centered vertically.
        textXpos = bitmapWidth + spacing
        textYpos = (height - textHeight) // 2

        # Draw the bitmap on the DC.
        try:
            dc.DrawBitmap(bitmap, bitmapXpos, bitmapYpos, True)
        except Exception as exc:  # bitmap might be image and need converted. Ex: if disabled.
            dc.DrawBitmap(bitmap.ConvertToBitmap(), bitmapXpos, bitmapYpos, True)

        # Draw the text
        dc.DrawText(label, textXpos, textYpos)

        # Let's see if we have keyboard focus and, if this is the case,
        # we draw a dotted rectangle around the text (Windows behavior,
        # I don't know on other platforms...).
        if self.HasFocus():
            # Yes, we are focused! So, now, use a transparent brush with
            # a dotted black pen to draw a rectangle around the text.
            dc.SetBrush(wx.TRANSPARENT_BRUSH)
            dc.SetPen(self._focusIndPen)
            dc.DrawRectangle(textXpos, textYpos, textWidth, textHeight)

    def OnMouseClick(self, event):
        """
        Handles the ``wx.EVT_LEFT_DOWN`` event for :class:`GenCheckBox`.

        :param `event`: A `wx.MouseEvent` to be processed.
        :type `event`: `wx.MouseEvent`
        """

        if not self.IsEnabled():
            # Nothing to do, we are disabled.
            return

        self.SendCheckBoxEvent()
        event.Skip()

    def SendCheckBoxEvent(self):
        """ Actually sends the wx.wxEVT_COMMAND_CHECKBOX_CLICKED event. """

        # This part of the code may be reduced to a 3-liner code
        # but it is kept for better understanding the event handling.
        # If you can, however, avoid code duplication; in this case,
        # I could have done:
        #
        # self._checked = not self.IsChecked()
        # checkEvent = wx.CommandEvent(wx.wxEVT_COMMAND_CHECKBOX_CLICKED,
        #                              self.GetId())
        # checkEvent.SetInt(int(self._checked))
        if self.IsChecked():

            # We were checked, so we should become unchecked.
            self._checked = False

            # Fire a wx.CommandEvent: this generates a
            # wx.wxEVT_COMMAND_CHECKBOX_CLICKED event that can be caught by the
            # developer by doing something like:
            # MyCheckBox.Bind(wx.EVT_CHECKBOX, self.OnCheckBox)
            checkEvent = wx.CommandEvent(wx.wxEVT_COMMAND_CHECKBOX_CLICKED,
                                         self.GetId())

            # Set the integer event value to 0 (we are switching to unchecked state).
            checkEvent.SetInt(0)

        else:

            # We were unchecked, so we should become checked.
            self._checked = True

            checkEvent = wx.CommandEvent(wx.wxEVT_COMMAND_CHECKBOX_CLICKED,
                                         self.GetId())

            # Set the integer event value to 1 (we are switching to checked state).
            checkEvent.SetInt(1)

        # Set the originating object for the event (ourselves).
        checkEvent.SetEventObject(self)

        # Watch for a possible listener of this event that will catch it and
        # eventually process it.
        self.GetEventHandler().ProcessEvent(checkEvent)

        # Refresh ourselves: the bitmap has changed.
        self.Refresh()


# -----------------------------------------------------------------------------


class DefineNativeCheckBoxBitmapsMixin():
    """
    Inherit this mixin in your :class:`wx.Window` based subclass to easily
    define the native CheckBox Bitmaps as attributes which can then be used
    to customize a widgets appearance/functionality with.

    Sample example usage::

        class MyCheckListBoxSTC(wx.stc.StyledTextCtrl, DefineNativeCheckBoxBitmapsMixin):
            '''Customized StyledTextCtrl Setup like a CheckListBox.'''
            def __init__(self, parent, id=wx.ID_ANY,
                         pos=wx.DefaultPosition, size=wx.DefaultSize,
                         style=0, name='styledtextctrl'):
                wx.stc.StyledTextCtrl.__init__(self, parent, id, pos, size, style, name)

                # Define the checkbox bitmaps as attributes.
                self.DefineNativeCheckBoxBitmaps()
                # After the bitmaps have become attributes you can easily snag
                # them all later on from inside a method with this inherited method.
                ## self.checkbox_bitmaps = self.GetNativeCheckBoxBitmaps()

                # Setup a margin to hold bookmarks.
                self.SetMarginType(1, wx.stc.STC_MARGIN_SYMBOL)
                self.SetMarginSensitive(1, True)
                self.SetMarginWidth(1, 16)
                # Define the bookmark images.
                self.MarkerDefineBitmap(0, self.native_checkbox_unchecked_bmp)
                self.MarkerDefineBitmap(1, self.native_checkbox_checked_bmp)

                # ... do something with the bitmaps when you click the margin event.

    """
    def DefineNativeCheckBoxBitmaps(self):
        """
        Define native checkbox bitmaps as attributes. Returns True if all bitmaps was defined Ok.

        bitmaps defined::

            self.native_checkbox_unchecked_bmp
            self.native_checkbox_unchecked_disabled_bmp
            self.native_checkbox_checked_bmp
            self.native_checkbox_checked_disabled_bmp
            self.native_checkbox_3state_bmp
            self.native_checkbox_3state_disabled_bmp
            self.native_checkbox_current_bmp
            self.native_checkbox_pressed_bmp

        :rtype: bool
        """
        render = wx.RendererNative.Get()
        cbX, cbY = render.GetCheckBoxSize(self)
        bmp = wx.Bitmap(cbX, cbY)
        dc = wx.MemoryDC(bmp)
        DrawCheckBox = render.DrawCheckBox
        DrawCheckBox(self, dc, (0, 0, cbX, cbY), wx.CONTROL_ISDEFAULT)
        self.native_checkbox_unchecked_bmp = dc.GetAsBitmap((0, 0, cbX, cbY))
        DrawCheckBox(self, dc, (0, 0, cbX, cbY), wx.CONTROL_ISDEFAULT | wx.CONTROL_DISABLED)
        self.native_checkbox_unchecked_disabled_bmp = dc.GetAsBitmap((0, 0, cbX, cbY))
        DrawCheckBox(self, dc, (0, 0, cbX, cbY), wx.CONTROL_CHECKED)
        self.native_checkbox_checked_bmp = dc.GetAsBitmap((0, 0, cbX, cbY))
        DrawCheckBox(self, dc, (0, 0, cbX, cbY), wx.CONTROL_CHECKED | wx.CONTROL_DISABLED)
        self.native_checkbox_checked_disabled_bmp = dc.GetAsBitmap((0, 0, cbX, cbY))
        DrawCheckBox(self, dc, (0, 0, cbX, cbY), wx.CONTROL_CHECKABLE)
        self.native_checkbox_3state_bmp = dc.GetAsBitmap((0, 0, cbX, cbY))
        DrawCheckBox(self, dc, (0, 0, cbX, cbY), wx.CONTROL_CHECKABLE | wx.CONTROL_DISABLED)
        self.native_checkbox_3state_disabled_bmp = dc.GetAsBitmap((0, 0, cbX, cbY))
        DrawCheckBox(self, dc, (0, 0, cbX, cbY), wx.CONTROL_CURRENT)
        self.native_checkbox_current_bmp = dc.GetAsBitmap((0, 0, cbX, cbY))
        DrawCheckBox(self, dc, (0, 0, cbX, cbY), wx.CONTROL_PRESSED)
        self.native_checkbox_pressed_bmp = dc.GetAsBitmap((0, 0, cbX, cbY))
        if (self.native_checkbox_unchecked_bmp.IsOk() and
            self.native_checkbox_unchecked_disabled_bmp.IsOk() and
            self.native_checkbox_checked_bmp.IsOk() and
            self.native_checkbox_checked_disabled_bmp.IsOk() and
            self.native_checkbox_3state_bmp.IsOk() and
            self.native_checkbox_3state_disabled_bmp.IsOk() and
            self.native_checkbox_current_bmp.IsOk() and
            self.native_checkbox_pressed_bmp.IsOk()
            ):
            return True
        return False

    def GetNativeCheckBoxBitmaps(self):
        """
        Get a tuple of the defined checkbox bitmaps.

        :rtype: tuple
        """
        return (self.native_checkbox_unchecked_bmp,
                self.native_checkbox_unchecked_disabled_bmp,
                self.native_checkbox_checked_bmp,
                self.native_checkbox_checked_disabled_bmp,
                self.native_checkbox_3state_bmp,
                self.native_checkbox_3state_disabled_bmp,
                self.native_checkbox_current_bmp,
                self.native_checkbox_pressed_bmp,
                )


# -----------------------------------------------------------------------------


def _GetCheckedBitmap(self):
    """
    Get a native checkbox(Checked) bitmap.

    :rtype: `wx.Bitmap`
    """
    render = wx.RendererNative.Get()
    cbX, cbY = render.GetCheckBoxSize(self)
    bmp = wx.Bitmap(cbX, cbY)
    dc = wx.MemoryDC(bmp)
    render.DrawCheckBox(self, dc, (0, 0, cbX, cbY), wx.CONTROL_CHECKED)
    native_checkbox_checked_bmp = dc.GetAsBitmap((0, 0, cbX, cbY))
    return native_checkbox_checked_bmp

def _GetCheckedImage(self):
    """
    Get a native checkbox(Checked) image.

    :rtype: `wx.Image`
    """
    return _GetCheckedBitmap(self).ConvertToImage()

def _GetNotCheckedBitmap(self):
    """
    Get a native checkbox(Unchecked) bitmap.

    :rtype: `wx.Bitmap`
    """
    render = wx.RendererNative.Get()
    cbX, cbY = render.GetCheckBoxSize(self)
    bmp = wx.Bitmap(cbX, cbY)
    dc = wx.MemoryDC(bmp)
    render.DrawCheckBox(self, dc, (0, 0, cbX, cbY), wx.CONTROL_ISDEFAULT)
    native_checkbox_unchecked_bmp = dc.GetAsBitmap((0, 0, cbX, cbY))
    return native_checkbox_unchecked_bmp

def _GetNotCheckedImage(self):
    """
    Get a native checkbox(Unchecked) image.

    :rtype: `wx.Image`
    """
    return _GetNotCheckedBitmap(self).ConvertToImage()


# -----------------------------------------------------------------------------


def _GrayOut(anImage):
    """
    Convert the given image (in place) to a grayed-out version,
    appropriate for a 'disabled' appearance.

    :param `anImage`: A `wx.Image` to gray out.
    :type `anImage`: `wx.Image`
    :rtype: `wx.Bitmap`
    """

    factor = 0.7  # 0 < f < 1.  Higher Is Grayer

    if anImage.HasMask():
        maskColor = (anImage.GetMaskRed(), anImage.GetMaskGreen(), anImage.GetMaskBlue())
    else:
        maskColor = None

    data = map(ord, list(anImage.GetData()))

    for i in range(0, len(data), 3):
        pixel = (data[i], data[i + 1], data[i + 2])
        pixel = _MakeGray(pixel, factor, maskColor)

        for x in range(3):
            data[i + x] = pixel[x]

    anImage.SetData(''.join(map(chr, data)))

    return anImage.ConvertToBitmap()


def _MakeGray(rgbTuple, factor, maskColor):
    """
    Make a pixel grayed-out. If the pixel matches the maskcolor, it won't be
    changed.

    :type `rgbTuple`: red, green, blue 3-tuple
    :type `factor`: float
    :type `maskColor`: red, green, blue 3-tuple
    """
    r, g, b = rgbTuple
    if (r, g, b) != maskColor:
        return map(lambda x: int((230 - x) * factor) + x, (r, g, b))
    else:
        return (r, g, b)


if __name__ == '__main__':
    # Small sample program to test.
    app = wx.App(redirect=False)
    class MyFrame(wx.Frame, DefineNativeCheckBoxBitmapsMixin):
        def __init__(self, parent, id=wx.ID_ANY, title=wx.EmptyString,
                     pos=wx.DefaultPosition, size=wx.DefaultSize,
                     style=wx.DEFAULT_FRAME_STYLE, name='frame'):
            wx.Frame.__init__(self, parent, id, title, pos, size, style, name)
            ## self.DefineNativeCheckBoxBitmaps()
            ## self.checkbox_bitmaps = self.GetNativeCheckBoxBitmaps()
            cb1 = GenCheckBox(self, label="PurePython Checkbox1", pos=(10, 10))
            cb2 = GenCheckBox(self, label="PurePython Checkbox2", pos=(10, 50))
            cb1.Bind(wx.EVT_CHECKBOX, self.OnCheckBox)
            cb2.Bind(wx.EVT_CHECKBOX, self.OnCheckBox)
            cb2.SetForegroundColour(wx.GREEN)
            cb2.SetBackgroundColour(wx.BLACK)
            sizer = wx.BoxSizer()
            sizer.Add(cb1, 0, wx.ALL, 5)
            sizer.Add(cb2, 0, wx.ALL, 5)
            self.SetSizer(sizer)

        def OnCheckBox(self, event):
            evtObj = event.GetEventObject()
            print(evtObj.GetLabel(), evtObj.IsChecked())

    frame = MyFrame(None, wx.ID_ANY, "Test Pure-Py Checkbox")
    frame.Show()
    app.MainLoop()