optparse_gui.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. # -*- coding: utf-8 -*-
  2. '''
  3. A drop-in replacement for optparse ("import optparse_gui as optparse")
  4. Provides an identical interface to optparse(.OptionParser),
  5. But displays an automatically generated wx dialog in order to enter the
  6. options/args, instead of parsing command line arguments
  7. Classes:
  8. * :class:`OptparseDialog`
  9. * :class:`UserCancelledError`
  10. * :class:`Option`
  11. * :class:`OptionParser`
  12. Functions:
  13. * :func:`checkLabel`
  14. '''
  15. # python 2 and 3 compatibility
  16. from builtins import dict
  17. import os
  18. import sys
  19. import optparse
  20. try:
  21. import wx
  22. import wx.lib.filebrowsebutton as filebrowse
  23. except:
  24. pass
  25. __version__ = 0.2
  26. __revision__ = '$Id$'
  27. # for required options
  28. from .optparse_required import STREQUIRED
  29. TEXTCTRL_SIZE = (400, -1)
  30. def checkLabel(option):
  31. """Create the label for an option, it add the required string if needed
  32. :param option: and Option object
  33. """
  34. label = option.dest
  35. label = label.replace('_', ' ')
  36. label = label.replace('--', '')
  37. label = label.capitalize()
  38. if option.required is True:
  39. return "{lab} [{req}]".format(lab=label, req=STREQUIRED)
  40. else:
  41. return label
  42. class OptparseDialog(wx.Dialog):
  43. '''The dialog presented to the user with dynamically generated controls,
  44. to fill in the required options.
  45. :param optParser: the optparse object
  46. :param str title: the title to add in the GUI
  47. :param parent: the parent GUI
  48. :param int ID: the ID of GUI
  49. :param pos: the position of GUI
  50. :param size: the dimension of GUI
  51. :param style: the style of GUI
  52. Based on the wx.Dialog sample from wx Docs & Demos
  53. '''
  54. def __init__(self, optParser, title, parent=None, ID=0,
  55. pos=wx.DefaultPosition, size=wx.DefaultSize,
  56. style=wx.DEFAULT_DIALOG_STYLE|wx.THICK_FRAME):
  57. """Function for initialization"""
  58. # TODO fix icon
  59. # modis_icon = wx.Icon('/home/lucadelu/github/pyModis/pyModis.ico',
  60. # wx.BITMAP_TYPE_ICO)
  61. # self.SetIcon(modis_icon)
  62. provider = wx.SimpleHelpProvider()
  63. wx.HelpProvider_Set(provider)
  64. pre = wx.PreDialog()
  65. pre.SetExtraStyle(wx.DIALOG_EX_CONTEXTHELP)
  66. pre.Create(parent, ID, title, pos, size, style)
  67. self.PostCreate(pre)
  68. self.CenterOnScreen()
  69. sizer = wx.BoxSizer(wx.VERTICAL)
  70. self.args_ctrl = []
  71. self.option_controls = dict()
  72. # IN THE TOP OF GUI THERE WAS THE NAME OF THE SCRIPT, BUT NOW IT IS IN
  73. # THE TITLE
  74. # top_label_text = '%s %s' % (optParser.get_prog_name(),
  75. # optParser.get_version())
  76. # label = wx.StaticText(self, -1, top_label_text)
  77. # sizer.Add(label, 0, wx.ALIGN_CENTRE | wx.ALL, 5)
  78. # Add a text control for entering args
  79. arg = self._checkArg(optParser.get_prog_name())
  80. sizer.Add(arg, 0, wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.RIGHT |
  81. wx.TOP, 5)
  82. self.browse_option_map = dict()
  83. # Add controls for all the options
  84. for option in optParser.list_of_option:
  85. if option.dest is None:
  86. continue
  87. if option.help is None:
  88. option.help = ''
  89. if checkLabel(option) == 'Formats':
  90. continue
  91. box = wx.BoxSizer(wx.HORIZONTAL)
  92. if 'store' == option.action:
  93. label = wx.StaticText(self, -1, checkLabel(option))
  94. label.SetHelpText(option.help.replace(' [default=%default]',
  95. ''))
  96. box.Add(label, 0, wx.ALIGN_CENTRE | wx.ALL, 5)
  97. if 'choice' == option.type:
  98. choices = list(set(option.choices))
  99. if optparse.NO_DEFAULT == option.default:
  100. option.default = choices[0]
  101. ctrl = wx.ComboBox(self, -1, choices=choices,
  102. value=option.default,
  103. size=TEXTCTRL_SIZE,
  104. style=wx.CB_DROPDOWN | wx.CB_READONLY |
  105. wx.CB_SORT)
  106. elif option.type in ['file', 'output']:
  107. if option.type == 'file':
  108. fmode = wx.OPEN
  109. elif option.type == 'output':
  110. fmode = wx.SAVE
  111. ctrl = filebrowse.FileBrowseButton(self, id=wx.ID_ANY,
  112. fileMask='*',
  113. labelText='',
  114. dialogTitle='Choose output file',
  115. buttonText='Browse',
  116. startDirectory=os.getcwd(),
  117. fileMode=fmode,
  118. size=TEXTCTRL_SIZE)
  119. elif option.type == 'directory':
  120. ctrl = filebrowse.DirBrowseButton(self, id=wx.ID_ANY,
  121. labelText='',
  122. dialogTitle='Choose output file',
  123. buttonText='Browse',
  124. startDirectory=os.getcwd(),
  125. size=TEXTCTRL_SIZE)
  126. else:
  127. if 'MULTILINE' in option.help:
  128. ctrl = wx.TextCtrl(self, -1, "", size=(400, 100),
  129. style=wx.TE_MULTILINE |
  130. wx.TE_PROCESS_ENTER)
  131. else:
  132. ctrl = wx.TextCtrl(self, -1, "", size=TEXTCTRL_SIZE)
  133. if (option.default != optparse.NO_DEFAULT) and \
  134. (option.default is not None):
  135. ctrl.Value = str(option.default)
  136. box.Add(ctrl, 1, wx.ALIGN_RIGHT | wx.ALL, 5)
  137. elif option.action in ('store_true', 'store_false'):
  138. ctrl = wx.CheckBox(self, -1, checkLabel(option),
  139. size=(300, -1))
  140. box.Add(ctrl, 0, wx.ALIGN_CENTRE | wx.ALL, 5)
  141. elif option.action == 'group_name':
  142. label = wx.StaticText(self, -1, checkLabel(option))
  143. font = wx.Font(12, wx.DECORATIVE, wx.NORMAL, wx.BOLD)
  144. label.SetFont(font)
  145. box.Add(label, 0, wx.ALIGN_CENTRE | wx.ALL, 5)
  146. ctrl = None
  147. else:
  148. raise NotImplementedError('Unknown option action: '
  149. '{act}'.format(act=repr(option.action)))
  150. if ctrl:
  151. ctrl.SetHelpText(option.help.replace(' [default=%default]',
  152. ''))
  153. self.option_controls[option] = ctrl
  154. sizer.Add(box, 0, wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
  155. line = wx.StaticLine(self, -1, size=(20, -1), style=wx.LI_HORIZONTAL)
  156. sizer.Add(line, 0,
  157. wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.TOP, 5)
  158. btnsizer = wx.StdDialogButtonSizer()
  159. if wx.Platform != "__WXMSW__":
  160. btn = wx.ContextHelpButton(self)
  161. btnsizer.AddButton(btn)
  162. btn = wx.Button(self, wx.ID_OK)
  163. btn.SetHelpText("The OK button completes the dialog")
  164. btn.SetDefault()
  165. btnsizer.AddButton(btn)
  166. btn = wx.Button(self, wx.ID_CANCEL)
  167. btn.SetHelpText("The Cancel button cancels the dialog. (Cool, huh?)")
  168. btnsizer.AddButton(btn)
  169. btnsizer.Realize()
  170. sizer.Add(btnsizer, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
  171. self.SetSizer(sizer)
  172. sizer.Fit(self)
  173. def _getOptions(self):
  174. """Return a dictionary with the options and their values"""
  175. option_values = dict()
  176. for option, ctrl in self.option_controls.items():
  177. try:
  178. option_values[option] = ctrl.Value
  179. except:
  180. option_values[option] = ctrl.GetValue()
  181. return option_values
  182. def _checkArg(self, name):
  183. """Create an option in WX
  184. :param str name: the name of command to parse
  185. """
  186. sizer = wx.BoxSizer(wx.HORIZONTAL)
  187. # check what is the module
  188. if name == 'modis_convert.py' or name == 'modis_parse.py' or \
  189. name == 'modis_quality.py':
  190. ltext = 'File HDF [%s]' % STREQUIRED
  191. self.htext = 'Select HDF file'
  192. self.typecont = 'file'
  193. elif name == 'modis_download.py' or \
  194. name == 'modis_download_from_list.py':
  195. ltext = 'Destination Folder [%s]' % STREQUIRED
  196. self.htext = 'Select directory where save MODIS files'
  197. self.typecont = 'dir'
  198. elif name == 'modis_mosaic.py':
  199. ltext = 'File containig HDF list [%s]' % STREQUIRED
  200. self.htext = 'Select file containig a list of MODIS file'
  201. self.typecont = 'file'
  202. elif name == 'modis_multiparse.py':
  203. ltext = 'List of HDF file [%s]' % STREQUIRED
  204. self.htext = 'List of MODIS files'
  205. self.typecont = 'mfile'
  206. else:
  207. ltext = 'Test'
  208. self.htext = 'Test'
  209. label = wx.StaticText(self, -1, ltext)
  210. label.SetHelpText(self.htext)
  211. sizer.Add(item=label, flag=wx.ALIGN_LEFT | wx.ALIGN_CENTRE_VERTICAL |
  212. wx.ALL, border=5)
  213. if self.typecont == 'dir':
  214. self.arg_ctrl = filebrowse.DirBrowseButton(self, id=wx.ID_ANY,
  215. labelText='',
  216. dialogTitle=self.htext,
  217. buttonText='Browse',
  218. startDirectory=os.getcwd(),
  219. changeCallback=self.onText,
  220. size=TEXTCTRL_SIZE)
  221. elif self.typecont in ['file', 'mfile']:
  222. self.arg_ctrl = filebrowse.FileBrowseButton(self, id=wx.ID_ANY,
  223. fileMask='*',
  224. labelText='',
  225. dialogTitle=self.htext,
  226. buttonText='Browse',
  227. startDirectory=os.getcwd(),
  228. fileMode=wx.OPEN,
  229. changeCallback=self.onText,
  230. size=TEXTCTRL_SIZE)
  231. sizer.Add(item=self.arg_ctrl, flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
  232. return sizer
  233. def onText(self, event):
  234. """File changed"""
  235. myId = event.GetId()
  236. me = wx.FindWindowById(myId)
  237. wktfile = me.GetValue()
  238. if len(wktfile) > 0:
  239. if self.typecont == 'mfile':
  240. self.args_ctrl.append(wktfile.split(','))
  241. else:
  242. self.args_ctrl.append(wktfile)
  243. event.Skip()
  244. def onBrowse(self, event):
  245. """Choose file"""
  246. dlg = wx.FileDialog(parent=self, message=self.htext,
  247. defaultDir=os.getcwd(), style=wx.OPEN)
  248. if dlg.ShowModal() == wx.ID_OK:
  249. path = dlg.GetPath()
  250. self.args_ctrl.SetValue(path)
  251. dlg.Destroy()
  252. event.Skip()
  253. def getOptionsAndArgs(self):
  254. '''Parse the options and args
  255. :return: a dictionary of option names and values, a sequence of args
  256. '''
  257. option_values = self._getOptions()
  258. args = self.args_ctrl
  259. return option_values, args
  260. class UserCancelledError(Exception):
  261. """??"""
  262. pass
  263. class Option(optparse.Option):
  264. """Extended optparse.Option class"""
  265. SUPER = optparse.Option
  266. TYPES = SUPER.TYPES + ('file', 'output', 'directory', 'group_name')
  267. ACTIONS = SUPER.ACTIONS + ('group_name',)
  268. TYPED_ACTIONS = SUPER.TYPED_ACTIONS + ('group_name',)
  269. # for required options
  270. ATTRS = optparse.Option.ATTRS + [STREQUIRED]
  271. def __init__(self, *opts, **attrs):
  272. if attrs.get(STREQUIRED, False):
  273. attrs['help'] = '(Required) ' + attrs.get('help', "")
  274. optparse.Option.__init__(self, *opts, **attrs)
  275. class OptionParser(optparse.OptionParser):
  276. """Extended optparse.OptionParser to create the GUI for the module"""
  277. SUPER = optparse.OptionParser
  278. def __init__(self, *args, **kwargs):
  279. """Function to initialize the object"""
  280. if wx.GetApp() is None:
  281. self.app = wx.App(False)
  282. if 'option_class' not in kwargs:
  283. kwargs['option_class'] = Option
  284. self.SUPER.__init__(self, *args, **kwargs)
  285. def parse_args(self, args=None, values=None):
  286. '''This is the heart of it all overrides
  287. optparse.OptionParser.parse_args
  288. :param arg: is irrelevant and thus ignored, it's here only for
  289. interface compatibility
  290. :param values: is irrelevant and thus ignored, it's here only for
  291. interface compatibility
  292. '''
  293. # preprocess command line arguments and set to defaults
  294. option_values, args = self.SUPER.parse_args(self, args, values)
  295. self.list_of_option = self.option_list
  296. for group in self.option_groups:
  297. title = "--{n}".format(n=group.title.replace(" ", "_"))
  298. o = Option(title, type='group_name', dest=title, help=title,
  299. metavar=title, action='group_name')
  300. self.list_of_option.append(o)
  301. for option in group.option_list:
  302. self.list_of_option.append(option)
  303. for option in self.list_of_option:
  304. if option.dest and hasattr(option_values, option.dest):
  305. default = getattr(option_values, option.dest)
  306. if default is not None:
  307. option.default = default
  308. dlg = OptparseDialog(optParser=self,
  309. title="{name} GUI".format(name=self.description))
  310. if args:
  311. dlg.args_ctrl.Value = ' '.join(args)
  312. dlg_result = dlg.ShowModal()
  313. if wx.ID_OK != dlg_result:
  314. sys.exit()
  315. if values is None:
  316. values = self.get_default_values()
  317. option_values, args = dlg.getOptionsAndArgs()
  318. for option, value in option_values.items():
  319. if option.required and value == "":
  320. self.error("The option %s is mandatory" % option)
  321. if ('store_true' == option.action) and (value is False):
  322. setattr(values, option.dest, False)
  323. continue
  324. if ('store_false' == option.action) and (value is True):
  325. setattr(values, option.dest, False)
  326. continue
  327. if option.takes_value() is False:
  328. value = None
  329. if isinstance(value, str):
  330. value = str(value)
  331. option.process(option, value, values, self)
  332. return values, args
  333. def error(self, msg):
  334. """Return an error message with wx.MessageDialog
  335. :param str msg: is the error string to pass to message dialog
  336. """
  337. wx.MessageDialog(None, msg, 'Error!', wx.ICON_ERROR).ShowModal()
  338. sys.exit()