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.

191 lines
7.3 KiB

  1. import wx
  2. from wx.lib.scrolledpanel import ScrolledPanel
  3. from gooey.gui.util import wx_util
  4. from gooey.util.functional import getin, flatmap, merge, compact, indexunique
  5. from gooey.gui.components.widgets.radio_group import RadioGroup
  6. class ConfigPage(ScrolledPanel):
  7. def __init__(self, parent, rawWidgets, *args, **kwargs):
  8. super(ConfigPage, self).__init__(parent, *args, **kwargs)
  9. self.SetupScrolling(scroll_x=False, scrollToTop=False)
  10. self.rawWidgets = rawWidgets
  11. self.reifiedWidgets = []
  12. self.layoutComponent()
  13. self.widgetsMap = indexunique(lambda x: x._id, self.reifiedWidgets)
  14. ## TODO: need to rethink what uniquely identifies an argument.
  15. ## Out-of-band IDs, while simple, make talking to the client program difficult
  16. ## unless they're agreed upon before hand. Commands, as used here, have the problem
  17. ## of (a) not being nearly granular enough (for instance, `-v` could represent totally different
  18. ## things given context/parser position), and (b) cannot identify positional args.
  19. def firstCommandIfPresent(self, widget):
  20. commands = widget._meta['commands']
  21. return commands[0] if commands else ''
  22. def getPositionalArgs(self):
  23. return [widget.getValue()['cmd'] for widget in self.reifiedWidgets
  24. if widget.info['cli_type'] == 'positional']
  25. def getOptionalArgs(self):
  26. return [widget.getValue()['cmd'] for widget in self.reifiedWidgets
  27. if widget.info['cli_type'] != 'positional']
  28. def isValid(self):
  29. states = [widget.getValue() for widget in self.reifiedWidgets]
  30. return not any(compact([state['error'] for state in states]))
  31. def seedUI(self, seeds):
  32. radioWidgets = self.indexInternalRadioGroupWidgets()
  33. for id, values in seeds.items():
  34. if id in self.widgetsMap:
  35. self.widgetsMap[id].setOptions(values)
  36. if id in radioWidgets:
  37. radioWidgets[id].setOptions(values)
  38. def indexInternalRadioGroupWidgets(self):
  39. groups = filter(lambda x: x.info['type'] == 'RadioGroup', self.reifiedWidgets)
  40. widgets = flatmap(lambda group: group.widgets, groups)
  41. return indexunique(lambda x: x._id, widgets)
  42. def displayErrors(self):
  43. states = [widget.getValue() for widget in self.reifiedWidgets]
  44. errors = [state for state in states if state['error']]
  45. for error in errors:
  46. widget = self.widgetsMap[error['id']]
  47. widget.setErrorString(error['error'])
  48. widget.showErrorString(True)
  49. while widget.GetParent():
  50. widget.Layout()
  51. widget = widget.GetParent()
  52. def resetErrors(self):
  53. for widget in self.reifiedWidgets:
  54. widget.setErrorString('')
  55. widget.showErrorString(False)
  56. def hideErrors(self):
  57. for widget in self.reifiedWidgets:
  58. widget.hideErrorString()
  59. def layoutComponent(self):
  60. sizer = wx.BoxSizer(wx.VERTICAL)
  61. for item in self.rawWidgets['contents']:
  62. self.makeGroup(self, sizer, item, 0, wx.EXPAND)
  63. self.SetSizer(sizer)
  64. def makeGroup(self, parent, thissizer, group, *args):
  65. '''
  66. Messily builds the (potentially) nested and grouped layout
  67. Note! Mutates `self.reifiedWidgets` in place with the widgets as they're
  68. instantiated! I cannot figure out how to split out the creation of the
  69. widgets from their styling without WxPython violently exploding
  70. TODO: sort out the WX quirks and clean this up.
  71. '''
  72. # determine the type of border , if any, the main sizer will use
  73. if getin(group, ['options', 'show_border'], False):
  74. boxDetails = wx.StaticBox(parent, -1, group['name'] or '')
  75. boxSizer = wx.StaticBoxSizer(boxDetails, wx.VERTICAL)
  76. else:
  77. boxSizer = wx.BoxSizer(wx.VERTICAL)
  78. boxSizer.AddSpacer(10)
  79. if group['name']:
  80. boxSizer.Add(wx_util.h1(parent, group['name'] or ''), 0, wx.TOP | wx.BOTTOM | wx.LEFT, 8)
  81. group_description = getin(group, ['description'])
  82. if group_description:
  83. description = wx.StaticText(parent, label=group_description)
  84. boxSizer.Add(description, 0, wx.EXPAND | wx.LEFT, 10)
  85. # apply an underline when a grouping border is not specified
  86. if not getin(group, ['options', 'show_border'], False) and group['name']:
  87. boxSizer.Add(wx_util.horizontal_rule(parent), 0, wx.EXPAND | wx.LEFT, 10)
  88. ui_groups = self.chunkWidgets(group)
  89. for uigroup in ui_groups:
  90. sizer = wx.BoxSizer(wx.HORIZONTAL)
  91. for item in uigroup:
  92. widget = self.reifyWidget(parent, item)
  93. # !Mutate the reifiedWidgets instance variable in place
  94. self.reifiedWidgets.append(widget)
  95. sizer.Add(widget, 1, wx.ALL, 5)
  96. boxSizer.Add(sizer, 0, wx.ALL | wx.EXPAND, 5)
  97. # apply the same layout rules recursively for subgroups
  98. hs = wx.BoxSizer(wx.HORIZONTAL)
  99. for e, subgroup in enumerate(group['groups']):
  100. self.makeGroup(parent, hs, subgroup, 1, wx.ALL | wx.EXPAND, 5)
  101. if e % getin(group, ['options', 'columns'], 2) \
  102. or e == len(group['groups']):
  103. boxSizer.Add(hs, *args)
  104. hs = wx.BoxSizer(wx.HORIZONTAL)
  105. thissizer.Add(boxSizer, *args)
  106. def chunkWidgets(self, group):
  107. ''' chunk the widgets up into groups based on their sizing hints '''
  108. ui_groups = []
  109. subgroup = []
  110. for index, item in enumerate(group['items']):
  111. if getin(item, ['options', 'full_width'], False):
  112. ui_groups.append(subgroup)
  113. ui_groups.append([item])
  114. subgroup = []
  115. else:
  116. subgroup.append(item)
  117. if len(subgroup) == getin(group, ['options', 'columns'], 2) \
  118. or item == group['items'][-1]:
  119. ui_groups.append(subgroup)
  120. subgroup = []
  121. return ui_groups
  122. def reifyWidget(self, parent, item):
  123. ''' Convert a JSON description of a widget into a WxObject '''
  124. from gooey.gui.components import widgets
  125. widgetClass = getattr(widgets, item['type'])
  126. return widgetClass(parent, item)
  127. class TabbedConfigPage(ConfigPage):
  128. """
  129. Splits top-level groups across tabs
  130. """
  131. def layoutComponent(self):
  132. # self.rawWidgets['contents'] = self.rawWidgets['contents'][1:2]
  133. self.notebook = wx.Notebook(self, style=wx.BK_DEFAULT)
  134. panels = [wx.Panel(self.notebook) for _ in self.rawWidgets['contents']]
  135. sizers = [wx.BoxSizer(wx.VERTICAL) for _ in panels]
  136. for group, panel, sizer in zip(self.rawWidgets['contents'], panels, sizers):
  137. self.makeGroup(panel, sizer, group, 0, wx.EXPAND)
  138. panel.SetSizer(sizer)
  139. panel.Layout()
  140. self.notebook.AddPage(panel, group['name'])
  141. self.notebook.Layout()
  142. _sizer = wx.BoxSizer(wx.VERTICAL)
  143. _sizer.Add(self.notebook, 1, wx.EXPAND)
  144. self.SetSizer(_sizer)
  145. self.Layout()