From 229e8b54c63e15c37b954e40afeb259a86e592ff Mon Sep 17 00:00:00 2001 From: chriskiehl Date: Sun, 15 Oct 2017 16:00:06 -0700 Subject: [PATCH] add Listbox widget type --- README.md | 1 + gooey/gui/model.py | 6 +- gooey/gui/widgets/components.py | 10 ++ gooey/gui/widgets/widget_pack.py | 35 +++++ gooey/python_bindings/argparse_to_json.py | 3 +- gooey/python_bindings/gooey_parser.py | 177 +++++++++++----------- 6 files changed, 145 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index c8f93e6..1fc8b56 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,7 @@ However, by dropping in `GooeyParser` and supplying a `widget` name, you can dis | DirChooser/FileChooser |

| | DateChooser                                             |

| | PasswordField |

| +| Listbox | ![image](https://user-images.githubusercontent.com/1408720/31590191-fadd06f2-b1c0-11e7-9a49-7cbf0c6d33d1.png) | diff --git a/gooey/gui/model.py b/gooey/gui/model.py index ed4c061..c3c1629 100644 --- a/gooey/gui/model.py +++ b/gooey/gui/model.py @@ -61,7 +61,11 @@ class MyWidget(object): arg = str(self.commands[0]).replace('-', '') repeated_args = arg * int(self._value) return '-' + repeated_args - + if self.type == 'Listbox': + if self.commands and self._value: + return u'{} {}'.format(self.commands[0], ' '.join(map(quote, self._value))) + else: + return ' '.join(map(quote, self._value)) if self._value else '' if self.type == 'Dropdown': if self._value == 'Select Option': return None diff --git a/gooey/gui/widgets/components.py b/gooey/gui/widgets/components.py index 251735b..bfc5925 100644 --- a/gooey/gui/widgets/components.py +++ b/gooey/gui/widgets/components.py @@ -217,12 +217,22 @@ class RadioGroup(object): pass +class Listbox(BaseGuiComponent): + widget_class = widget_pack.ListboxPayload + + def set_value(self, val): + if val: + self.widget_pack.set_value(val) + def build_subclass(name, widget_class): # this seemed faster than typing class X a bunch return type(name, (BaseGuiComponent,), {'widget_class': widget_class}) + + + FileChooser = build_subclass('FileChooser', widget_pack.FileChooserPayload) MultiFileChooser = build_subclass('MultiFileChooser', widget_pack.MultiFileSaverPayload) DirChooser = build_subclass('DirChooser', widget_pack.DirChooserPayload) diff --git a/gooey/gui/widgets/widget_pack.py b/gooey/gui/widgets/widget_pack.py index bf5fc94..0ad1eb2 100644 --- a/gooey/gui/widgets/widget_pack.py +++ b/gooey/gui/widgets/widget_pack.py @@ -9,6 +9,7 @@ from gooey.gui.util.filedrop import FileDrop from gooey.gui.widgets.calender_dialog import CalendarDlg + class WidgetPack(object): """ Interface specifying the contract to which @@ -92,10 +93,12 @@ class BaseMultiFileChooser(BaseFileChooser): def get_path(self, dlg): return os.pathsep.join(dlg.GetPaths()) + class MultiFileSaverPayload(BaseMultiFileChooser): def __init__(self, *args, **kwargs): BaseMultiFileChooser.__init__(self, build_dialog(wx.FD_MULTIPLE, False)) + class MultiDirChooserPayload(BaseMultiFileChooser): class MyMultiDirChooser(MDD.MultiDirDialog): def __init__(self, *args, **kwargs): @@ -150,6 +153,7 @@ class TextAreaPayload(WidgetPack): def get_value(self): return self.widget.GetValue() + class DropdownPayload(WidgetPack): default_value = 'Select Option' @@ -175,6 +179,32 @@ class DropdownPayload(WidgetPack): self.widget.SetValue(text) +class ListboxPayload(WidgetPack): + default_value = 'Select Option' + + def __init__(self, no_quoting=False): + self.widget = None + self.option_string = None + self.no_quoting = no_quoting + + def build(self, parent, data, choices=None): + self.widget = wx.ListBox( + parent=parent, + choices=choices, + size=(-1,60), + style=wx.LB_MULTIPLE + ) + return self.widget + + def get_value(self): + return [self.widget.GetString(index) + for index in self.widget.GetSelections()] + + def set_value(self, strings): + for s in strings: + self.widget.SetStringSelection(s) + + class CounterPayload(WidgetPack): def __init__(self): self.widget = None @@ -192,10 +222,12 @@ class CounterPayload(WidgetPack): def get_value(self): return self.widget.GetValue() + class DirDialog(wx.DirDialog): def __init__(self, parent, *args, **kwargs): wx.DirDialog.__init__(self, parent, 'Select Directory', style=wx.DD_DEFAULT_STYLE) + class PasswordInputPayload(WidgetPack): def __init__(self, no_quoting=False): self.widget = None @@ -214,15 +246,18 @@ class PasswordInputPayload(WidgetPack): def get_value(self): return self.widget.GetValue() + def safe_default(data, default): return '' + def build_dialog(style, exist_constraint=True, **kwargs): if exist_constraint: return lambda panel: wx.FileDialog(panel, style=style | wx.FD_FILE_MUST_EXIST, **kwargs) else: return lambda panel: wx.FileDialog(panel, style=style, **kwargs) + def build_subclass(subclass, dialog): return type(subclass, (BaseFileChooser,), {'dialog': dialog}) diff --git a/gooey/python_bindings/argparse_to_json.py b/gooey/python_bindings/argparse_to_json.py index 878909d..f867081 100644 --- a/gooey/python_bindings/argparse_to_json.py +++ b/gooey/python_bindings/argparse_to_json.py @@ -32,7 +32,8 @@ VALID_WIDGETS = ( 'CheckBox', 'MultiDirChooser', 'Textarea', - 'PasswordField' + 'PasswordField', + 'Listbox' ) diff --git a/gooey/python_bindings/gooey_parser.py b/gooey/python_bindings/gooey_parser.py index aee804e..12b644b 100644 --- a/gooey/python_bindings/gooey_parser.py +++ b/gooey/python_bindings/gooey_parser.py @@ -1,96 +1,103 @@ from argparse import ArgumentParser, _SubParsersAction, _MutuallyExclusiveGroup + from gooey.gui.lang.i18n import _ class GooeySubParser(_SubParsersAction): - def __init__(self, *args, **kwargs): - super(GooeySubParser, self).__init__(*args, **kwargs) + def __init__(self, *args, **kwargs): + super(GooeySubParser, self).__init__(*args, **kwargs) + class GooeyMutuallyExclusiveGroup(_MutuallyExclusiveGroup): - def __init__(self, parser, widgets, *args, **kwargs): - self.parser = parser - self.widgets = widgets - super(GooeyMutuallyExclusiveGroup, self).__init__(self.parser, *args, **kwargs) + def __init__(self, parser, widgets, *args, **kwargs): + self.parser = parser + self.widgets = widgets + super(GooeyMutuallyExclusiveGroup, self).__init__(self.parser, *args, + **kwargs) - def add_argument(self, *args, **kwargs): - widget = kwargs.pop('widget', None) - metavar = kwargs.pop('metavar', None) - super(GooeyMutuallyExclusiveGroup, self).add_argument(*args, **kwargs) - self.parser._actions[-1].metavar = metavar - self.widgets[self.parser._actions[-1].dest] = widget + def add_argument(self, *args, **kwargs): + widget = kwargs.pop('widget', None) + metavar = kwargs.pop('metavar', None) + super(GooeyMutuallyExclusiveGroup, self).add_argument(*args, **kwargs) + self.parser._actions[-1].metavar = metavar + self.widgets[self.parser._actions[-1].dest] = widget class GooeyParser(object): - def __init__(self, **kwargs): - self.__dict__['parser'] = ArgumentParser(**kwargs) - self.widgets = {} - - @property - def _mutually_exclusive_groups(self): - return self.parser._mutually_exclusive_groups - - @property - def _actions(self): - return self.parser._actions - - @property - def description(self): - return self.parser.description - - def add_argument(self, *args, **kwargs): - widget = kwargs.pop('widget', None) - metavar = kwargs.pop('metavar', None) - self.parser.add_argument(*args, **kwargs) - self.parser._actions[-1].metavar = metavar - self.widgets[self.parser._actions[-1].dest] = widget - - # def add_mutually_exclusive_group(self, **kwargs): - # return self.parser.add_mutually_exclusive_group(**kwargs) - - def add_mutually_exclusive_group(self, **kwargs): - group = GooeyMutuallyExclusiveGroup(self.parser, self.widgets, **kwargs) - self.parser._mutually_exclusive_groups.append(group) - return group - - def add_argument_group(self, *args, **kwargs): - return self.parser.add_argument_group(*args, **kwargs) - - def parse_args(self, args=None, namespace=None): - return self.parser.parse_args(args, namespace) - - def add_subparsers(self, **kwargs): - if self._subparsers is not None: - self.error(_('cannot have multiple subparser arguments')) - - # add the parser class to the arguments if it's not present - kwargs.setdefault('parser_class', type(self)) - - if 'title' in kwargs or 'description' in kwargs: - title = _(kwargs.pop('title', 'subcommands')) - description = _(kwargs.pop('description', None)) - self._subparsers = self.add_argument_group(title, description) - else: - self._subparsers = self._positionals - - # prog defaults to the usage message of this parser, skipping - # optional arguments and with no "usage:" prefix - if kwargs.get('prog') is None: - formatter = self._get_formatter() - positionals = self._get_positional_actions() - groups = self._mutually_exclusive_groups - formatter.add_usage(self.usage, positionals, groups, '') - kwargs['prog'] = formatter.format_help().strip() - - # create the parsers action and add it to the positionals list - parsers_class = self._pop_action_class(kwargs, 'parsers') - action = parsers_class(option_strings=[], **kwargs) - self._subparsers._add_action(action) - - # return the created parsers action - return action - - def __getattr__(self, item): - return getattr(self.parser, item) - - def __setattr__(self, key, value): - return setattr(self.parser, key, value) + def __init__(self, **kwargs): + self.__dict__['parser'] = ArgumentParser(**kwargs) + self.widgets = {} + + @property + def _mutually_exclusive_groups(self): + return self.parser._mutually_exclusive_groups + + @property + def _actions(self): + return self.parser._actions + + @property + def description(self): + return self.parser.description + + def add_argument(self, *args, **kwargs): + widget = kwargs.pop('widget', None) + metavar = kwargs.pop('metavar', None) + + if widget and widget == 'Listbox': + if not 'nargs' in kwargs or kwargs['nargs'] not in ['*', '+']: + raise ValueError( + 'Gooey\'s Listbox widget requires that nargs be specified.\n' + 'Nargs must be set to either `*` or `+` (e.g. nargs="*")' + ) + self.parser.add_argument(*args, **kwargs) + self.parser._actions[-1].metavar = metavar + self.widgets[self.parser._actions[-1].dest] = widget + + def add_mutually_exclusive_group(self, **kwargs): + group = GooeyMutuallyExclusiveGroup(self.parser, self.widgets, **kwargs) + self.parser._mutually_exclusive_groups.append(group) + return group + + def add_argument_group(self, *args, **kwargs): + return self.parser.add_argument_group(*args, **kwargs) + + def parse_args(self, args=None, namespace=None): + return self.parser.parse_args(args, namespace) + + def add_subparsers(self, **kwargs): + if self._subparsers is not None: + self.error(_('cannot have multiple subparser arguments')) + + # add the parser class to the arguments if it's not present + kwargs.setdefault('parser_class', type(self)) + + if 'title' in kwargs or 'description' in kwargs: + title = _(kwargs.pop('title', 'subcommands')) + description = _(kwargs.pop('description', None)) + self._subparsers = self.add_argument_group(title, description) + else: + self._subparsers = self._positionals + + # prog defaults to the usage message of this parser, skipping + # optional arguments and with no "usage:" prefix + if kwargs.get('prog') is None: + formatter = self._get_formatter() + positionals = self._get_positional_actions() + groups = self._mutually_exclusive_groups + formatter.add_usage(self.usage, positionals, groups, '') + kwargs['prog'] = formatter.format_help().strip() + + # create the parsers action and add it to the positionals list + parsers_class = self._pop_action_class(kwargs, 'parsers') + action = parsers_class(option_strings=[], **kwargs) + self._subparsers._add_action(action) + + # return the created parsers action + return action + + def __getattr__(self, item): + return getattr(self.parser, item) + + def __setattr__(self, key, value): + return setattr(self.parser, key, value)