mirror of https://github.com/chriskiehl/Gooey.git
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
163 lines
6.4 KiB
import wx
|
|
from gooey.gui.components.widgets.bases import BaseWidget
|
|
from gooey.gui.lang.i18n import _
|
|
from gooey.gui.util import wx_util
|
|
from gooey.gui.components.widgets import CheckBox
|
|
from gooey.util.functional import getin, findfirst, merge
|
|
|
|
|
|
class RadioGroup(BaseWidget):
|
|
|
|
def __init__(self, parent, widgetInfo, *args, **kwargs):
|
|
super(RadioGroup, self).__init__(parent, *args, **kwargs)
|
|
self._parent = parent
|
|
self.info = widgetInfo
|
|
self._id = widgetInfo['id']
|
|
self._options = widgetInfo['options']
|
|
self.widgetInfo = widgetInfo
|
|
self.error = wx.StaticText(self, label='')
|
|
self.radioButtons = self.createRadioButtons()
|
|
self.selected = None
|
|
self.widgets = self.createWidgets()
|
|
self.arrange()
|
|
|
|
|
|
for button in self.radioButtons:
|
|
button.Bind(wx.EVT_LEFT_DOWN, self.handleButtonClick)
|
|
|
|
initialSelection = getin(self.info, ['options', 'initial_selection'], None)
|
|
if initialSelection is not None:
|
|
self.selected = self.radioButtons[initialSelection]
|
|
self.selected.SetValue(True)
|
|
self.handleImplicitCheck()
|
|
|
|
self.applyStyleRules()
|
|
|
|
|
|
def getValue(self):
|
|
for button, widget in zip(self.radioButtons, self.widgets):
|
|
if button.GetValue(): # is Checked
|
|
return merge(widget.getValue(), {'id': self._id})
|
|
else:
|
|
# just return the first widget's value even though it's
|
|
# not active so that the expected interface is satisfied
|
|
return self.widgets[0].getValue()
|
|
|
|
def setErrorString(self, message):
|
|
for button, widget in zip(self.radioButtons, self.widgets):
|
|
if button.GetValue(): # is Checked
|
|
widget.setErrorString(message)
|
|
self.Layout()
|
|
|
|
def showErrorString(self, b):
|
|
for button, widget in zip(self.radioButtons, self.widgets):
|
|
if button.GetValue(): # is Checked
|
|
widget.showErrorString(b)
|
|
|
|
|
|
def arrange(self, *args, **kwargs):
|
|
title = getin(self.widgetInfo, ['options', 'title'], _('choose_one'))
|
|
if getin(self.widgetInfo, ['options', 'show_border'], False):
|
|
boxDetails = wx.StaticBox(self, -1, title)
|
|
boxSizer = wx.StaticBoxSizer(boxDetails, wx.VERTICAL)
|
|
else:
|
|
title = wx_util.h1(self, title)
|
|
title.SetForegroundColour(self._options['label_color'])
|
|
boxSizer = wx.BoxSizer(wx.VERTICAL)
|
|
boxSizer.AddSpacer(10)
|
|
boxSizer.Add(title, 0)
|
|
|
|
for btn, widget in zip(self.radioButtons, self.widgets):
|
|
sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
sizer.Add(btn,0, wx.RIGHT, 4)
|
|
sizer.Add(widget, 1, wx.EXPAND)
|
|
boxSizer.Add(sizer, 0, wx.ALL | wx.EXPAND, 5)
|
|
self.SetSizer(boxSizer)
|
|
|
|
|
|
def handleButtonClick(self, event):
|
|
currentSelection = self.selected
|
|
nextSelection = event.EventObject
|
|
|
|
if not self.isSameRadioButton(currentSelection, nextSelection):
|
|
self.selected = nextSelection
|
|
self.selected.SetValue(True)
|
|
else:
|
|
# user clicked on an already enabled radio button.
|
|
# if it is not in the required section, allow it to be deselected
|
|
if not self.widgetInfo['required']:
|
|
self.selected.SetValue(False)
|
|
self.selected = None
|
|
|
|
self.applyStyleRules()
|
|
self.handleImplicitCheck()
|
|
|
|
|
|
def isSameRadioButton(self, radioButton1, radioButton2):
|
|
return (getattr(radioButton1, 'Id', 'r1-not-found') ==
|
|
getattr(radioButton2, 'Id', 'r2-not-found'))
|
|
|
|
|
|
def applyStyleRules(self):
|
|
"""
|
|
Conditionally disabled/enables form fields based on the current
|
|
section in the radio group
|
|
"""
|
|
# for reasons I have been completely unable to figure out
|
|
# or understand, IFF you've interacted with one of the radio Buttons's
|
|
# child components, then the act of disabling that component will
|
|
# reset the state of the radioButtons thus causing it to forget
|
|
# what should be selected. So, that is why we're collected the initial
|
|
# state of all the buttons and resetting each button's state as we go.
|
|
# it's wonky as hell
|
|
states = [x.GetValue() for x in self.radioButtons]
|
|
for button, selected, widget in zip(self.radioButtons, states, self.widgets):
|
|
if isinstance(widget, CheckBox):
|
|
widget.hideInput()
|
|
if not selected: # not checked
|
|
widget.Disable()
|
|
else:
|
|
widget.Enable()
|
|
button.SetValue(selected)
|
|
|
|
|
|
def handleImplicitCheck(self):
|
|
"""
|
|
Checkboxes are hidden when inside of a RadioGroup as a selection of
|
|
the Radio button is an implicit selection of the Checkbox. As such, we have
|
|
to manually "check" any checkbox as needed.
|
|
"""
|
|
for button, widget in zip(self.radioButtons, self.widgets):
|
|
if isinstance(widget, CheckBox):
|
|
if button.GetValue(): # checked
|
|
widget.setValue(True)
|
|
else:
|
|
widget.setValue(False)
|
|
|
|
|
|
def createRadioButtons(self):
|
|
# button groups in wx are statefully determined via a style flag
|
|
# on the first button (what???). All button instances are part of the
|
|
# same group until a new button is created with the style flag RG_GROUP
|
|
# https://wxpython.org/Phoenix/docs/html/wx.RadioButton.html
|
|
# (What???)
|
|
firstButton = wx.RadioButton(self, style=wx.RB_GROUP)
|
|
firstButton.SetValue(False)
|
|
buttons = [firstButton]
|
|
|
|
for _ in getin(self.widgetInfo, ['data','widgets'], [])[1:]:
|
|
buttons.append(wx.RadioButton(self))
|
|
return buttons
|
|
|
|
def createWidgets(self):
|
|
"""
|
|
Instantiate the Gooey Widgets that are used within the RadioGroup
|
|
"""
|
|
from gooey.gui.components import widgets
|
|
widgets = [getattr(widgets, item['type'])(self, item)
|
|
for item in getin(self.widgetInfo, ['data', 'widgets'], [])]
|
|
# widgets should be disabled unless
|
|
# explicitly selected
|
|
for widget in widgets:
|
|
widget.Disable()
|
|
return widgets
|