diff --git a/src/app/dialogs/action_sorter.py b/src/app/dialogs/action_sorter.py index 6296bf0..ae3832e 100644 --- a/src/app/dialogs/action_sorter.py +++ b/src/app/dialogs/action_sorter.py @@ -9,7 +9,7 @@ import wx from argparse import ( _StoreAction, _StoreConstAction, _StoreFalseAction, _StoreTrueAction, - _CountAction, _AppendAction) + _CountAction, _AppendAction, _HelpAction) DEBUG = 1 @@ -58,7 +58,7 @@ class ActionSorter(object): self._positionals = self.get_positionals(self._actions) self._choices = self.get_optionals_with_choices(self._actions) self._optionals = self.get_optionals_without_choices(self._actions) - self._flags = self.get_flag_style_optionals(self._actions) + self._flags = self.get_flag_style_optionals(self._actions) self._counters = self.get_counter_actions(self._actions) if DEBUG: @@ -67,6 +67,7 @@ class ActionSorter(object): self._display('ActionSorter: optionals', self._optionals) self._display('ActionSorter: booleans', self._flags) self._display('ActionSorter: counters', self._counters) + print '|-------------------------' def _display(self, _type, something): for i in something: @@ -106,7 +107,9 @@ class ActionSorter(object): for action in actions if action.option_strings and not action.choices - and action not in boolean_actions] + and not isinstance(action, _CountAction) + and not isinstance(action, _HelpAction) + and type(action) not in boolean_actions] def get_optionals_with_choices(self, actions): ''' diff --git a/src/app/dialogs/action_sorter_unittest.py b/src/app/dialogs/action_sorter_unittest.py new file mode 100644 index 0000000..bcbe5bb --- /dev/null +++ b/src/app/dialogs/action_sorter_unittest.py @@ -0,0 +1,80 @@ +''' +Created on Jan 16, 2014 + +@author: Chris +''' + +import time +import unittest +import argparse_test_data +from functools import partial +from argparse import _StoreAction, _HelpAction +from action_sorter import ActionSorter + +class Test(unittest.TestCase): + + + def setUp(self): + self._actions = argparse_test_data.parser._actions + self.sorted_actions = ActionSorter(self._actions) + # pain in the A... PEP8 be damned! + self.expected_positionals = [ + "_StoreAction(option_strings=[], dest='filename', nargs=None, const=None, default=None, type=None, choices=None, help='Name of the file you want to read', metavar=None)", + '''_StoreAction(option_strings=[], dest='outfile', nargs=None, const=None, default=None, type=None, choices=None, help="Name of the file where you'll save the output", metavar=None)''' + ] + self.expected_choices = [ + '''_StoreAction(option_strings=['-T', '--tester'], dest='tester', nargs=None, const=None, default=None, type=None, choices=['yes', 'no'], help="Yo, what's up man? I'm a help message!", metavar=None)''' + ] + self.expected_optionals = [ + '''_StoreAction(option_strings=['-o', '--outfile'], dest='outfile', nargs=None, const=None, default=None, type=None, choices=None, help='Redirects output to the file specified by you, the awesome user', metavar=None)''', + '''_StoreAction(option_strings=['-v', '--verbose'], dest='verbose', nargs=None, const=None, default=None, type=None, choices=None, help='Toggles verbosity off', metavar=None)''', + '''_StoreAction(option_strings=['-s', '--schimzammy'], dest='schimzammy', nargs=None, const=None, default=None, type=None, choices=None, help='Add in an optional shimzammy parameter', metavar=None)''' + ] + self.expected_counters = [ + '''_CountAction(option_strings=['-e', '--repeat'], dest='repeat', nargs=0, const=None, default=None, type=None, choices=None, help='Set the number of times to repeat', metavar=None)''' + ] + + self.expected_flags = [ + '''_StoreConstAction(option_strings=['-c', '--constoption'], dest='constoption', nargs=0, const='myconstant', default=None, type=None, choices=None, help='Make sure the const action is correctly sorted', metavar=None)''', + '''_StoreTrueAction(option_strings=['-t', '--truify'], dest='truify', nargs=0, const=True, default=False, type=None, choices=None, help='Ensure the store_true actions are sorted', metavar=None)''', + '''_StoreFalseAction(option_strings=['-f', '--falsificle'], dest='falsificle', nargs=0, const=False, default=True, type=None, choices=None, help='Ensure the store_false actions are sorted', metavar=None)''' + ] + + def testPositionalsReturnsOnlyPositionalActions(self): + positionals = self.sorted_actions._positionals + self.assertEqual(len(positionals), 2) + + self.assertForAllActionsInList(positionals,self.expected_positionals) + + def testHelpActionNotInOptionals(self): + _isinstance = lambda x: isinstance(x, _HelpAction) + self.assertFalse(any(map(_isinstance, self.sorted_actions._optionals))) + + def testChoicesOnlyReturnsChoices(self): + self.assertForAllActionsInList(self.sorted_actions._choices, + self.expected_choices) + + def testOptionalsOnlyReturnsOptionals(self): + self.assertForAllActionsInList(self.sorted_actions._optionals, + self.expected_optionals) + + def testCounterSortOnlyReturnsCounters(self): + self.assertForAllActionsInList(self.sorted_actions._counters, + self.expected_counters) + + def testFlagSortReturnsOnlyFlags(self): + self.assertForAllActionsInList(self.sorted_actions._flags, + self.expected_flags) + + def assertForAllActionsInList(self, actions, expected_actions): + for index, action in enumerate(actions): + self.assertEqual(str(action), expected_actions[index]) + + + + + + +if __name__ == "__main__": + #import sys;sys.argv = ['', 'Test.testName'] + unittest.main() \ No newline at end of file diff --git a/src/app/dialogs/advanced_config.py b/src/app/dialogs/advanced_config.py index e32e984..9f5a130 100644 --- a/src/app/dialogs/advanced_config.py +++ b/src/app/dialogs/advanced_config.py @@ -5,11 +5,13 @@ Created on Dec 28, 2013 ''' import wx - +import components +from wx.lib import wordwrap +from itertools import chain from component_factory import ComponentFactory from wx.lib.scrolledpanel import ScrolledPanel - +PADDING = 10 class AdvancedConfigPanel(ScrolledPanel): @@ -23,25 +25,67 @@ class AdvancedConfigPanel(ScrolledPanel): self.components = ComponentFactory(parser) self.container = wx.BoxSizer(wx.VERTICAL) + self.container.AddSpacer(15) + + self.AddHeaderMsg("Required Arguments") self.container.AddSpacer(10) - self.AddRequiredArgsHeaderMsg() - self.AddWidgets(self.components.positionals) + box = wx.StaticBox(self, label="") + boxsizer = wx.StaticBoxSizer(box, wx.VERTICAL) + self.AddWidgets(self.container, self.components.positionals, add_space=True) + self.container.AddSpacer(10) + self.container.Add(self._draw_horizontal_line(), + 0, wx.LEFT | wx.RIGHT | wx.EXPAND, PADDING) + + self.container.AddSpacer(10) + self.AddHeaderMsg("Optional Arguments") + self.container.AddSpacer(15) + + flag_grids = self.CreateComponentGrid(self.components.flags, vgap=15) + opt_choice_counter_grid = self.CreateComponentGrid(c for c in self.components + if not isinstance(c, components.Flag) + and not isinstance(c, components.Positional)) + self.container.Add(opt_choice_counter_grid, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, PADDING) + self.container.AddSpacer(30) + self.container.Add(flag_grids, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, PADDING) + +# sizer_params = [(grid, 0, wx.LEFT|wx.RIGHT|wx.EXPAND, PADDING) +# for grid in component_grids] +# self.container.AddMany(sizer_params) self.SetSizer(self.container) + self.Bind(wx.EVT_SIZE, self.OnResize) - def AddRequiredArgsHeaderMsg(self): - required_msg = wx.StaticText(self, label="Required Arguments") - self.container.Add(required_msg, 0, wx.LEFT | wx.RIGHT, 20) - - def AddWidgets(self, components): - if not components: - return - component = components[0] - widget_group = component.Build(parent=self) - self.container.Add(widget_group) - self.AddWidgets(components[1:]) + def AddHeaderMsg(self, label): + required_msg = wx.StaticText(self, label=label) + font_size = required_msg.GetFont().GetPointSize() + bold = wx.Font(font_size*1.2, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD) + required_msg.SetFont(bold) + self.container.Add(required_msg, 0, wx.LEFT | wx.RIGHT, PADDING) + + + def AddWidgets(self, sizer, components, add_space=False, padding=PADDING): + for component in components: + widget_group = component.Build(parent=self) + sizer.Add(widget_group, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, padding) + if add_space: + sizer.AddSpacer(8) + + def CreateComponentGrid(self, components, cols=2, vgap=10): + gridsizer = wx.GridSizer(rows=0, cols=cols, vgap=vgap,hgap=4) + self.AddWidgets(gridsizer, components) + return gridsizer + + def _draw_horizontal_line(self): + line = wx.StaticLine(self, -1, style=wx.LI_HORIZONTAL) + line.SetSize((10, 10)) + return line + + def OnResize(self, evt): + print evt.m_size + for component in self.components: + component.Update(evt.m_size) diff --git a/src/app/dialogs/advanced_config.pyc b/src/app/dialogs/advanced_config.pyc index aa35c90..81da9b8 100644 Binary files a/src/app/dialogs/advanced_config.pyc and b/src/app/dialogs/advanced_config.pyc differ diff --git a/src/app/dialogs/advanced_config_unittest.py b/src/app/dialogs/advanced_config_unittest.py index 3942a35..63f703b 100644 --- a/src/app/dialogs/advanced_config_unittest.py +++ b/src/app/dialogs/advanced_config_unittest.py @@ -9,24 +9,18 @@ import os import sys import unittest import advanced_config +import argparse_test_data from argparse import ArgumentParser -class Test(unittest.TestCase): - +class TestAdvancedConfigPanel(unittest.TestCase): def setUp(self): - parser = ArgumentParser(description='Example Argparse Program') - parser.add_argument("filename", help="Name of the file you want to read") - parser.add_argument('-T', '--tester', choices=['yes','no']) - parser.add_argument('-o', '--outfile', help='Redirects output to the specified file') - parser.add_argument('-v', '--verbose', help='Toggles verbosity off') - parser.add_argument('-e', '--repeat', action='count', help='Set the number of times to repeat') - self.parser = parser + self.parser = argparse_test_data.parser def buildWindow(self): app = wx.PySimpleApp() module_name = os.path.split(sys.argv[0])[-1] - frame = wx.Frame(None, -1, module_name) + frame = wx.Frame(None, -1, module_name, size=(640,480)) panel = advanced_config.AdvancedConfigPanel(frame, self.parser) frame.Show() @@ -38,4 +32,14 @@ class Test(unittest.TestCase): if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] - unittest.main() \ No newline at end of file + unittest.main() + + + + + + + + + + \ No newline at end of file diff --git a/src/app/dialogs/argparse_test_data.py b/src/app/dialogs/argparse_test_data.py new file mode 100644 index 0000000..032cc17 --- /dev/null +++ b/src/app/dialogs/argparse_test_data.py @@ -0,0 +1,22 @@ +''' +Created on Jan 16, 2014 + +@author: Chris +''' + +from argparse import ArgumentParser + + +parser = ArgumentParser(description='Example Argparse Program') +parser.add_argument("filename", help="Name of the file you want to read") # positional +parser.add_argument("outfile", help="Name of the file where you'll save the output") # positional +parser.add_argument('-T', '--tester', choices=['yes','no'], help="Yo, what's up man? I'm a help message!") # Choice +parser.add_argument('-o', '--outfile', help='Redirects output to the file specified by you, the awesome user') # Optional +parser.add_argument('-v', '--verbose', help='Toggles verbosity off') # Optional +parser.add_argument('-s', '--schimzammy', help='Add in an optional shimzammy parameter') # Optional +parser.add_argument('-e', '--repeat', action='count', help='Set the number of times to repeat') # Counter +parser.add_argument('-c', '--constoption', action="store_const", const="myconstant", help='Make sure the const action is correctly sorted') # Flag +parser.add_argument('-t', '--truify', action="store_true", help='Ensure the store_true actions are sorted') # Flag +parser.add_argument('-f', '--falsificle', action="store_false", help='Ensure the store_false actions are sorted') # Flag + + \ No newline at end of file diff --git a/src/app/dialogs/component_factory.py b/src/app/dialogs/component_factory.py index 98f1af8..5fbbd16 100644 --- a/src/app/dialogs/component_factory.py +++ b/src/app/dialogs/component_factory.py @@ -4,9 +4,10 @@ Created on Dec 8, 2013 @author: Chris ''' -import wx +import itertools import components import action_sorter +import argparse_test_data class ComponentFactory(object): @@ -22,16 +23,16 @@ class ComponentFactory(object): self.positionals = self.BuildPositionals(self._actions) self.choices = self.BuildChoices(self._actions) self.optionals = self.BuildOptionals(self._actions) - self.booleans = self.BuildFlags(self._actions) + self.flags = self.BuildFlags(self._actions) self.counters = self.BuildCounters(self._actions) self._components = [ - self.positionals, - self.choices, - self.optionals, - self.booleans, - self.counters - ] + self.positionals, + self.choices, + self.optionals, + self.flags, + self.counters + ] def BuildPositionals(self, actions): return self._AssembleWidgetsFromActions(actions, 'Positional', '_positionals') @@ -53,11 +54,19 @@ class ComponentFactory(object): actions_list = getattr(actions, actiontype) return [cls(action) for action in actions_list] + +# def __getitem__(self, slice): +# return self._components[slice] + def __iter__(self): + ''' + return an iterator for all of the contained + components + ''' + return itertools.chain(*self._components) if __name__ == '__main__': - pass - + a = ComponentFactory(argparse_test_data.parser) diff --git a/src/app/dialogs/component_factory.pyc b/src/app/dialogs/component_factory.pyc index 6c2b340..d73e05a 100644 Binary files a/src/app/dialogs/component_factory.pyc and b/src/app/dialogs/component_factory.pyc differ diff --git a/src/app/dialogs/components.py b/src/app/dialogs/components.py index 25c3210..b3fc3eb 100644 --- a/src/app/dialogs/components.py +++ b/src/app/dialogs/components.py @@ -26,21 +26,26 @@ class AbstractComponent(object): self._widget = None def Build(self, parent): + self._widget = self.BuildWidget(parent, self._action) - sizer = wx.BoxSizer(wx.VERTICAL) + self._msg = (self.CreateHelpMsgWidget(parent, self._action) + if self.HasHelpMsg(self._action) + else None) + sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.CreateDestNameWidget(parent, self._action)) sizer.AddSpacer(2) - if self.HasHelpMsg(self._action): - sizer.Add(self.CreateHelpMsgWidget(parent, self._action)) + if self._msg: + sizer.Add(self._msg, 0, wx.EXPAND) sizer.AddSpacer(2) else: - sizer.AddSpacer(10) - + sizer.AddStretchSpacer(1) + if self.HasNargs(self._action): sizer.Add(self.AddNargsMsg(parent, self._action)) - + + sizer.AddStretchSpacer(1) sizer.Add(self._widget, 0, wx.EXPAND) return sizer @@ -80,6 +85,9 @@ class AbstractComponent(object): darkgray = (54,54,54) statictext.SetForegroundColour(darkgray) + def __str__(self): + return str(self._action) + @abstractmethod def BuildWidget(self, parent, action): @@ -90,6 +98,36 @@ class AbstractComponent(object): def GetValue(self): ''' Returns the state of the given widget ''' pass + +# @abstractmethod + 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) + @@ -148,16 +186,61 @@ class Flag(AbstractComponent): self._widget = None self.contents = None + def Build(self, parent): + self._widget = self.BuildWidget(parent, self._action) + self._msg = (self.CreateHelpMsgWidget(parent, self._action) + if self.HasHelpMsg(self._action) + else None) + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(self.CreateDestNameWidget(parent, self._action)) + sizer.AddSpacer(6) + + if self.HasNargs(self._action): + sizer.Add(self.AddNargsMsg(parent, self._action)) + + if self._msg: + hsizer = self.buildHorizonalMsgSizer(parent) + sizer.Add(hsizer, 1, wx.EXPAND) + else: + sizer.AddStretchSpacer(1) + sizer.Add(self._widget, 0, wx.EXPAND) + return sizer + def BuildWidget(self, parent, action): - if len(action.option_strings) > 1: - label = action.option_strings[0] - else: - label = '' - return wx.CheckBox(parent, -1, label=label) + return wx.CheckBox(parent, -1, label='') + + def buildHorizonalMsgSizer(self, panel): + if not self._msg: + return None + sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer.Add(self._widget, 0) + sizer.AddSpacer(6) + sizer.Add(self._msg, 1, wx.EXPAND) + return sizer def GetValue(self): return self._widget.GetValue() + def Update(self, size): + ''' + Custom wrapper calculator to account for the + increased size of the _msg widget after being + inlined with the wx.CheckBox + ''' + if self._msg is None: + return + help_msg = self._msg + width, height = size + content_area = int((width/2)*.70) + + 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 Counter(AbstractComponent): diff --git a/src/app/dialogs/components_unittest.py b/src/app/dialogs/components_unittest.py index a7a2631..b604923 100644 --- a/src/app/dialogs/components_unittest.py +++ b/src/app/dialogs/components_unittest.py @@ -24,7 +24,7 @@ class ComponentsTest(unittest.TestCase): parser.add_argument('-T', '--tester', choices=['yes','no']) parser.add_argument('-o', '--outfile', help='Redirects output to the specified file') parser.add_argument('-v', '--verbose', help='Toggles verbosity off') - parser.add_argument('-e', '--repeat', action='count', help='Set the number of times to repeat') + parser.add_argument('-e', '--repeat', action='count') action = parser._actions self.actions = { 'help' : action[0], @@ -36,10 +36,10 @@ class ComponentsTest(unittest.TestCase): } - def BuildWindow(self, component): + def BuildWindow(self, component, _type): app = wx.PySimpleApp() module_name = os.path.split(sys.argv[0])[-1] - frame = wx.Frame(None, -1, module_name) + frame = wx.Frame(None, -1, _type) panel = wx.Panel(frame, -1, size=(320,240)) component_sizer = component.Build(panel) @@ -65,10 +65,10 @@ class ComponentsTest(unittest.TestCase): def testCounterWidgetBuild(self): self.SetupWidgetAndBuildWindow('Counter') - def SetupWidgetAndBuildWindow(self, _type): component = getattr(components, _type)(self.actions[_type]) - self.BuildWindow(component) + print component + self.BuildWindow(component, _type) if __name__ == "__main__": diff --git a/src/app/dialogs/simple_config_panel.py b/src/app/dialogs/simple_config_panel.py index 3a44594..781a567 100644 --- a/src/app/dialogs/simple_config_panel.py +++ b/src/app/dialogs/simple_config_panel.py @@ -45,8 +45,9 @@ class BodyDisplayPanel(wx.Panel): return self.cmd_textbox.GetValue() def _bold_static_text(self, text_label): - bold = wx.Font(8, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD) text = wx.StaticText(self, label=text_label) + font_size = text.GetFont().GetPointSize() + bold = wx.Font(font_size, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD) text.SetFont(bold) return text diff --git a/src/experiments/__init__.py b/src/experiments/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/experiments/command.py b/src/experiments/command.py new file mode 100644 index 0000000..0b21f8f --- /dev/null +++ b/src/experiments/command.py @@ -0,0 +1,22 @@ +''' +Created on Jan 7, 2014 + +@author: Chris +''' + +class Command(object): + def __init__(self): + pass + + def execute(self): + pass + + +class NextButton(Command): + def execute(self): + print "Next Button" + +class CancelButton(Command): + def execute(self): + print 'Cancel button!' +