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.

225 lines
9.0 KiB

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