You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

163 lines
6.4 KiB

  1. import wx
  2. from gooey.gui.components.widgets.bases import BaseWidget
  3. from gooey.gui.lang.i18n import _
  4. from gooey.gui.util import wx_util
  5. from gooey.gui.components.widgets import CheckBox
  6. from gooey.util.functional import getin, findfirst, merge
  7. class RadioGroup(BaseWidget):
  8. def __init__(self, parent, widgetInfo, *args, **kwargs):
  9. super(RadioGroup, self).__init__(parent, *args, **kwargs)
  10. self._parent = parent
  11. self.info = widgetInfo
  12. self._id = widgetInfo['id']
  13. self._options = widgetInfo['options']
  14. self.widgetInfo = widgetInfo
  15. self.error = wx.StaticText(self, label='')
  16. self.radioButtons = self.createRadioButtons()
  17. self.selected = None
  18. self.widgets = self.createWidgets()
  19. self.arrange()
  20. for button in self.radioButtons:
  21. button.Bind(wx.EVT_LEFT_DOWN, self.handleButtonClick)
  22. initialSelection = getin(self.info, ['options', 'initial_selection'], None)
  23. if initialSelection is not None:
  24. self.selected = self.radioButtons[initialSelection]
  25. self.selected.SetValue(True)
  26. self.handleImplicitCheck()
  27. self.applyStyleRules()
  28. def getValue(self):
  29. for button, widget in zip(self.radioButtons, self.widgets):
  30. if button.GetValue(): # is Checked
  31. return merge(widget.getValue(), {'id': self._id})
  32. else:
  33. # just return the first widget's value even though it's
  34. # not active so that the expected interface is satisfied
  35. return self.widgets[0].getValue()
  36. def setErrorString(self, message):
  37. for button, widget in zip(self.radioButtons, self.widgets):
  38. if button.GetValue(): # is Checked
  39. widget.setErrorString(message)
  40. self.Layout()
  41. def showErrorString(self, b):
  42. for button, widget in zip(self.radioButtons, self.widgets):
  43. if button.GetValue(): # is Checked
  44. widget.showErrorString(b)
  45. def arrange(self, *args, **kwargs):
  46. title = getin(self.widgetInfo, ['options', 'title'], _('choose_one'))
  47. if getin(self.widgetInfo, ['options', 'show_border'], False):
  48. boxDetails = wx.StaticBox(self, -1, title)
  49. boxSizer = wx.StaticBoxSizer(boxDetails, wx.VERTICAL)
  50. else:
  51. title = wx_util.h1(self, title)
  52. title.SetForegroundColour(self._options['label_color'])
  53. boxSizer = wx.BoxSizer(wx.VERTICAL)
  54. boxSizer.AddSpacer(10)
  55. boxSizer.Add(title, 0)
  56. for btn, widget in zip(self.radioButtons, self.widgets):
  57. sizer = wx.BoxSizer(wx.HORIZONTAL)
  58. sizer.Add(btn,0, wx.RIGHT, 4)
  59. sizer.Add(widget, 1, wx.EXPAND)
  60. boxSizer.Add(sizer, 0, wx.ALL | wx.EXPAND, 5)
  61. self.SetSizer(boxSizer)
  62. def handleButtonClick(self, event):
  63. currentSelection = self.selected
  64. nextSelection = event.EventObject
  65. if not self.isSameRadioButton(currentSelection, nextSelection):
  66. self.selected = nextSelection
  67. self.selected.SetValue(True)
  68. else:
  69. # user clicked on an already enabled radio button.
  70. # if it is not in the required section, allow it to be deselected
  71. if not self.widgetInfo['required']:
  72. self.selected.SetValue(False)
  73. self.selected = None
  74. self.applyStyleRules()
  75. self.handleImplicitCheck()
  76. def isSameRadioButton(self, radioButton1, radioButton2):
  77. return (getattr(radioButton1, 'Id', 'r1-not-found') ==
  78. getattr(radioButton2, 'Id', 'r2-not-found'))
  79. def applyStyleRules(self):
  80. """
  81. Conditionally disabled/enables form fields based on the current
  82. section in the radio group
  83. """
  84. # for reasons I have been completely unable to figure out
  85. # or understand, IFF you've interacted with one of the radio Buttons's
  86. # child components, then the act of disabling that component will
  87. # reset the state of the radioButtons thus causing it to forget
  88. # what should be selected. So, that is why we're collected the initial
  89. # state of all the buttons and resetting each button's state as we go.
  90. # it's wonky as hell
  91. states = [x.GetValue() for x in self.radioButtons]
  92. for button, selected, widget in zip(self.radioButtons, states, self.widgets):
  93. if isinstance(widget, CheckBox):
  94. widget.hideInput()
  95. if not selected: # not checked
  96. widget.Disable()
  97. else:
  98. widget.Enable()
  99. button.SetValue(selected)
  100. def handleImplicitCheck(self):
  101. """
  102. Checkboxes are hidden when inside of a RadioGroup as a selection of
  103. the Radio button is an implicit selection of the Checkbox. As such, we have
  104. to manually "check" any checkbox as needed.
  105. """
  106. for button, widget in zip(self.radioButtons, self.widgets):
  107. if isinstance(widget, CheckBox):
  108. if button.GetValue(): # checked
  109. widget.setValue(True)
  110. else:
  111. widget.setValue(False)
  112. def createRadioButtons(self):
  113. # button groups in wx are statefully determined via a style flag
  114. # on the first button (what???). All button instances are part of the
  115. # same group until a new button is created with the style flag RG_GROUP
  116. # https://wxpython.org/Phoenix/docs/html/wx.RadioButton.html
  117. # (What???)
  118. firstButton = wx.RadioButton(self, style=wx.RB_GROUP)
  119. firstButton.SetValue(False)
  120. buttons = [firstButton]
  121. for _ in getin(self.widgetInfo, ['data','widgets'], [])[1:]:
  122. buttons.append(wx.RadioButton(self))
  123. return buttons
  124. def createWidgets(self):
  125. """
  126. Instantiate the Gooey Widgets that are used within the RadioGroup
  127. """
  128. from gooey.gui.components import widgets
  129. widgets = [getattr(widgets, item['type'])(self, item)
  130. for item in getin(self.widgetInfo, ['data', 'widgets'], [])]
  131. # widgets should be disabled unless
  132. # explicitly selected
  133. for widget in widgets:
  134. widget.Disable()
  135. return widgets