diff --git a/gooey/gui/ChooserRunner.py b/gooey/gui/chooser_runner.py similarity index 86% rename from gooey/gui/ChooserRunner.py rename to gooey/gui/chooser_runner.py index 9f27e3d..3744a63 100644 --- a/gooey/gui/ChooserRunner.py +++ b/gooey/gui/chooser_runner.py @@ -3,7 +3,7 @@ __author__ = 'Chris' import wx from wx.lib import wordwrap -from Chooser import FileChooser, DirectoryChooser, CalendarChooser +from choosers import FileChooser, DirectoryChooser, CalendarChooser class MyFrame(wx.Frame): def __init__(self, parent): diff --git a/gooey/gui/Chooser.py b/gooey/gui/choosers.py similarity index 100% rename from gooey/gui/Chooser.py rename to gooey/gui/choosers.py diff --git a/gooey/gui/componenets2_runner.py b/gooey/gui/componenets2_runner.py new file mode 100644 index 0000000..f8a0d83 --- /dev/null +++ b/gooey/gui/componenets2_runner.py @@ -0,0 +1,26 @@ +__author__ = 'Chris' + +import wx +import components2 + + +class MyFrame(wx.Frame): + def __init__(self, parent): + wx.Frame.__init__(self, parent, title="test", size=(320, 240)) + self.SetBackgroundColour('#ffffff') + + sizer = wx.BoxSizer(wx.VERTICAL) + f = components2.Counter({ + 'title': 'cool title', + 'help_msg': 'cool help msg that is super long and intense andd has lots of words!', 'nargs': '+', + 'option_strings': ['-f', '--fudger'], + 'choices': ['choice 1', 'choice 2', 'choice 3'] + }) + sizer.Add(f.build(self), 0, wx.EXPAND) + self.SetSizer(sizer) + +if __name__ == '__main__': + app = wx.App(False) + frame = MyFrame(None) + frame.Show(True) + app.MainLoop() diff --git a/gooey/gui/components.py b/gooey/gui/components.py index c6cbb56..8d80e40 100644 --- a/gooey/gui/components.py +++ b/gooey/gui/components.py @@ -20,6 +20,122 @@ class BuildException(RuntimeError): pass +class AbstractGuiComponent(object): + ''' + Template pattern-y abstract class for the gui. + Children must all implement the BuildWidget and getValue + methods. + ''' + __metaclass__ = ABCMeta + + def __init__(self): + self._widget = None + self.msg = EMPTY + + def Build(self, parent): + self._widget = self.BuildInputWidget(parent, self._action) + if self.HasHelpMsg(self._action): + self._msg = self.CreateHelpMsgWidget(parent, self._action) + else: + self._msg = None + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(self.CreateNameLabelWidget(parent, self._action)) + sizer.AddSpacer(2) + + if self._msg: + sizer.Add(self._msg, 0, wx.EXPAND) + sizer.AddSpacer(2) + else: + sizer.AddStretchSpacer(1) + + sizer.AddStretchSpacer(1) + sizer.Add(self._widget, 0, wx.EXPAND) + return sizer + + @abstractmethod + def BuildInputWidget(self, parent, action): + ''' Must construct the main widget type for the Action ''' + pass + + + def HasHelpMsg(self, action): + return action.help is not None + + + def CreateHelpMsgWidget(self, parent, action): + base_text = wx.StaticText(parent, label=action.help) + if self.HasNargs(action): + base_text.SetLabelText(base_text.GetLabelText() + self.CreateNargsMsg(action)) + styling.MakeDarkGrey(base_text) + return base_text + + def HasNargs(self, action): + return action.nargs is not None and action.nargs is not 0 + + + def CreateNargsMsg(self, action): + if isinstance(action.nargs, int): + return '\n(Note: exactly {0} arguments are required)'.format(action.nargs) + elif action.nargs == '+': + return '\n(Note: at least 1 or more arguments are required)' + return '' + + + def CreateNameLabelWidget(self, parent, action): + label = str(action.dest).title() + if len(action.option_strings) > 1: + label += ' (%s)' % action.option_strings[0] + text = wx.StaticText(parent, label=label) + styling.MakeBold(text) + return text + + + def AssertInitialization(self, clsname): + if not self._widget: + raise BuildException('%s was not correctly initialized' % clsname) + + + def __str__(self): + return str(self._action) + + + @abstractmethod + def GetValue(self): + ''' Returns the state of the given widget ''' + pass + + + def Update(self, size): + ''' + Manually word wraps the StaticText help objects which would + otherwise not wrap on resize + + Content area is based on each grid having two equally sized + columns, where the content area is defined as 87% of the halved + window width. The wiggle room is the distance +- 10% of the + content_area. + + Wrap calculation is run only when the size of the help_msg + extends outside of the wiggle_room. This was done to avoid + the "flickering" that comes from constantly resizing a + StaticText object. + ''' + if self._msg is None: + return + help_msg = self._msg + width, height = size + content_area = int((width / 2) * .87) + + print 'wiget size', help_msg.Size[0] + wiggle_room = range(int(content_area - content_area * .05), int(content_area + content_area * .05)) + print '(', int(content_area - content_area * .05), ' -> ', int(content_area + content_area * .05), ')' + if help_msg.Size[0] not in wiggle_room: + self._msg.SetLabel(self._msg.GetLabelText().replace('\n', ' ')) + self._msg.Wrap(content_area) + + + class AbstractComponent(object): ''' Template pattern-y abstract class for the gui. diff --git a/gooey/gui/components2.py b/gooey/gui/components2.py new file mode 100644 index 0000000..1913e97 --- /dev/null +++ b/gooey/gui/components2.py @@ -0,0 +1,101 @@ +__author__ = 'Chris' + +import wx +import styling + +import widget_pack + +class BaseGuiComponent(object): + def __init__(self, data, widget_pack): + self.data = data + + # parent + self.panel = None + + # Widgets + self.title = None + self.help_msg = None + + # Internal WidgetPack + self.widget_pack = widget_pack + + def build(self, parent): + return self.do_layout(parent) + + def do_layout(self, parent): + self.panel = wx.Panel(parent) + + self.title = self.createTitle(self.panel) + self.help_msg = self.createHelpMsgWidget(self.panel) + core_widget_set = self.widget_pack.build(self.panel, self.data) + + vertical_container = wx.BoxSizer(wx.VERTICAL) + + vertical_container.Add(self.title) + vertical_container.AddSpacer(2) + + if self.help_msg.GetLabelText(): + vertical_container.Add(self.help_msg, 1, wx.EXPAND) + vertical_container.AddSpacer(2) + else: + vertical_container.AddStretchSpacer(1) + + vertical_container.Add(core_widget_set, 0, wx.EXPAND) + self.panel.SetSizer(vertical_container) + + self.panel.Bind(wx.EVT_SIZE, self.onResize) + return self.panel + + def createHelpMsgWidget(self, parent): + label_text = (self.formatExtendedHelpMsg(self.data) + if self.data['nargs'] + else self.data['help_msg']) + base_text = wx.StaticText(parent, label=label_text) + styling.MakeDarkGrey(base_text) + return base_text + + def createTitle(self, parent): + text = wx.StaticText(parent, label=self.data['title'].title()) + styling.MakeBold(text) + return text + + def formatExtendedHelpMsg(self, data): + base_text = data['help_msg'] + nargs = data['nargs'] + if isinstance(nargs, int): + return '{base}\n(Note: exactly {nargs} arguments are required)'.format(base=base_text, nargs=nargs) + elif nargs == '+': + return '{base}\n(Note: at least 1 or more arguments are required)'.format(base=base_text) + return base_text + + def onResize(self, evt): + # handle internal widgets + self._onResize(evt) + # propagate event to child widgets + self.widget_pack.onResize(evt) + evt.Skip() + + def _onResize(self, evt): + if self.help_msg is None: + return + container_width, _ = self.panel.Size + text_width, _ = self.help_msg.Size + + if text_width != container_width: + self.help_msg.SetLabel(self.help_msg.GetLabelText().replace('\n', ' ')) + self.help_msg.Wrap(container_width) + + def getValue(self): + return self.widget_pack.getValue() + + +FileChooser = lambda data: BaseGuiComponent(data=data, widget_pack=widget_pack.FileChooserPayload()) +DirChooser = lambda data: BaseGuiComponent(data=data, widget_pack=widget_pack.DirChooserPayload()) +DateChooser = lambda data: BaseGuiComponent(data=data, widget_pack=widget_pack.DateChooserPayload()) +TextField = lambda data: BaseGuiComponent(data=data, widget_pack=widget_pack.TextInputPayload()) +Dropdown = lambda data: BaseGuiComponent(data=data, widget_pack=widget_pack.DropdownPayload()) +Counter = lambda data: BaseGuiComponent(data=data, widget_pack=widget_pack.CounterPayload()) + + + + diff --git a/gooey/gui/widget_pack.py b/gooey/gui/widget_pack.py new file mode 100644 index 0000000..8ee0c34 --- /dev/null +++ b/gooey/gui/widget_pack.py @@ -0,0 +1,160 @@ + + +__author__ = 'Chris' + +import wx +from abc import ABCMeta, abstractmethod +from calender_dialog import CalendarDlg + + +class WidgetPack(object): + """ + Interface specifying the contract to which + all `WidgetPack`s will adhere + """ + __metaclass__ = ABCMeta + + @abstractmethod + def build(self, parent, data): + pass + + @abstractmethod + def getValue(self): + pass + + def onResize(self, evt): + pass + + + +class BaseChooser(WidgetPack): + def __init__(self, button_text='Browse'): + self.button_text = button_text + + self.parent = None + self.text_box = None + self.button = None + + def build(self, parent, data=None): + self.parent = parent + self.text_box = wx.TextCtrl(self.parent) + self.button = wx.Button(self.parent, label=self.button_text, size=(73, 23)) + + widget_sizer = wx.BoxSizer(wx.HORIZONTAL) + widget_sizer.Add(self.text_box, 1, wx.EXPAND) + widget_sizer.AddSpacer(10) + widget_sizer.Add(self.button, 0) + + parent.Bind(wx.EVT_BUTTON, self.onButton, self.button) + return widget_sizer + + def getValue(self): + return self.text_box.GetValue() + + def onButton(self, evt): + raise NotImplementedError + + +class FileChooserPayload(BaseChooser): + def __init__(self): + BaseChooser.__init__(self) + + def onButton(self, evt): + dlg = wx.FileDialog(self.parent, style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) + result = (dlg.GetPath() + if dlg.ShowModal() == wx.ID_OK + else None) + if result: + # self.text_box references a field on the class this is passed into + # kinda hacky, but avoided a buncha boilerplate + self.text_box.SetLabelText(result) + + +class DirChooserPayload(BaseChooser): + def __init__(self): + BaseChooser.__init__(self) + + def onButton(self, evt): + dlg = wx.DirDialog(self.parent, 'Select directory', style=wx.DD_DEFAULT_STYLE) + result = (dlg.GetPath() + if dlg.ShowModal() == wx.ID_OK + else None) + if result: + self.text_box.SetLabelText(result) + + +class DateChooserPayload(BaseChooser): + def __init__(self): + BaseChooser.__init__(self, button_text='Pick Date') + + def onButton(self, evt): + dlg = CalendarDlg(self.parent) + dlg.ShowModal() + if dlg.GetPath(): + self.text_box.SetLabelText(dlg.GetPath()) + + +class TextInputPayload(WidgetPack): + def __init__(self): + self.widget = None + + def build(self, parent, data): + self.widget = wx.TextCtrl(parent) + return self.widget + + def getValue(self): + return self.widget.GetValue() + + +class DropdownPayload(WidgetPack): + default_value = 'Select Option' + def __init__(self): + self.option_string = None + self.widget = None + + def build(self, parent, data): + self.option_string = data['option_strings'][0] + self.widget = wx.ComboBox( + parent=parent, + id=-1, + value=self.default_value, + choices=data['choices'], + style=wx.CB_DROPDOWN + ) + return self.widget + + def getValue(self): + if self.widget.GetValue() == self.default_value: + return None + return ' '.join([self.option_string, self.widget.GetValue()]) + + +class CounterPayload(WidgetPack): + def __init__(self): + self.option_string = None + self.widget = None + + def build(self, parent, data): + self.option_string = data['option_strings'][0] + self.widget = wx.ComboBox( + parent=parent, + id=-1, + value='', + choices=[str(x) for x in range(1, 7)], + style=wx.CB_DROPDOWN + ) + return self.widget + + def getValue(self): + ''' + Returns + str(option_string * DropDown Value) + e.g. + -vvvvv + ''' + dropdown_value = self.widget.GetValue() + if not str(dropdown_value).isdigit(): + return None + arg = str(self.option_string).replace('-', '') + repeated_args = arg * int(dropdown_value) + return '-' + repeated_args