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.

210 lines
7.3 KiB

  1. from functools import reduce
  2. import wx
  3. from gooey.gui import formatters, events
  4. from gooey.gui.util import wx_util
  5. from gooey.util.functional import getin, ifPresent
  6. from gooey.gui.validators import runValidator
  7. from gooey.gui.components.util.wrapped_static_text import AutoWrappedStaticText
  8. from gooey.gui.components.mouse import notifyMouseEvent
  9. class BaseWidget(wx.Panel):
  10. widget_class = None
  11. def arrange(self, label, text):
  12. raise NotImplementedError
  13. def getWidget(self, parent, **options):
  14. return self.widget_class(parent, **options)
  15. def connectSignal(self):
  16. raise NotImplementedError
  17. def getSublayout(self, *args, **kwargs):
  18. raise NotImplementedError
  19. def setValue(self, value):
  20. raise NotImplementedError
  21. def receiveChange(self, *args, **kwargs):
  22. raise NotImplementedError
  23. def dispatchChange(self, value, **kwargs):
  24. raise NotImplementedError
  25. def formatOutput(self, metatdata, value):
  26. raise NotImplementedError
  27. class TextContainer(BaseWidget):
  28. # TODO: fix this busted-ass inheritance hierarchy.
  29. # Cracking at the seems for more advanced widgets
  30. # problems:
  31. # - all the usual textbook problems of inheritance
  32. # - assumes there will only ever be ONE widget created
  33. # - assumes those widgets are all created in `getWidget`
  34. # - all the above make for extremely awkward lifecycle management
  35. # - no clear point at which binding is correct.
  36. # - I think the core problem here is that I couple the interface
  37. # for shared presentation layout with the specification of
  38. # a behavioral interface
  39. # - This should be broken apart.
  40. # - presentation can be ad-hoc or composed
  41. # - behavioral just needs a typeclass of get/set/format for Gooey's purposes
  42. widget_class = None
  43. def __init__(self, parent, widgetInfo, *args, **kwargs):
  44. super(TextContainer, self).__init__(parent, *args, **kwargs)
  45. self.info = widgetInfo
  46. self._id = widgetInfo['id']
  47. self._meta = widgetInfo['data']
  48. self._options = widgetInfo['options']
  49. self.label = wx.StaticText(self, label=widgetInfo['data']['display_name'])
  50. self.help_text = AutoWrappedStaticText(self, label=widgetInfo['data']['help'] or '')
  51. self.error = AutoWrappedStaticText(self, label='')
  52. self.error.Hide()
  53. self.widget = self.getWidget(self)
  54. self.layout = self.arrange(*args, **kwargs)
  55. self.setColors()
  56. self.SetSizer(self.layout)
  57. self.bindMouseEvents()
  58. self.Bind(wx.EVT_SIZE, self.onSize)
  59. # Checking for None instead of truthiness means False-evaluaded defaults can be used.
  60. if self._meta['default'] is not None:
  61. self.setValue(self._meta['default'])
  62. self.onComponentInitialized()
  63. def onComponentInitialized(self):
  64. pass
  65. def bindMouseEvents(self):
  66. """
  67. Send any LEFT DOWN mouse events to interested
  68. listeners via pubsub. see: gooey.gui.mouse for background.
  69. """
  70. self.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)
  71. self.label.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)
  72. self.help_text.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)
  73. self.error.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)
  74. self.widget.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)
  75. def arrange(self, *args, **kwargs):
  76. wx_util.make_bold(self.label)
  77. wx_util.withColor(self.label, self._options['label_color'])
  78. wx_util.withColor(self.help_text, self._options['help_color'])
  79. wx_util.withColor(self.error, self._options['error_color'])
  80. self.help_text.SetMinSize((0,-1))
  81. layout = wx.BoxSizer(wx.VERTICAL)
  82. if self._options.get('show_label', True):
  83. layout.Add(self.label, 0, wx.EXPAND)
  84. else:
  85. self.label.Show(False)
  86. layout.AddStretchSpacer(1)
  87. layout.AddSpacer(2)
  88. if self.help_text and self._options.get('show_help', True):
  89. layout.Add(self.help_text, 1, wx.EXPAND)
  90. layout.AddSpacer(2)
  91. else:
  92. self.help_text.Show(False)
  93. layout.AddStretchSpacer(1)
  94. layout.Add(self.getSublayout(), 0, wx.EXPAND)
  95. layout.Add(self.error, 1, wx.EXPAND)
  96. self.error.Hide()
  97. return layout
  98. def setColors(self):
  99. wx_util.make_bold(self.label)
  100. wx_util.withColor(self.label, self._options['label_color'])
  101. wx_util.withColor(self.help_text, self._options['help_color'])
  102. wx_util.withColor(self.error, self._options['error_color'])
  103. if self._options.get('label_bg_color'):
  104. self.label.SetBackgroundColour(self._options.get('label_bg_color'))
  105. if self._options.get('help_bg_color'):
  106. self.help_text.SetBackgroundColour(self._options.get('help_bg_color'))
  107. if self._options.get('error_bg_color'):
  108. self.error.SetBackgroundColour(self._options.get('error_bg_color'))
  109. def getWidget(self, *args, **options):
  110. return self.widget_class(*args, **options)
  111. def getWidgetValue(self):
  112. raise NotImplementedError
  113. def getSublayout(self, *args, **kwargs):
  114. layout = wx.BoxSizer(wx.HORIZONTAL)
  115. layout.Add(self.widget, 1, wx.EXPAND)
  116. return layout
  117. def onSize(self, event):
  118. # print(self.GetSize())
  119. # self.error.Wrap(self.GetSize().width)
  120. # self.help_text.Wrap(500)
  121. # self.Layout()
  122. event.Skip()
  123. def getValue(self):
  124. userValidator = getin(self._options, ['validator', 'test'], 'True')
  125. message = getin(self._options, ['validator', 'message'], '')
  126. testFunc = eval('lambda user_input: bool(%s)' % userValidator)
  127. satisfies = testFunc if self._meta['required'] else ifPresent(testFunc)
  128. value = self.getWidgetValue()
  129. return {
  130. 'id': self._id,
  131. 'cmd': self.formatOutput(self._meta, value),
  132. 'rawValue': value,
  133. 'test': runValidator(satisfies, value),
  134. 'error': None if runValidator(satisfies, value) else message,
  135. 'clitype': 'positional'
  136. if self._meta['required'] and not self._meta['commands']
  137. else 'optional'
  138. }
  139. def setValue(self, value):
  140. self.widget.SetValue(value)
  141. def setErrorString(self, message):
  142. self.error.SetLabel(message)
  143. self.error.Wrap(self.Size.width)
  144. self.Layout()
  145. def showErrorString(self, b):
  146. self.error.Wrap(self.Size.width)
  147. self.error.Show(b)
  148. def setOptions(self, values):
  149. return None
  150. def receiveChange(self, metatdata, value):
  151. raise NotImplementedError
  152. def dispatchChange(self, value, **kwargs):
  153. raise NotImplementedError
  154. def formatOutput(self, metadata, value):
  155. raise NotImplementedError
  156. class BaseChooser(TextContainer):
  157. """ Base Class for the Chooser widget types """
  158. def setValue(self, value):
  159. self.widget.setValue(value)
  160. def getWidgetValue(self):
  161. return self.widget.getValue()
  162. def formatOutput(self, metatdata, value):
  163. return formatters.general(metatdata, value)