pyedit.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
  1. """GRASS GIS Simple Python Editor
  2. Copyright (C) 2016 by the GRASS Development Team
  3. This program is free software under the GNU General Public
  4. License (>=v2). Read the file COPYING that comes with GRASS GIS
  5. for details.
  6. :authors: Vaclav Petras
  7. :authors: Martin Landa
  8. """
  9. import sys
  10. import os
  11. import stat
  12. try:
  13. from StringIO import StringIO
  14. except ImportError:
  15. from io import StringIO
  16. import time
  17. import wx
  18. import grass.script as gscript
  19. from grass.script.utils import try_remove
  20. # needed just for testing
  21. if __name__ == "__main__":
  22. from grass.script.setup import set_gui_path
  23. set_gui_path()
  24. from core.gcmd import GError
  25. from gui_core.pystc import PyStc, SetDarkMode
  26. from core import globalvar
  27. from core.menutree import MenuTreeModelBuilder
  28. from gui_core.menu import RecentFilesMenu, Menu
  29. from gui_core.toolbars import BaseToolbar, BaseIcons
  30. from gui_core.wrap import IsDark
  31. from icons.icon import MetaIcon
  32. from core.debug import Debug
  33. # TODO: add validation: call/import pep8 (error message if not available)
  34. # TODO: run with parameters (alternatively, just use console or GUI)
  35. # TODO: add more examples (better separate file)
  36. # TODO: add test for templates and examples
  37. # TODO: add pep8 test for templates and examples
  38. # TODO: add snippets?
  39. def script_template():
  40. """The most simple script which runs and gives something"""
  41. return r"""#!/usr/bin/env python3
  42. import grass.script as gs
  43. def main():
  44. gs.run_command('g.region', flags='p')
  45. if __name__ == '__main__':
  46. main()
  47. """
  48. def module_template():
  49. """Template from which to start writing GRASS module"""
  50. import getpass
  51. author = getpass.getuser()
  52. properties = {}
  53. properties["name"] = "module name"
  54. properties["author"] = author
  55. properties["description"] = "Module description"
  56. output = StringIO()
  57. # header
  58. output.write(
  59. r"""#!/usr/bin/env python3
  60. #
  61. #%s
  62. #
  63. # MODULE: %s
  64. #
  65. # AUTHOR(S): %s
  66. #
  67. # PURPOSE: %s
  68. #
  69. # DATE: %s
  70. #
  71. #%s
  72. """
  73. % (
  74. "#" * 72,
  75. properties["name"],
  76. properties["author"],
  77. "\n# ".join(properties["description"].splitlines()),
  78. time.asctime(),
  79. "#" * 72,
  80. )
  81. )
  82. # UI
  83. output.write(
  84. r"""
  85. # %%module
  86. # %% description: %s
  87. # %%end
  88. """
  89. % (" ".join(properties["description"].splitlines()))
  90. )
  91. # import modules
  92. output.write(
  93. r"""
  94. import sys
  95. import os
  96. import atexit
  97. import grass.script as gs
  98. """
  99. )
  100. # cleanup()
  101. output.write(
  102. r"""
  103. RAST_REMOVE = []
  104. def cleanup():
  105. """
  106. )
  107. output.write(
  108. r""" gs.run_command('g.remove', flags='f', type='raster',
  109. name=RAST_REMOVE)
  110. """
  111. )
  112. output.write("\ndef main():\n")
  113. output.write(
  114. r""" options, flags = gs.parser()
  115. gs.run_command('g.remove', flags='f', type='raster',
  116. name=RAST_REMOVE)
  117. """
  118. )
  119. output.write("\n return 0\n")
  120. output.write(
  121. r"""
  122. if __name__ == "__main__":
  123. atexit.register(cleanup)
  124. sys.exit(main())
  125. """
  126. )
  127. return output.getvalue()
  128. def script_example():
  129. """Example of a simple script"""
  130. return r"""#!/usr/bin/env python3
  131. import grass.script as gs
  132. def main():
  133. input_raster = 'elevation'
  134. output_raster = 'high_areas'
  135. stats = gs.parse_command('r.univar', map='elevation', flags='g')
  136. raster_mean = float(stats['mean'])
  137. raster_stddev = float(stats['stddev'])
  138. raster_high = raster_mean + raster_stddev
  139. gs.mapcalc('{r} = {a} > {m}'.format(r=output_raster, a=input_raster,
  140. m=raster_high))
  141. if __name__ == "__main__":
  142. main()
  143. """
  144. def module_example():
  145. """Example of a GRASS module"""
  146. return r"""#!/usr/bin/env python3
  147. # %module
  148. # % description: Adds the values of two rasters (A + B)
  149. # % keyword: raster
  150. # % keyword: algebra
  151. # % keyword: sum
  152. # %end
  153. # %option G_OPT_R_INPUT
  154. # % key: araster
  155. # % description: Name of input raster A in an expression A + B
  156. # %end
  157. # %option G_OPT_R_INPUT
  158. # % key: braster
  159. # % description: Name of input raster B in an expression A + B
  160. # %end
  161. # %option G_OPT_R_OUTPUT
  162. # %end
  163. import sys
  164. import grass.script as gs
  165. def main():
  166. options, flags = gs.parser()
  167. araster = options['araster']
  168. braster = options['braster']
  169. output = options['output']
  170. gs.mapcalc('{r} = {a} + {b}'.format(r=output, a=araster, b=braster))
  171. return 0
  172. if __name__ == "__main__":
  173. sys.exit(main())
  174. """
  175. def module_error_handling_example():
  176. """Example of a GRASS module"""
  177. return r"""#!/usr/bin/env python3
  178. # %module
  179. # % description: Selects values from raster above value of mean plus standard deviation
  180. # % keyword: raster
  181. # % keyword: select
  182. # % keyword: standard deviation
  183. # %end
  184. # %option G_OPT_R_INPUT
  185. # %end
  186. # %option G_OPT_R_OUTPUT
  187. # %end
  188. import sys
  189. import grass.script as gs
  190. from grass.exceptions import CalledModuleError
  191. def main():
  192. options, flags = gs.parser()
  193. input_raster = options['input']
  194. output_raster = options['output']
  195. try:
  196. stats = gs.parse_command('r.univar', map=input_raster, flags='g')
  197. except CalledModuleError as e:
  198. gs.fatal('{0}'.format(e))
  199. raster_mean = float(stats['mean'])
  200. raster_stddev = float(stats['stddev'])
  201. raster_high = raster_mean + raster_stddev
  202. gs.mapcalc('{r} = {i} > {v}'.format(r=output_raster, i=input_raster,
  203. v=raster_high))
  204. return 0
  205. if __name__ == "__main__":
  206. sys.exit(main())
  207. """
  208. def open_url(url):
  209. import webbrowser
  210. webbrowser.open(url)
  211. class PyEditController(object):
  212. # using the naming GUI convention, change for controller?
  213. # pylint: disable=invalid-name
  214. def __init__(self, panel, guiparent, giface):
  215. """Simple editor, this class could be a pure controller"""
  216. self.guiparent = guiparent
  217. self.giface = giface
  218. self.body = panel
  219. self.filename = None
  220. self.tempfile = None # bool, make them strings for better code
  221. self.overwrite = False
  222. self.parameters = None
  223. # Get first (File) menu
  224. menu = guiparent.menubar.GetMenu(0)
  225. self.recent_files = RecentFilesMenu(
  226. app_name="pyedit",
  227. parent_menu=menu,
  228. pos=1,
  229. ) # pos=1 recent files menu position (index) in the parent (File) menu
  230. self.recent_files.file_requested.connect(self.OpenRecentFile)
  231. def _openFile(self, file_path):
  232. """Try open file and read content
  233. :param str file_path: file path
  234. :return str or None: file content or None
  235. """
  236. try:
  237. with open(file_path, "r") as f:
  238. content = f.read()
  239. return content
  240. except PermissionError:
  241. GError(
  242. message=_(
  243. "Permission denied <{}>. Please change file "
  244. "permission for reading.".format(file_path)
  245. ),
  246. parent=self.guiparent,
  247. showTraceback=False,
  248. )
  249. except IOError:
  250. GError(
  251. message=_("Couldn't read file <{}>.".format(file_path)),
  252. parent=self.guiparent,
  253. )
  254. def _writeFile(self, file_path, content, additional_err_message=""):
  255. """Try open file and write content
  256. :param str file_path: file path
  257. :param str content: content written to the file
  258. :param str additional_err_message: additional error message
  259. :return None or True: file written or None
  260. """
  261. try:
  262. with open(file_path, "w") as f:
  263. f.write(content)
  264. return True
  265. except PermissionError:
  266. GError(
  267. message=_(
  268. "Permission denied <{}>. Please change file "
  269. "permission for writting.{}".format(
  270. file_path,
  271. additional_err_message,
  272. ),
  273. ),
  274. parent=self.guiparent,
  275. showTraceback=False,
  276. )
  277. except IOError:
  278. GError(
  279. message=_(
  280. "Couldn't write file <{}>.{}".format(
  281. file_path, additional_err_message
  282. ),
  283. ),
  284. parent=self.guiparent,
  285. )
  286. def OnRun(self, event):
  287. """Run Python script"""
  288. if not self.filename:
  289. self.filename = gscript.tempfile() + ".py"
  290. self.tempfile = True
  291. file_is_written = self._writeFile(
  292. file_path=self.filename,
  293. content=self.body.GetText(),
  294. additional_err_message=" Unable to launch Python script.",
  295. )
  296. if file_is_written:
  297. mode = stat.S_IMODE(os.lstat(self.filename)[stat.ST_MODE])
  298. os.chmod(self.filename, mode | stat.S_IXUSR)
  299. else:
  300. # always save automatically before running
  301. file_is_written = self._writeFile(
  302. file_path=self.filename,
  303. content=self.body.GetText(),
  304. additional_err_message=" Unable to launch Python script.",
  305. )
  306. if file_is_written:
  307. # set executable file
  308. # (not sure if needed every time but useful for opened files)
  309. os.chmod(self.filename, stat.S_IRWXU | stat.S_IWUSR)
  310. if file_is_written:
  311. # run in console as other modules, avoid Python shell which
  312. # carries variables over to the next execution
  313. env = os.environ.copy()
  314. if self.overwrite:
  315. env["GRASS_OVERWRITE"] = "1"
  316. cmd = [self.filename]
  317. if self.parameters:
  318. cmd.extend(self.parameters)
  319. self.giface.RunCmd(cmd, env=env)
  320. def SaveAs(self):
  321. """Save python script to file"""
  322. if self.tempfile:
  323. try_remove(self.filename)
  324. self.tempfile = False
  325. filename = None
  326. dlg = wx.FileDialog(
  327. parent=self.guiparent,
  328. message=_("Choose file to save"),
  329. defaultDir=os.getcwd(),
  330. wildcard=_("Python script (*.py)|*.py"),
  331. style=wx.FD_SAVE,
  332. )
  333. if dlg.ShowModal() == wx.ID_OK:
  334. filename = dlg.GetPath()
  335. if not filename:
  336. return
  337. # check for extension
  338. if filename[-3:] != ".py":
  339. filename += ".py"
  340. if os.path.exists(filename):
  341. dlg = wx.MessageDialog(
  342. parent=self.guiparent,
  343. message=_(
  344. "File <%s> already exists. " "Do you want to overwrite this file?"
  345. )
  346. % filename,
  347. caption=_("Save file"),
  348. style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION,
  349. )
  350. if dlg.ShowModal() == wx.ID_NO:
  351. dlg.Destroy()
  352. return
  353. dlg.Destroy()
  354. self.filename = filename
  355. self.tempfile = False
  356. self.Save()
  357. def Save(self):
  358. """Save current content to a file and set executable permissions"""
  359. assert self.filename
  360. file_is_written = self._writeFile(
  361. file_path=self.filename,
  362. content=self.body.GetText(),
  363. )
  364. if file_is_written:
  365. # executable file
  366. os.chmod(self.filename, stat.S_IRWXU | stat.S_IWUSR)
  367. def OnSave(self, event):
  368. """Save python script to file
  369. Just save if file already specified, save as action otherwise.
  370. """
  371. if self.filename and not self.tempfile:
  372. self.Save()
  373. else:
  374. self.SaveAs()
  375. if self.filename:
  376. self.recent_files.AddFileToHistory(
  377. filename=self.filename,
  378. )
  379. def IsModified(self):
  380. """Check if python script has been modified"""
  381. return self.body.modified
  382. def Open(self):
  383. """Ask for a filename and load its content"""
  384. filename = ""
  385. dlg = wx.FileDialog(
  386. parent=self.guiparent,
  387. message=_("Open file"),
  388. defaultDir=os.getcwd(),
  389. wildcard=_("Python script (*.py)|*.py"),
  390. style=wx.FD_OPEN,
  391. )
  392. if dlg.ShowModal() == wx.ID_OK:
  393. filename = dlg.GetPath()
  394. if not filename:
  395. return
  396. content = self._openFile(file_path=filename)
  397. if content:
  398. self.body.SetText(content)
  399. else:
  400. return
  401. self.filename = filename
  402. self.tempfile = False
  403. def OnOpen(self, event):
  404. """Handle open event but ask about replacing content first"""
  405. if self.CanReplaceContent("file"):
  406. self.Open()
  407. if self.filename:
  408. self.recent_files.AddFileToHistory(
  409. filename=self.filename,
  410. )
  411. def OpenRecentFile(self, path, file_exists, file_history):
  412. """Try open recent file and read content
  413. :param str path: file path
  414. :param bool file_exists: file path exists
  415. :param bool file_history: file history obj instance
  416. :return: None
  417. """
  418. if not file_exists:
  419. GError(
  420. _(
  421. "File <{}> doesn't exist."
  422. "It was probably moved or deleted.".format(path)
  423. ),
  424. parent=self.guiparent,
  425. )
  426. else:
  427. if self.CanReplaceContent(by_message="file"):
  428. self.filename = path
  429. content = self._openFile(file_path=path)
  430. if content:
  431. self.body.SetText(content)
  432. file_history.AddFileToHistory(filename=path) # move up the list
  433. self.tempfile = False
  434. def IsEmpty(self):
  435. """Check if python script is empty"""
  436. return len(self.body.GetText()) == 0
  437. def IsContentValuable(self):
  438. """Check if content of the editor is valuable to user
  439. Used for example to check if content should be saved before closing.
  440. The content is not valuable for example if it already saved in a file.
  441. """
  442. Debug.msg(
  443. 2,
  444. "pyedit IsContentValuable? empty=%s, modified=%s"
  445. % (self.IsEmpty(), self.IsModified()),
  446. )
  447. return not self.IsEmpty() and self.IsModified()
  448. def SetScriptTemplate(self, event):
  449. if self.CanReplaceContent("template"):
  450. self.body.SetText(script_template())
  451. self.filename = None
  452. def SetModuleTemplate(self, event):
  453. if self.CanReplaceContent("template"):
  454. self.body.SetText(module_template())
  455. self.filename = None
  456. def SetScriptExample(self, event):
  457. if self.CanReplaceContent("example"):
  458. self.body.SetText(script_example())
  459. self.filename = None
  460. def SetModuleExample(self, event):
  461. if self.CanReplaceContent("example"):
  462. self.body.SetText(module_example())
  463. self.filename = None
  464. def SetModuleErrorHandlingExample(self, event):
  465. if self.CanReplaceContent("example"):
  466. self.body.SetText(module_error_handling_example())
  467. self.filename = None
  468. def CanReplaceContent(self, by_message):
  469. """Check with user if we can replace content by something else
  470. Asks user if replacement is OK depending on the state of the editor.
  471. Use before replacing all editor content by some other text.
  472. :param by_message: message used to ask user if it is OK to replace
  473. the content with something else; special values are 'template',
  474. 'example' and 'file' which will use predefined messages, otherwise
  475. a translatable, user visible string should be used.
  476. """
  477. if by_message == "template":
  478. message = _("Replace the content by the template?")
  479. elif by_message == "example":
  480. message = _("Replace the content by the example?")
  481. elif by_message == "file":
  482. message = _("Replace the current content by the file content?")
  483. else:
  484. message = by_message
  485. if self.IsContentValuable():
  486. dlg = wx.MessageDialog(
  487. parent=self.guiparent,
  488. message=message,
  489. caption=_("Replace content"),
  490. style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION,
  491. )
  492. if dlg.ShowModal() == wx.ID_NO:
  493. dlg.Destroy()
  494. return False
  495. dlg.Destroy()
  496. return True
  497. def OnSetParameters(self, event):
  498. """Handle setting CLI parameters for the script (asks for input)"""
  499. dlg = wx.TextEntryDialog(
  500. parent=self.guiparent,
  501. caption=_("Set parameters for the script"),
  502. message=_(
  503. "Specify command line parameters for the script separated by spaces:"
  504. ),
  505. )
  506. if self.parameters:
  507. dlg.SetValue(" ".join(self.parameters))
  508. # TODO: modality might not be needed here if we bind the events
  509. if dlg.ShowModal() == wx.ID_OK:
  510. text = dlg.GetValue().strip()
  511. # TODO: split in the same way as in console
  512. if text:
  513. self.parameters = text.split()
  514. else:
  515. self.parameters = None
  516. def OnHelp(self, event):
  517. # inspired by g.manual but simple not using GRASS_HTML_BROWSER
  518. # not using g.manual because it does not show
  519. entry = "libpython/script_intro.html"
  520. major, minor, patch = gscript.version()["version"].split(".")
  521. url = "https://grass.osgeo.org/grass%s%s/manuals/%s" % (major, minor, entry)
  522. open_url(url)
  523. def OnPythonHelp(self, event):
  524. url = "https://docs.python.org/%s/tutorial/" % sys.version_info[0]
  525. open_url(url)
  526. def OnModulesHelp(self, event):
  527. self.giface.Help("full_index")
  528. def OnSubmittingHelp(self, event):
  529. open_url("https://trac.osgeo.org/grass/wiki/Submitting/Python")
  530. def OnAddonsHelp(self, event):
  531. open_url("https://grass.osgeo.org/development/code-submission/")
  532. def OnSupport(self, event):
  533. open_url("https://grass.osgeo.org/support/")
  534. class PyEditToolbar(BaseToolbar):
  535. # GUI class
  536. # pylint: disable=too-many-ancestors
  537. # pylint: disable=too-many-public-methods
  538. """PyEdit toolbar"""
  539. def __init__(self, parent):
  540. BaseToolbar.__init__(self, parent)
  541. self.icons = {
  542. "open": MetaIcon(img="open", label=_("Open (Ctrl+O)")),
  543. "save": MetaIcon(img="save", label=_("Save (Ctrl+S)")),
  544. "run": MetaIcon(img="execute", label=_("Run (Ctrl+R)")),
  545. # TODO: better icons for overwrite modes
  546. "overwriteTrue": MetaIcon(img="locked", label=_("Activate overwrite")),
  547. "overwriteFalse": MetaIcon(img="unlocked", label=_("Deactive overwrite")),
  548. "quit": MetaIcon(img="quit", label=_("Quit Simple Python Editor")),
  549. }
  550. # workaround for http://trac.wxwidgets.org/ticket/13888
  551. if sys.platform == "darwin":
  552. parent.SetToolBar(self)
  553. self.InitToolbar(self._toolbarData())
  554. # realize the toolbar
  555. self.Realize()
  556. def _toolbarData(self):
  557. """Toolbar data"""
  558. return self._getToolbarData(
  559. (
  560. ("open", self.icons["open"], self.parent.OnOpen),
  561. ("save", self.icons["save"], self.parent.OnSave),
  562. (None,),
  563. ("run", self.icons["run"], self.parent.OnRun),
  564. (
  565. "overwrite",
  566. self.icons["overwriteTrue"],
  567. self.OnSetOverwrite,
  568. wx.ITEM_CHECK,
  569. ),
  570. (None,),
  571. ("help", BaseIcons["help"], self.parent.OnHelp),
  572. ("quit", self.icons["quit"], self.parent.OnClose),
  573. )
  574. )
  575. # TODO: add overwrite also to the menu and sync with toolbar
  576. def OnSetOverwrite(self, event):
  577. if self.GetToolState(self.overwrite):
  578. self.SetToolNormalBitmap(
  579. self.overwrite, self.icons["overwriteFalse"].GetBitmap()
  580. )
  581. self.SetToolShortHelp(
  582. self.overwrite, self.icons["overwriteFalse"].GetLabel()
  583. )
  584. self.parent.overwrite = True
  585. else:
  586. self.SetToolNormalBitmap(
  587. self.overwrite, self.icons["overwriteTrue"].GetBitmap()
  588. )
  589. self.SetToolShortHelp(
  590. self.overwrite, self.icons["overwriteTrue"].GetLabel()
  591. )
  592. self.parent.overwrite = False
  593. class PyEditFrame(wx.Frame):
  594. # GUI class and a lot of trampoline methods
  595. # pylint: disable=missing-docstring
  596. # pylint: disable=too-many-public-methods
  597. # pylint: disable=invalid-name
  598. def __init__(
  599. self, parent, giface, id=wx.ID_ANY, title=_("Simple Python Editor"), **kwargs
  600. ):
  601. wx.Frame.__init__(self, parent=parent, id=id, title=title, **kwargs)
  602. self.parent = parent
  603. filename = os.path.join(globalvar.WXGUIDIR, "xml", "menudata_pyedit.xml")
  604. self.menubar = Menu(
  605. parent=self, model=MenuTreeModelBuilder(filename).GetModel(separators=True)
  606. )
  607. self.SetMenuBar(self.menubar)
  608. self.toolbar = PyEditToolbar(parent=self)
  609. # workaround for http://trac.wxwidgets.org/ticket/13888
  610. # TODO: toolbar is set in toolbar and here
  611. if sys.platform != "darwin":
  612. self.SetToolBar(self.toolbar)
  613. self.panel = PyStc(parent=self)
  614. if IsDark():
  615. SetDarkMode(self.panel)
  616. self.controller = PyEditController(
  617. panel=self.panel, guiparent=self, giface=giface
  618. )
  619. # don't start with an empty page
  620. self.panel.SetText(script_template())
  621. sizer = wx.BoxSizer(wx.VERTICAL)
  622. sizer.Add(self.panel, proportion=1, flag=wx.EXPAND)
  623. sizer.Fit(self)
  624. sizer.SetSizeHints(self)
  625. self.SetSizer(sizer)
  626. self.Fit()
  627. self.SetAutoLayout(True)
  628. self.Layout()
  629. self.Bind(wx.EVT_CLOSE, self.OnClose)
  630. # TODO: it would be nice if we can pass the controller to the menu
  631. # might not be possible on the side of menu
  632. # here we use self and self.controller which might make it harder
  633. def OnOpen(self, *args, **kwargs):
  634. self.controller.OnOpen(*args, **kwargs)
  635. def OnSave(self, *args, **kwargs):
  636. self.controller.OnSave(*args, **kwargs)
  637. def OnClose(self, *args, **kwargs):
  638. # this will be often true because PyStc is using EVT_KEY_DOWN
  639. # to say if it was modified, not actual user change in text
  640. if self.controller.IsContentValuable():
  641. self.controller.OnSave(*args, **kwargs)
  642. self.Destroy()
  643. def OnRun(self, *args, **kwargs):
  644. # save without asking
  645. self.controller.OnRun(*args, **kwargs)
  646. def OnHelp(self, *args, **kwargs):
  647. self.controller.OnHelp(*args, **kwargs)
  648. def OnSimpleScriptTemplate(self, *args, **kwargs):
  649. self.controller.SetScriptTemplate(*args, **kwargs)
  650. def OnGrassModuleTemplate(self, *args, **kwargs):
  651. self.controller.SetModuleTemplate(*args, **kwargs)
  652. def OnSimpleScriptExample(self, *args, **kwargs):
  653. self.controller.SetScriptExample(*args, **kwargs)
  654. def OnGrassModuleExample(self, *args, **kwargs):
  655. self.controller.SetModuleExample(*args, **kwargs)
  656. def OnGrassModuleErrorHandlingExample(self, *args, **kwargs):
  657. self.controller.SetModuleErrorHandlingExample(*args, **kwargs)
  658. def OnPythonHelp(self, *args, **kwargs):
  659. self.controller.OnPythonHelp(*args, **kwargs)
  660. def OnModulesHelp(self, *args, **kwargs):
  661. self.controller.OnModulesHelp(*args, **kwargs)
  662. def OnSubmittingHelp(self, *args, **kwargs):
  663. self.controller.OnSubmittingHelp(*args, **kwargs)
  664. def OnAddonsHelp(self, *args, **kwargs):
  665. self.controller.OnAddonsHelp(*args, **kwargs)
  666. def OnSupport(self, *args, **kwargs):
  667. self.controller.OnSupport(*args, **kwargs)
  668. def _get_overwrite(self):
  669. return self.controller.overwrite
  670. def _set_overwrite(self, overwrite):
  671. self.controller.overwrite = overwrite
  672. overwrite = property(
  673. _get_overwrite, _set_overwrite, doc="Tells if overwrite should be used"
  674. )
  675. def OnSetParameters(self, *args, **kwargs):
  676. self.controller.OnSetParameters(*args, **kwargs)
  677. def main():
  678. """Test application (potentially useful as g.gui.pyedit)"""
  679. from core.giface import StandaloneGrassInterface
  680. app = wx.App()
  681. giface = StandaloneGrassInterface()
  682. simple_editor = PyEditFrame(parent=None, giface=giface)
  683. simple_editor.SetSize((600, 800))
  684. simple_editor.Show()
  685. app.MainLoop()
  686. if __name__ == "__main__":
  687. main()