def handle_option_merge(group_defaults, incoming_options, title): """ Merges a set of group defaults with incoming options. A bunch of ceremony here is to ensure backwards compatibility with the old num_required_cols and num_optional_cols decorator args. They are used as the seed values for the new group defaults which keeps the old behavior _mostly_ in tact. Known failure points: * Using custom groups / names. No 'positional arguments' group means no required_cols arg being honored * Non-positional args marked as required. It would take group shuffling along the lines of that required to make mutually exclusive groups show in the correct place. In short, not worth the complexity for a legacy feature that's been succeeded by a much more powerful alternative. """ if title == 'positional arguments': # the argparse default 'required' bucket req_cols = getin(group_defaults, ['legacy', 'required_cols'], 2) new_defaults = assoc(group_defaults, 'columns', req_cols) return merge(new_defaults, incoming_options) else: opt_cols = getin(group_defaults, ['legacy', 'optional_cols'], 2) new_defaults = assoc(group_defaults, 'columns', opt_cols) return merge(new_defaults, incoming_options)
def getValue(self): regexFunc = lambda x: bool(re.match(userValidator, x)) userValidator = getin(self._options, ['validator', 'test'], 'True') message = getin(self._options, ['validator', 'message'], '') testFunc = regexFunc \ if getin(self._options, ['validator', 'type'], None) == 'RegexValidator'\ else eval('lambda user_input: bool(%s)' % userValidator) satisfies = testFunc if self._meta['required'] else ifPresent(testFunc) value = self.getWidgetValue() return { 'id': self._id, 'cmd': self.formatOutput(self._meta, value), 'rawValue': value, 'test': runValidator(satisfies, value), 'error': None if runValidator(satisfies, value) else message, 'clitype': 'positional' if self._meta['required'] and not self._meta['commands'] else 'optional' }
def getValue(self): userValidatorCB = None validatesCB = True value = self.getWidgetValue() if 'callback' in self._options['validator']: userValidatorCB = self._options['validator']['callback'] validatesCB = userValidatorCB(value) userValidator = getin(self._options, ['validator', 'test'], 'True') message = getin(self._options, ['validator', 'message'], '') testFunc = eval('lambda user_input: bool(%s)' % userValidator) satisfies = testFunc if self._meta['required'] else ifPresent(testFunc) return { 'id': self._id, 'cmd': self.formatOutput(self._meta, value), 'rawValue': value, 'test': runValidator(satisfies, value), 'error': None if runValidator(satisfies, value) and validatesCB else message, 'clitype': 'positional' if self._meta['required'] and not self._meta['commands'] else 'optional' }
def makeGroup(self, parent, thissizer, group, *args): ''' Messily builds the (potentially) nested and grouped layout Note! Mutates `self.reifiedWidgets` in place with the widgets as they're instantiated! I cannot figure out how to split out the creation of the widgets from their styling without WxPython violently exploding TODO: sort out the WX quirks and clean this up. ''' # determine the type of border , if any, the main sizer will use if getin(group, ['options', 'show_border'], False): boxDetails = wx.StaticBox(parent, -1, group['name'] or '') boxSizer = wx.StaticBoxSizer(boxDetails, wx.VERTICAL) else: boxSizer = wx.BoxSizer(wx.VERTICAL) boxSizer.AddSpacer(10) if group['name']: boxSizer.Add(wx_util.h1(parent, group['name'] or ''), 0, wx.TOP | wx.BOTTOM | wx.LEFT, 8) group_description = getin(group, ['description']) if group_description: description = wx.StaticText(parent, label=group_description) boxSizer.Add(description, 0, wx.EXPAND | wx.LEFT, 10) # apply an underline when a grouping border is not specified if not getin(group, ['options', 'show_border'], False) and group['name']: boxSizer.Add(wx_util.horizontal_rule(parent), 0, wx.EXPAND | wx.LEFT, 10) ui_groups = self.chunkWidgets(group) for uigroup in ui_groups: sizer = wx.BoxSizer(wx.HORIZONTAL) for item in uigroup: widget = self.reifyWidget(parent, item) # !Mutate the reifiedWidgets instance variable in place self.reifiedWidgets.append(widget) sizer.Add(widget, 1, wx.ALL, 5) boxSizer.Add(sizer, 0, wx.ALL | wx.EXPAND, 5) # apply the same layout rules recursively for subgroups hs = wx.BoxSizer(wx.HORIZONTAL) for e, subgroup in enumerate(group['groups']): self.makeGroup(parent, hs, subgroup, 1, wx.ALL | wx.EXPAND, 5) if e % getin(group, ['options', 'columns'], 2) \ or e == len(group['groups']): boxSizer.Add(hs, *args) hs = wx.BoxSizer(wx.HORIZONTAL) thissizer.Add(boxSizer, *args)
def test_choice_string_cooersion(self): """ Issue 321 - must coerce choice types to string to support wx.ComboBox """ parser = ArgumentParser() parser.add_argument('--foo', default=1, choices=[1, 2, 3]) choice_action = parser._actions[-1] result = argparse_to_json.action_to_json(choice_action, 'Dropdown', {}) self.assertEqual(getin(result, ['data', 'choices']), ['1', '2', '3']) # default value is also converted to a string type self.assertEqual(getin(result, ['data', 'default']), '1')
def makeGroup(self, parent, thissizer, group, *args): ''' Messily builds the (potentially) nested and grouped layout Note! Mutates `self.reifiedWidgets` in place with the widgets as they're instantiated! I cannot figure out how to split out the creation of the widgets from their styling without WxPython violently exploding TODO: sort out the WX quirks and clean this up. ''' # determine the type of border , if any, the main sizer will use if getin(group, ['options', 'show_border'], False): boxDetails = wx.StaticBox(parent, -1, group['name'] or '') boxSizer = wx.StaticBoxSizer(boxDetails, wx.VERTICAL) else: boxSizer = wx.BoxSizer(wx.VERTICAL) boxSizer.AddSpacer(10) if group['name']: boxSizer.Add(wx_util.h1(parent, group['name'] or ''), 0, wx.TOP | wx.BOTTOM | wx.LEFT, 8) group_description = getin(group, ['description']) if group_description: description = wx.StaticText(parent, label=group_description) boxSizer.Add(description, 0, wx.EXPAND | wx.LEFT, 10) # apply an underline when a grouping border is not specified if not getin(group, ['options', 'show_border'], False) and group['name']: boxSizer.Add(wx_util.horizontal_rule(parent), 0, wx.EXPAND | wx.LEFT, 10) ui_groups = self.chunkWidgets(group) for uigroup in ui_groups: sizer = wx.BoxSizer(wx.HORIZONTAL) for item in uigroup: widget = self.reifyWidget(parent, item) # !Mutate the reifiedWidgets instance variable in place self.reifiedWidgets.append(widget) sizer.Add(widget, 1, wx.ALL, 5) boxSizer.Add(sizer, 0, wx.ALL | wx.EXPAND, 5) # apply the same layout rules recursively for subgroups hs = wx.BoxSizer(wx.HORIZONTAL) for e, subgroup in enumerate(group['groups']): self.makeGroup(parent, hs, subgroup, 1, wx.ALL | wx.EXPAND, 5) if e % getin(group, ['options', 'columns'], 2) \ or e == len(group['groups']): boxSizer.Add(hs, *args) hs = wx.BoxSizer(wx.HORIZONTAL) thissizer.Add(boxSizer, *args)
def test_suppress_is_removed_as_default_value(self): """ Issue #469 Argparse uses the literal string ==SUPPRESS== as an internal flag. When encountered in Gooey, these should be dropped and mapped to `None`. """ parser = ArgumentParser(prog='test_program') parser.add_argument("--foo", default=argparse.SUPPRESS) parser.add_argument('--version', action='version', version='1.0') result = argparse_to_json.convert(parser, required_cols=2, optional_cols=2) groups = getin(result, ['widgets', 'test_program', 'contents']) for item in groups[0]['items']: self.assertEqual(getin(item, ['data', 'default']), None)
def createWidgets(self): """ Instantiate the Gooey Widgets that are used within the RadioGroup """ from gooey.gui.components import widgets return [getattr(widgets, item['type'])(self, item) for item in getin(self.widgetInfo, ['data', 'widgets'], [])]
def test_version_maps_to_checkbox(self): testcases = [ [['--version'], {}, 'TextField'], # we only remap if the action is version # i.e. we don't care about the argument name itself [['--version'], { 'action': 'store' }, 'TextField'], # should get mapped to CheckBox becuase of the action [['--version'], { 'action': 'version' }, 'CheckBox'], # ditto, even through the 'name' isn't 'version' [['--foobar'], { 'action': 'version' }, 'CheckBox'], ] for args, kwargs, expectedType in testcases: with self.subTest([args, kwargs]): parser = argparse.ArgumentParser(prog='test') parser.add_argument(*args, **kwargs) result = argparse_to_json.convert(parser, num_required_cols=2, num_optional_cols=2) contents = getin(result, ['widgets', 'test', 'contents'])[0] self.assertEqual(contents['items'][0]['type'], expectedType)
def chunkWidgets(self, group): ''' chunk the widgets up into groups based on their sizing hints ''' ui_groups = [] subgroup = [] for index, item in enumerate(group['items']): if getin(item, ['options', 'full_width'], False): ui_groups.append(subgroup) ui_groups.append([item]) subgroup = [] else: subgroup.append(item) if len(subgroup) == getin(group, ['options', 'columns'], 2) \ or item == group['items'][-1]: ui_groups.append(subgroup) subgroup = [] return ui_groups
def chunkWidgets(self, group): ''' chunk the widgets up into groups based on their sizing hints ''' ui_groups = [] subgroup = [] for index, item in enumerate(group['items']): if getin(item, ['options', 'full_width'], False): ui_groups.append(subgroup) ui_groups.append([item]) subgroup = [] else: subgroup.append(item) if len(subgroup) == getin(group, ['options', 'columns'], 2) \ or item == group['items'][-1]: ui_groups.append(subgroup) subgroup = [] return ui_groups
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: boxSizer = wx.BoxSizer(wx.VERTICAL) boxSizer.AddSpacer(10) boxSizer.Add(wx_util.h1(self, 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, 1, wx.ALL | wx.EXPAND, 5) self.SetSizer(boxSizer)
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: boxSizer = wx.BoxSizer(wx.VERTICAL) boxSizer.AddSpacer(10) boxSizer.Add(wx_util.h1(self, 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, 1, wx.ALL | wx.EXPAND, 5) self.SetSizer(boxSizer)
def createWidgets(self): """ Instantiate the Gooey Widgets that are used within the RadioGroup """ from gooey.gui.components import widgets return [getattr(widgets, item['type'])(self, item) for item in getin(self.widgetInfo, ['data', 'widgets'], [])]
def getValue(self): userValidator = getin(self._options, ['validator', 'test'], 'True') message = getin(self._options, ['validator', 'message'], '') testFunc = eval('lambda user_input: bool(%s)' % userValidator) satisfies = testFunc if self._meta['required'] else ifPresent(testFunc) value = self.getWidgetValue() return { 'id': self._id, 'cmd': self.formatOutput(self._meta, value), 'rawValue': value, 'test': runValidator(satisfies, value), 'error': None if runValidator(satisfies, value) else message, 'clitype': 'positional' if self._meta['required'] and not self._meta['commands'] else 'optional' }
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 test_choice_string_cooersion_no_default(self): """ Make sure that choice types without a default don't create the literal string "None" but stick with the value None """ parser = ArgumentParser() parser.add_argument('--foo', choices=[1, 2, 3]) choice_action = parser._actions[-1] result = argparse_to_json.action_to_json(choice_action, 'Dropdown', {}) self.assertEqual(getin(result, ['data', 'default']), None)
def apply_default_rewrites(spec): top_level_subgroups = list(spec['widgets'].keys()) for subgroup in top_level_subgroups: path = ['widgets', subgroup, 'contents'] contents = getin(spec, path) for group in contents: if group['name'] == 'positional arguments': group['name'] = 'required_args_msg' if group['name'] == 'optional arguments': group['name'] = 'optional_args_msg' return spec
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
def test_listbox_defaults_cast_correctly(self): """ Issue XXX - defaults supplied in a list were turned into a string wholesale (list and all). The defaults should be stored as a list proper with only the _internal_ values coerced to strings. """ parser = GooeyParser() parser.add_argument('--foo', widget="Listbox", nargs="*", choices=[1, 2, 3], default=[1, 2]) choice_action = parser._actions[-1] result = argparse_to_json.action_to_json(choice_action, 'Listbox', {}) self.assertEqual(getin(result, ['data', 'default']), ['1', '2'])
def test_listbox_single_default_cast_correctly(self): """ Single arg defaults to listbox should be wrapped in a list and their contents coerced as usual. """ parser = GooeyParser() parser.add_argument('--foo', widget="Listbox", nargs="*", choices=[1, 2, 3], default="sup") choice_action = parser._actions[-1] result = argparse_to_json.action_to_json(choice_action, 'Listbox', {}) self.assertEqual(getin(result, ['data', 'default']), ['sup'])
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 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 getValue(self) -> t.FieldValue: regexFunc: Callable[[str], bool] = lambda x: bool(re.match(userValidator, x)) userValidator = getin(self._options, ['validator', 'test'], 'True') message = getin(self._options, ['validator', 'message'], '') testFunc = regexFunc \ if getin(self._options, ['validator', 'type'], None) == 'RegexValidator'\ else eval('lambda user_input: bool(%s)' % userValidator) satisfies = testFunc if self._meta['required'] else ifPresent(testFunc) value = self.getWidgetValue() return t.FieldValue( # type: ignore id=self._id, cmd=self.formatOutput(self._meta, value), meta=self._meta, rawValue=value, # type=self.info['type'], enabled=self.IsEnabled(), visible=self.IsShown(), test=runValidator(satisfies, value), error=None if runValidator(satisfies, value) else message, clitype=('positional' if self._meta['required'] and not self._meta['commands'] else 'optional'))
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.widgetInfo = widgetInfo self.error = wx.StaticText(self, label='') self.radioButtons = self.createRadioButtons() self.selected = None self.widgets = self.createWidgets() self.arrange() self.applyStyleRules() 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()
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.widgetInfo = widgetInfo self.error = wx.StaticText(self, label='') self.radioButtons = self.createRadioButtons() self.selected = None self.widgets = self.createWidgets() self.arrange() self.applyStyleRules() 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()
def makeGroup(self, parent, thissizer, group, *args): ''' Messily builds the (potentially) nested and grouped layout Note! Mutates `self.reifiedWidgets` in place with the widgets as they're instantiated! I cannot figure out how to split out the creation of the widgets from their styling without WxPython violently exploding TODO: sort out the WX quirks and clean this up. ''' # determine the type of border , if any, the main sizer will use if getin(group, ['options', 'show_border'], False): boxDetails = wx.StaticBox(parent, -1, self.getName(group) or '') boxSizer = wx.StaticBoxSizer(boxDetails, wx.VERTICAL) else: boxSizer = wx.BoxSizer(wx.VERTICAL) boxSizer.AddSpacer(10) if group['name']: groupName = wx_util.h1(parent, self.getName(group) or '') groupName.SetForegroundColour(getin(group, ['options', 'label_color'])) groupName.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent) boxSizer.Add(groupName, 0, wx.TOP | wx.BOTTOM | wx.LEFT, 8) group_description = getin(group, ['description']) if group_description: description = AutoWrappedStaticText(parent, label=group_description, target=boxSizer) description.SetForegroundColour(getin(group, ['options', 'description_color'])) description.SetMinSize((0, -1)) description.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent) boxSizer.Add(description, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10) # apply an underline when a grouping border is not specified # unless the user specifically requests not to show it if not getin(group, ['options', 'show_border'], False) and group['name'] \ and getin(group, ['options', 'show_underline'], True): boxSizer.Add(wx_util.horizontal_rule(parent), 0, wx.EXPAND | wx.LEFT, 10) ui_groups = self.chunkWidgets(group) for uigroup in ui_groups: sizer = wx.BoxSizer(wx.HORIZONTAL) for item in uigroup: widget = self.reifyWidget(parent, item) if not getin(item, ['options', 'visible'], True): widget.Hide() # !Mutate the reifiedWidgets instance variable in place self.reifiedWidgets.append(widget) sizer.Add(widget, 1, wx.ALL | wx.EXPAND, 5) boxSizer.Add(sizer, 0, wx.ALL | wx.EXPAND, 5) # apply the same layout rules recursively for subgroups hs = wx.BoxSizer(wx.HORIZONTAL) for e, subgroup in enumerate(group['groups']): self.makeGroup(parent, hs, subgroup, 1, wx.EXPAND) if len(group['groups']) != e: hs.AddSpacer(5) # self.makeGroup(parent, hs, subgroup, 1, wx.ALL | wx.EXPAND, 5) itemsPerColumn = getin(group, ['options', 'columns'], 2) if e % itemsPerColumn or (e + 1) == len(group['groups']): boxSizer.Add(hs, *args) hs = wx.BoxSizer(wx.HORIZONTAL) group_top_margin = getin(group, ['options', 'margin_top'], 1) marginSizer = wx.BoxSizer(wx.VERTICAL) marginSizer.Add(boxSizer, 1, wx.EXPAND | wx.TOP, group_top_margin) thissizer.Add(marginSizer, *args)