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.

327 lines
8.7 KiB

  1. '''
  2. Created on Jan 1, 2014
  3. @author: Chris
  4. TODO:
  5. Sanitize all GetValue inputs
  6. (to check that there's actual data there.
  7. '''
  8. import wx
  9. from abc import ABCMeta
  10. from abc import abstractmethod
  11. from gooey.gui import styling
  12. EMPTY = ''
  13. class BuildException(RuntimeError):
  14. pass
  15. class AbstractComponent(object):
  16. '''
  17. Template pattern-y abstract class for the gui.
  18. Children must all implement the BuildWidget and getValue
  19. methods.
  20. '''
  21. __metaclass__ = ABCMeta
  22. def __init__(self):
  23. self._widget = None
  24. self.msg = EMPTY
  25. def Build(self, parent):
  26. self._widget = self.BuildInputWidget(parent, self._action)
  27. if self.HasHelpMsg(self._action):
  28. self._msg = self.CreateHelpMsgWidget(parent, self._action)
  29. else:
  30. self._msg = None
  31. sizer = wx.BoxSizer(wx.VERTICAL)
  32. sizer.Add(self.CreateNameLabelWidget(parent, self._action))
  33. sizer.AddSpacer(2)
  34. if self._msg:
  35. sizer.Add(self._msg, 0, wx.EXPAND)
  36. sizer.AddSpacer(2)
  37. else:
  38. sizer.AddStretchSpacer(1)
  39. sizer.AddStretchSpacer(1)
  40. sizer.Add(self._widget, 0, wx.EXPAND)
  41. return sizer
  42. @abstractmethod
  43. def BuildInputWidget(self, parent, action):
  44. ''' Must construct the main widget type for the Action '''
  45. pass
  46. def HasHelpMsg(self, action):
  47. return action.help is not None
  48. def CreateHelpMsgWidget(self, parent, action):
  49. base_text = wx.StaticText(parent, label=action.help)
  50. if self.HasNargs(action):
  51. base_text.SetLabelText(base_text.GetLabelText() + self.CreateNargsMsg(action))
  52. styling.MakeDarkGrey(base_text)
  53. return base_text
  54. def HasNargs(self, action):
  55. return action.nargs == '+' or action.nargs == '?'
  56. def CreateNargsMsg(self, action):
  57. return ' (Note: at least 1 or more arguments are required)'
  58. def CreateNameLabelWidget(self, parent, action):
  59. label = str(action.dest).title()
  60. if len(action.option_strings) > 1:
  61. label += ' (%s)' % action.option_strings[0]
  62. text = wx.StaticText(parent, label=label)
  63. styling.MakeBold(text)
  64. return text
  65. def AssertInitialization(self, clsname):
  66. if not self._widget:
  67. raise BuildException('%s was not correctly initialized' % clsname)
  68. def __str__(self):
  69. return str(self._action)
  70. @abstractmethod
  71. def GetValue(self):
  72. ''' Returns the state of the given widget '''
  73. pass
  74. def Update(self, size):
  75. '''
  76. Manually word wraps the StaticText help objects which would
  77. otherwise not wrap on resize
  78. Content area is based on each grid having two equally sized
  79. columns, where the content area is defined as 87% of the halved
  80. window width. The wiggle room is the distance +- 10% of the
  81. content_area.
  82. Wrap calculation is run only when the size of the help_msg
  83. extends outside of the wiggle_room. This was done to avoid
  84. the "flickering" that comes from constantly resizing a
  85. StaticText object.
  86. '''
  87. if self._msg is None:
  88. return
  89. help_msg = self._msg
  90. width, height = size
  91. content_area = int((width / 2) * .87)
  92. print 'wiget size', help_msg.Size[0]
  93. wiggle_room = range(int(content_area - content_area * .05), int(content_area + content_area * .05))
  94. print '(', int(content_area - content_area * .05), ' -> ', int(content_area + content_area * .05), ')'
  95. if help_msg.Size[0] not in wiggle_room:
  96. self._msg.SetLabel(self._msg.GetLabelText().replace('\n', ' '))
  97. self._msg.Wrap(content_area)
  98. class Positional(AbstractComponent):
  99. """
  100. Represents a positional argument in a program
  101. e.g.
  102. mypyfile.py param1 <-- this guy
  103. """
  104. def __init__(self, action):
  105. self._action = action
  106. self._widget = None
  107. self.contents = None
  108. def BuildInputWidget(self, parent, action):
  109. return wx.TextCtrl(parent)
  110. def GetValue(self):
  111. '''
  112. Positionals have no associated options_string,
  113. so only the supplied arguments are returned.
  114. The order is assumed to be the same as the order
  115. of declaration in the client code
  116. Returns
  117. "argument_value"
  118. '''
  119. self.AssertInitialization('Positional')
  120. if str(self._widget.GetValue()) == EMPTY:
  121. return None
  122. return self._widget.GetValue()
  123. class Choice(AbstractComponent):
  124. """ A dropdown box """
  125. _DEFAULT_VALUE = 'Select Option'
  126. def __init__(self, action):
  127. self._action = action
  128. self._widget = None
  129. self.contents = None
  130. def GetValue(self):
  131. '''
  132. Returns
  133. "--option_name argument"
  134. '''
  135. self.AssertInitialization('Choice')
  136. if self._widget.GetValue() == self._DEFAULT_VALUE:
  137. return None
  138. return ' '.join(
  139. [self._action.option_strings[0], # get the verbose copy if available
  140. self._widget.GetValue()])
  141. def BuildInputWidget(self, parent, action):
  142. return wx.ComboBox(
  143. parent=parent,
  144. id=-1,
  145. value=self._DEFAULT_VALUE,
  146. choices=action.choices,
  147. style=wx.CB_DROPDOWN
  148. )
  149. class Optional(AbstractComponent):
  150. def __init__(self, action):
  151. self._action = action
  152. self._widget = None
  153. self.contents = None
  154. def BuildInputWidget(self, parent, action):
  155. return wx.TextCtrl(parent)
  156. def GetValue(self):
  157. '''
  158. General options are key/value style pairs (conceptually).
  159. Thus the name of the option, as well as the argument to it
  160. are returned
  161. e.g.
  162. >>> myscript --outfile myfile.txt
  163. returns
  164. "--Option Value"
  165. '''
  166. self.AssertInitialization('Optional')
  167. value = self._widget.GetValue()
  168. if not value:
  169. return None
  170. return ' '.join(
  171. [self._action.option_strings[0], # get the verbose copy if available
  172. value])
  173. class Flag(AbstractComponent):
  174. def __init__(self, action):
  175. self._action = action
  176. self._widget = None
  177. self.contents = None
  178. def Build(self, parent):
  179. self._widget = self.BuildInputWidget(parent, self._action)
  180. self._msg = (self.CreateHelpMsgWidget(parent, self._action)
  181. if self.HasHelpMsg(self._action)
  182. else None)
  183. sizer = wx.BoxSizer(wx.VERTICAL)
  184. sizer.Add(self.CreateNameLabelWidget(parent, self._action))
  185. sizer.AddSpacer(6)
  186. if self.HasNargs(self._action):
  187. sizer.Add(self.CreateNargsMsg(parent, self._action))
  188. if self._msg:
  189. hsizer = self.buildHorizonalMsgSizer(parent)
  190. sizer.Add(hsizer, 1, wx.EXPAND)
  191. else:
  192. sizer.AddStretchSpacer(1)
  193. sizer.Add(self._widget, 0, wx.EXPAND)
  194. return sizer
  195. def BuildInputWidget(self, parent, action):
  196. return wx.CheckBox(parent, -1, label='')
  197. def buildHorizonalMsgSizer(self, panel):
  198. if not self._msg:
  199. return None
  200. sizer = wx.BoxSizer(wx.HORIZONTAL)
  201. sizer.Add(self._widget, 0)
  202. sizer.AddSpacer(6)
  203. sizer.Add(self._msg, 1, wx.EXPAND)
  204. return sizer
  205. def GetValue(self):
  206. '''
  207. Flag options have no param associated with them.
  208. Thus we only need the name of the option.
  209. e.g
  210. >>> Python -v myscript
  211. returns
  212. Options name for argument (-v)
  213. '''
  214. if self._widget.GetValue():
  215. return self._action.option_strings[0]
  216. def Update(self, size):
  217. '''
  218. Custom wrapper calculator to account for the
  219. increased size of the _msg widget after being
  220. inlined with the wx.CheckBox
  221. '''
  222. if self._msg is None:
  223. return
  224. help_msg = self._msg
  225. width, height = size
  226. content_area = int((width / 3) * .70)
  227. wiggle_room = range(int(content_area - content_area * .05), int(content_area + content_area * .05))
  228. if help_msg.Size[0] not in wiggle_room:
  229. self._msg.SetLabel(self._msg.GetLabelText().replace('\n', ' '))
  230. self._msg.Wrap(content_area)
  231. class Counter(AbstractComponent):
  232. def __init__(self, action):
  233. self._action = action
  234. self._widget = None
  235. self.contents = None
  236. def BuildInputWidget(self, parent, action):
  237. levels = [str(x) for x in range(1, 7)]
  238. return wx.ComboBox(
  239. parent=parent,
  240. id=-1,
  241. value='',
  242. choices=levels,
  243. style=wx.CB_DROPDOWN
  244. )
  245. def GetValue(self):
  246. '''
  247. NOTE: Added on plane. Cannot remember exact implementation
  248. of counter objects. I believe that they count sequentail
  249. pairings of options
  250. e.g.
  251. -vvvvv
  252. But I'm not sure. That's what I'm going with for now.
  253. Returns
  254. str(action.options_string[0]) * DropDown Value
  255. '''
  256. dropdown_value = self._widget.GetValue()
  257. if not str(dropdown_value).isdigit():
  258. return None
  259. arg = str(self._action.option_strings[0]).replace('-', '')
  260. repeated_args = arg * int(dropdown_value)
  261. return '-' + repeated_args
  262. if __name__ == '__main__':
  263. pass