mcalc_builder.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812
  1. """
  2. @package modules::mcalc_builder
  3. @brief Map calculator, GUI wrapper for r.mapcalc
  4. Classes:
  5. - mcalc_builder::MapCalcFrame
  6. (C) 2008, 2011-2013 by the GRASS Development Team
  7. This program is free software under the GNU General Public License
  8. (>=v2). Read the file COPYING that comes with GRASS for details.
  9. @author Michael Barton, Arizona State University
  10. @author Martin Landa <landa.martin gmail.com>
  11. @author Tim Michelsen (load/save expression)
  12. """
  13. import os
  14. import re
  15. import wx
  16. import grass.script as grass
  17. from core import globalvar
  18. from core.gcmd import GError, RunCommand
  19. from core.giface import StandaloneGrassInterface
  20. from gui_core.gselect import Select
  21. from gui_core.widgets import IntegerValidator
  22. from gui_core.wrap import (
  23. Button,
  24. ClearButton,
  25. CloseButton,
  26. TextCtrl,
  27. StaticText,
  28. StaticBox,
  29. )
  30. from core.settings import UserSettings
  31. class MapCalcFrame(wx.Frame):
  32. """Mapcalc Frame class. Calculator-style window to create and run
  33. r(3).mapcalc statements.
  34. """
  35. def __init__(
  36. self,
  37. parent,
  38. giface,
  39. cmd,
  40. id=wx.ID_ANY,
  41. style=wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER,
  42. **kwargs,
  43. ):
  44. self.parent = parent
  45. self._giface = giface
  46. if self.parent:
  47. self.log = self.parent.GetLogWindow()
  48. else:
  49. self.log = None
  50. # grass command
  51. self.cmd = cmd
  52. if self.cmd == "r.mapcalc":
  53. self.rast3d = False
  54. title = _("Raster Map Calculator")
  55. if self.cmd == "r3.mapcalc":
  56. self.rast3d = True
  57. title = _("3D Raster Map Calculator")
  58. wx.Frame.__init__(self, parent, id=id, title=title, **kwargs)
  59. self.SetIcon(
  60. wx.Icon(os.path.join(globalvar.ICONDIR, "grass.ico"), wx.BITMAP_TYPE_ICO)
  61. )
  62. self.panel = wx.Panel(parent=self, id=wx.ID_ANY)
  63. self.CreateStatusBar()
  64. #
  65. # variables
  66. #
  67. self.heading = _("mapcalc statement")
  68. self.funct_dict = {
  69. "abs(x)": "abs()",
  70. "acos(x)": "acos()",
  71. "asin(x)": "asin()",
  72. "atan(x)": "atan()",
  73. "atan(x,y)": "atan( , )",
  74. "cos(x)": "cos()",
  75. "double(x)": "double()",
  76. "eval([x,y,...,]z)": "eval()",
  77. "exp(x)": "exp()",
  78. "exp(x,y)": "exp( , )",
  79. "float(x)": "float()",
  80. "graph(x,x1,y1[x2,y2..])": "graph( , , )",
  81. "if(x)": "if()",
  82. "if(x,a)": "if( , )",
  83. "if(x,a,b)": "if( , , )",
  84. "if(x,a,b,c)": "if( , , , )",
  85. "int(x)": "int()",
  86. "isnull(x)": "isnull()",
  87. "log(x)": "log(",
  88. "log(x,b)": "log( , )",
  89. "max(x,y[,z...])": "max( , )",
  90. "median(x,y[,z...])": "median( , )",
  91. "min(x,y[,z...])": "min( , )",
  92. "mod(x,y)": "mod( , )",
  93. "mode(x,y[,z...])": "mode( , )",
  94. "nmax(x,y[,z...])": "nmax( , )",
  95. "nmedian(x,y[,z...])": "nmedian( , )",
  96. "nmin(x,y[,z...])": "nmin( , )",
  97. "nmode(x,y[,z...])": "nmode( , )",
  98. "not(x)": "not()",
  99. "pow(x,y)": "pow( , )",
  100. "rand(a,b)": "rand( , )",
  101. "round(x)": "round()",
  102. "round(x,y)": "round( , )",
  103. "round(x,y,z)": "round( , , )",
  104. "sin(x)": "sin()",
  105. "sqrt(x)": "sqrt()",
  106. "tan(x)": "tan()",
  107. "xor(x,y)": "xor( , )",
  108. "row()": "row()",
  109. "col()": "col()",
  110. "nrows()": "nrows()",
  111. "ncols()": "ncols()",
  112. "x()": "x()",
  113. "y()": "y()",
  114. "ewres()": "ewres()",
  115. "nsres()": "nsres()",
  116. "area()": "area()",
  117. "null()": "null()",
  118. }
  119. if self.rast3d:
  120. self.funct_dict["z()"] = "z()"
  121. self.funct_dict["tbres()"] = "tbres()"
  122. element = "raster_3d"
  123. else:
  124. element = "cell"
  125. # characters which can be in raster map name but the map name must be
  126. # then quoted
  127. self.charactersToQuote = "+-&!<>%~?^|"
  128. # stores last typed map name in Select widget to distinguish typing
  129. # from selection
  130. self.lastMapName = ""
  131. self.operatorBox = StaticBox(
  132. parent=self.panel, id=wx.ID_ANY, label=" %s " % _("Operators")
  133. )
  134. self.outputBox = StaticBox(
  135. parent=self.panel, id=wx.ID_ANY, label=" %s " % _("Output")
  136. )
  137. self.operandBox = StaticBox(
  138. parent=self.panel, id=wx.ID_ANY, label=" %s " % _("Operands")
  139. )
  140. self.expressBox = StaticBox(
  141. parent=self.panel, id=wx.ID_ANY, label=" %s " % _("Expression")
  142. )
  143. #
  144. # Buttons
  145. #
  146. self.btn_clear = ClearButton(parent=self.panel)
  147. self.btn_help = Button(parent=self.panel, id=wx.ID_HELP)
  148. self.btn_run = Button(parent=self.panel, id=wx.ID_ANY, label=_("&Run"))
  149. self.btn_run.SetDefault()
  150. self.btn_close = CloseButton(parent=self.panel)
  151. self.btn_save = Button(parent=self.panel, id=wx.ID_SAVE)
  152. self.btn_save.SetToolTip(_("Save expression to file"))
  153. self.btn_load = Button(parent=self.panel, id=wx.ID_ANY, label=_("&Load"))
  154. self.btn_load.SetToolTip(_("Load expression from file"))
  155. self.btn_copy = Button(parent=self.panel, id=wx.ID_ANY, label=_("Copy"))
  156. self.btn_copy.SetToolTip(_("Copy the current command string to the clipboard"))
  157. self.btn = dict()
  158. self.btn["pow"] = Button(parent=self.panel, id=wx.ID_ANY, label="^")
  159. self.btn["pow"].SetToolTip(_("exponent"))
  160. self.btn["div"] = Button(parent=self.panel, id=wx.ID_ANY, label="/")
  161. self.btn["div"].SetToolTip(_("divide"))
  162. self.btn["add"] = Button(parent=self.panel, id=wx.ID_ANY, label="+")
  163. self.btn["add"].SetToolTip(_("add"))
  164. self.btn["minus"] = Button(parent=self.panel, id=wx.ID_ANY, label="-")
  165. self.btn["minus"].SetToolTip(_("subtract"))
  166. self.btn["mod"] = Button(parent=self.panel, id=wx.ID_ANY, label="%")
  167. self.btn["mod"].SetToolTip(_("modulus"))
  168. self.btn["mult"] = Button(parent=self.panel, id=wx.ID_ANY, label="*")
  169. self.btn["mult"].SetToolTip(_("multiply"))
  170. self.btn["parenl"] = Button(parent=self.panel, id=wx.ID_ANY, label="(")
  171. self.btn["parenr"] = Button(parent=self.panel, id=wx.ID_ANY, label=")")
  172. self.btn["lshift"] = Button(parent=self.panel, id=wx.ID_ANY, label="<<")
  173. self.btn["lshift"].SetToolTip(_("left shift"))
  174. self.btn["rshift"] = Button(parent=self.panel, id=wx.ID_ANY, label=">>")
  175. self.btn["rshift"].SetToolTip(_("right shift"))
  176. self.btn["rshiftu"] = Button(parent=self.panel, id=wx.ID_ANY, label=">>>")
  177. self.btn["rshiftu"].SetToolTip(_("right shift (unsigned)"))
  178. self.btn["gt"] = Button(parent=self.panel, id=wx.ID_ANY, label=">")
  179. self.btn["gt"].SetToolTip(_("greater than"))
  180. self.btn["gteq"] = Button(parent=self.panel, id=wx.ID_ANY, label=">=")
  181. self.btn["gteq"].SetToolTip(_("greater than or equal to"))
  182. self.btn["lt"] = Button(parent=self.panel, id=wx.ID_ANY, label="<")
  183. self.btn["lt"].SetToolTip(_("less than"))
  184. self.btn["lteq"] = Button(parent=self.panel, id=wx.ID_ANY, label="<=")
  185. self.btn["lteq"].SetToolTip(_("less than or equal to"))
  186. self.btn["eq"] = Button(parent=self.panel, id=wx.ID_ANY, label="==")
  187. self.btn["eq"].SetToolTip(_("equal to"))
  188. self.btn["noteq"] = Button(parent=self.panel, id=wx.ID_ANY, label="!=")
  189. self.btn["noteq"].SetToolTip(_("not equal to"))
  190. self.btn["compl"] = Button(parent=self.panel, id=wx.ID_ANY, label="~")
  191. self.btn["compl"].SetToolTip(_("one's complement"))
  192. self.btn["not"] = Button(parent=self.panel, id=wx.ID_ANY, label="!")
  193. self.btn["not"].SetToolTip(_("NOT"))
  194. self.btn["andbit"] = Button(parent=self.panel, id=wx.ID_ANY, label="&&")
  195. self.btn["andbit"].SetToolTip(_("bitwise AND"))
  196. self.btn["orbit"] = Button(parent=self.panel, id=wx.ID_ANY, label="|")
  197. self.btn["orbit"].SetToolTip(_("bitwise OR"))
  198. self.btn["and"] = Button(parent=self.panel, id=wx.ID_ANY, label="&&&&")
  199. self.btn["and"].SetToolTip(_("logical AND"))
  200. self.btn["andnull"] = Button(parent=self.panel, id=wx.ID_ANY, label="&&&&&&")
  201. self.btn["andnull"].SetToolTip(_("logical AND (ignores NULLs)"))
  202. self.btn["or"] = Button(parent=self.panel, id=wx.ID_ANY, label="||")
  203. self.btn["or"].SetToolTip(_("logical OR"))
  204. self.btn["ornull"] = Button(parent=self.panel, id=wx.ID_ANY, label="|||")
  205. self.btn["ornull"].SetToolTip(_("logical OR (ignores NULLs)"))
  206. self.btn["cond"] = Button(parent=self.panel, id=wx.ID_ANY, label="a ? b : c")
  207. self.btn["cond"].SetToolTip(_("conditional"))
  208. #
  209. # Text area
  210. #
  211. self.text_mcalc = TextCtrl(
  212. parent=self.panel, id=wx.ID_ANY, size=(-1, 100), style=wx.TE_MULTILINE
  213. )
  214. wx.CallAfter(self.text_mcalc.SetFocus)
  215. #
  216. # Map and function insertion text and ComboBoxes
  217. self.newmaplabel = StaticText(parent=self.panel, id=wx.ID_ANY)
  218. if self.rast3d:
  219. self.newmaplabel.SetLabel(_("Name for new 3D raster map to create"))
  220. else:
  221. self.newmaplabel.SetLabel(_("Name for new raster map to create"))
  222. # As we can write only to current mapset, names should not be fully qualified
  223. # to not confuse end user about writing in other mapset
  224. self.newmaptxt = Select(
  225. parent=self.panel,
  226. id=wx.ID_ANY,
  227. size=(250, -1),
  228. type=element,
  229. multiple=False,
  230. fullyQualified=False,
  231. )
  232. self.mapsellabel = StaticText(parent=self.panel, id=wx.ID_ANY)
  233. if self.rast3d:
  234. self.mapsellabel.SetLabel(_("Insert existing 3D raster map"))
  235. else:
  236. self.mapsellabel.SetLabel(_("Insert existing raster map"))
  237. self.mapselect = Select(
  238. parent=self.panel,
  239. id=wx.ID_ANY,
  240. size=(250, -1),
  241. type=element,
  242. multiple=False,
  243. )
  244. self.functlabel = StaticText(
  245. parent=self.panel, id=wx.ID_ANY, label=_("Insert mapcalc function")
  246. )
  247. self.function = wx.ComboBox(
  248. parent=self.panel,
  249. id=wx.ID_ANY,
  250. size=(250, -1),
  251. choices=sorted(self.funct_dict.keys()),
  252. style=wx.CB_DROPDOWN | wx.CB_READONLY | wx.TE_PROCESS_ENTER,
  253. )
  254. self.overwrite = wx.CheckBox(
  255. parent=self.panel,
  256. id=wx.ID_ANY,
  257. label=_("Allow output files to overwrite existing files"),
  258. )
  259. self.overwrite.SetValue(
  260. UserSettings.Get(group="cmd", key="overwrite", subkey="enabled")
  261. )
  262. self.randomSeed = wx.CheckBox(
  263. parent=self.panel, label=_("Generate random seed for rand()")
  264. )
  265. self.randomSeedStaticText = StaticText(parent=self.panel, label=_("Seed:"))
  266. self.randomSeedText = TextCtrl(
  267. parent=self.panel, size=(100, -1), validator=IntegerValidator()
  268. )
  269. self.randomSeedText.SetToolTip(_("Integer seed for rand() function"))
  270. self.randomSeed.SetValue(True)
  271. self.randomSeedStaticText.Disable()
  272. self.randomSeedText.Disable()
  273. self.addbox = wx.CheckBox(
  274. parent=self.panel,
  275. label=_("Add created raster map into layer tree"),
  276. style=wx.NO_BORDER,
  277. )
  278. self.addbox.SetValue(
  279. UserSettings.Get(group="cmd", key="addNewLayer", subkey="enabled")
  280. )
  281. if not self.parent or self.parent.GetName() != "LayerManager":
  282. self.addbox.Hide()
  283. #
  284. # Bindings
  285. #
  286. for btn in self.btn.keys():
  287. self.btn[btn].Bind(wx.EVT_BUTTON, self.AddMark)
  288. self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose)
  289. self.btn_clear.Bind(wx.EVT_BUTTON, self.OnClear)
  290. self.btn_run.Bind(wx.EVT_BUTTON, self.OnMCalcRun)
  291. self.btn_help.Bind(wx.EVT_BUTTON, self.OnHelp)
  292. self.btn_save.Bind(wx.EVT_BUTTON, self.OnSaveExpression)
  293. self.btn_load.Bind(wx.EVT_BUTTON, self.OnLoadExpression)
  294. self.btn_copy.Bind(wx.EVT_BUTTON, self.OnCopyCommand)
  295. self.mapselect.Bind(wx.EVT_TEXT, self.OnSelect)
  296. self.function.Bind(wx.EVT_COMBOBOX, self._return_funct)
  297. self.function.Bind(wx.EVT_TEXT_ENTER, self.OnSelect)
  298. self.newmaptxt.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
  299. self.text_mcalc.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
  300. self.overwrite.Bind(wx.EVT_CHECKBOX, self.OnUpdateStatusBar)
  301. self.randomSeed.Bind(wx.EVT_CHECKBOX, self.OnUpdateStatusBar)
  302. self.randomSeed.Bind(wx.EVT_CHECKBOX, self.OnSeedFlag)
  303. self.randomSeedText.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
  304. # bind closing to ESC
  305. self.Bind(wx.EVT_MENU, self.OnClose, id=wx.ID_CANCEL)
  306. accelTableList = [(wx.ACCEL_NORMAL, wx.WXK_ESCAPE, wx.ID_CANCEL)]
  307. accelTable = wx.AcceleratorTable(accelTableList)
  308. self.SetAcceleratorTable(accelTable)
  309. self._layout()
  310. self.SetMinSize(self.panel.GetBestSize())
  311. # workaround for http://trac.wxwidgets.org/ticket/13628
  312. self.SetSize(self.panel.GetBestSize())
  313. def _return_funct(self, event):
  314. i = event.GetString()
  315. self._addSomething(self.funct_dict[i])
  316. # reset
  317. win = self.FindWindowById(event.GetId())
  318. win.SetValue("")
  319. def _layout(self):
  320. sizer = wx.BoxSizer(wx.VERTICAL)
  321. controlSizer = wx.BoxSizer(wx.HORIZONTAL)
  322. operatorSizer = wx.StaticBoxSizer(self.operatorBox, wx.HORIZONTAL)
  323. outOpeSizer = wx.BoxSizer(wx.VERTICAL)
  324. buttonSizer1 = wx.GridBagSizer(5, 1)
  325. buttonSizer1.Add(self.btn["add"], pos=(0, 0))
  326. buttonSizer1.Add(self.btn["minus"], pos=(0, 1))
  327. buttonSizer1.Add(self.btn["mod"], pos=(5, 0))
  328. buttonSizer1.Add(self.btn["mult"], pos=(1, 0))
  329. buttonSizer1.Add(self.btn["div"], pos=(1, 1))
  330. buttonSizer1.Add(self.btn["pow"], pos=(5, 1))
  331. buttonSizer1.Add(self.btn["gt"], pos=(2, 0))
  332. buttonSizer1.Add(self.btn["gteq"], pos=(2, 1))
  333. buttonSizer1.Add(self.btn["eq"], pos=(4, 0))
  334. buttonSizer1.Add(self.btn["lt"], pos=(3, 0))
  335. buttonSizer1.Add(self.btn["lteq"], pos=(3, 1))
  336. buttonSizer1.Add(self.btn["noteq"], pos=(4, 1))
  337. buttonSizer2 = wx.GridBagSizer(5, 1)
  338. buttonSizer2.Add(self.btn["and"], pos=(0, 0))
  339. buttonSizer2.Add(self.btn["andbit"], pos=(1, 0))
  340. buttonSizer2.Add(self.btn["andnull"], pos=(2, 0))
  341. buttonSizer2.Add(self.btn["or"], pos=(0, 1))
  342. buttonSizer2.Add(self.btn["orbit"], pos=(1, 1))
  343. buttonSizer2.Add(self.btn["ornull"], pos=(2, 1))
  344. buttonSizer2.Add(self.btn["lshift"], pos=(3, 0))
  345. buttonSizer2.Add(self.btn["rshift"], pos=(3, 1))
  346. buttonSizer2.Add(self.btn["rshiftu"], pos=(4, 0))
  347. buttonSizer2.Add(self.btn["cond"], pos=(5, 0))
  348. buttonSizer2.Add(self.btn["compl"], pos=(5, 1))
  349. buttonSizer2.Add(self.btn["not"], pos=(4, 1))
  350. outputSizer = wx.StaticBoxSizer(self.outputBox, wx.VERTICAL)
  351. outputSizer.Add(self.newmaplabel, flag=wx.ALIGN_CENTER | wx.TOP, border=5)
  352. outputSizer.Add(self.newmaptxt, flag=wx.EXPAND | wx.ALL, border=5)
  353. operandSizer = wx.StaticBoxSizer(self.operandBox, wx.HORIZONTAL)
  354. buttonSizer3 = wx.GridBagSizer(7, 1)
  355. buttonSizer3.Add(
  356. self.functlabel, pos=(0, 0), span=(1, 2), flag=wx.ALIGN_CENTER | wx.EXPAND
  357. )
  358. buttonSizer3.Add(self.function, pos=(1, 0), span=(1, 2))
  359. buttonSizer3.Add(
  360. self.mapsellabel, pos=(2, 0), span=(1, 2), flag=wx.ALIGN_CENTER
  361. )
  362. buttonSizer3.Add(self.mapselect, pos=(3, 0), span=(1, 2))
  363. threebutton = wx.GridBagSizer(1, 2)
  364. threebutton.Add(self.btn["parenl"], pos=(0, 0), span=(1, 1), flag=wx.ALIGN_LEFT)
  365. threebutton.Add(
  366. self.btn["parenr"], pos=(0, 1), span=(1, 1), flag=wx.ALIGN_CENTER
  367. )
  368. threebutton.Add(self.btn_clear, pos=(0, 2), span=(1, 1), flag=wx.ALIGN_RIGHT)
  369. buttonSizer3.Add(threebutton, pos=(4, 0), span=(1, 1), flag=wx.ALIGN_CENTER)
  370. buttonSizer4 = wx.BoxSizer(wx.HORIZONTAL)
  371. buttonSizer4.Add(self.btn_load, flag=wx.ALL, border=5)
  372. buttonSizer4.Add(self.btn_save, flag=wx.ALL, border=5)
  373. buttonSizer4.Add(self.btn_copy, flag=wx.ALL, border=5)
  374. buttonSizer4.AddSpacer(30)
  375. buttonSizer4.Add(self.btn_help, flag=wx.ALL, border=5)
  376. buttonSizer4.Add(self.btn_run, flag=wx.ALL, border=5)
  377. buttonSizer4.Add(self.btn_close, flag=wx.ALL, border=5)
  378. operatorSizer.Add(buttonSizer1, proportion=0, flag=wx.ALL | wx.EXPAND, border=5)
  379. operatorSizer.Add(
  380. buttonSizer2,
  381. proportion=0,
  382. flag=wx.TOP | wx.BOTTOM | wx.RIGHT | wx.EXPAND,
  383. border=5,
  384. )
  385. operandSizer.Add(buttonSizer3, proportion=0, flag=wx.ALL, border=5)
  386. controlSizer.Add(
  387. operatorSizer, proportion=1, flag=wx.RIGHT | wx.EXPAND, border=5
  388. )
  389. outOpeSizer.Add(outputSizer, proportion=0, flag=wx.EXPAND)
  390. outOpeSizer.Add(operandSizer, proportion=1, flag=wx.EXPAND | wx.TOP, border=5)
  391. controlSizer.Add(outOpeSizer, proportion=0, flag=wx.EXPAND)
  392. expressSizer = wx.StaticBoxSizer(self.expressBox, wx.HORIZONTAL)
  393. expressSizer.Add(self.text_mcalc, proportion=1, flag=wx.EXPAND)
  394. sizer.Add(controlSizer, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)
  395. sizer.Add(
  396. expressSizer, proportion=1, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5
  397. )
  398. sizer.Add(buttonSizer4, proportion=0, flag=wx.ALIGN_RIGHT | wx.ALL, border=3)
  399. randomSizer = wx.BoxSizer(wx.HORIZONTAL)
  400. randomSizer.Add(
  401. self.randomSeed,
  402. proportion=0,
  403. flag=wx.RIGHT | wx.ALIGN_CENTER_VERTICAL,
  404. border=20,
  405. )
  406. randomSizer.Add(
  407. self.randomSeedStaticText,
  408. proportion=0,
  409. flag=wx.RIGHT | wx.ALIGN_CENTER_VERTICAL,
  410. border=5,
  411. )
  412. randomSizer.Add(self.randomSeedText, proportion=0)
  413. sizer.Add(randomSizer, proportion=0, flag=wx.LEFT | wx.RIGHT, border=5)
  414. sizer.Add(self.overwrite, proportion=0, flag=wx.LEFT | wx.RIGHT, border=5)
  415. if self.addbox.IsShown():
  416. sizer.Add(self.addbox, proportion=0, flag=wx.LEFT | wx.RIGHT, border=5)
  417. self.panel.SetAutoLayout(True)
  418. self.panel.SetSizer(sizer)
  419. sizer.Fit(self.panel)
  420. self.Layout()
  421. def AddMark(self, event):
  422. """Sends operators to insertion method"""
  423. if event.GetId() == self.btn["compl"].GetId():
  424. mark = "~"
  425. elif event.GetId() == self.btn["not"].GetId():
  426. mark = "!"
  427. elif event.GetId() == self.btn["pow"].GetId():
  428. mark = "^"
  429. elif event.GetId() == self.btn["div"].GetId():
  430. mark = "/"
  431. elif event.GetId() == self.btn["add"].GetId():
  432. mark = "+"
  433. elif event.GetId() == self.btn["minus"].GetId():
  434. mark = "-"
  435. elif event.GetId() == self.btn["mod"].GetId():
  436. mark = "%"
  437. elif event.GetId() == self.btn["mult"].GetId():
  438. mark = "*"
  439. elif event.GetId() == self.btn["lshift"].GetId():
  440. mark = "<<"
  441. elif event.GetId() == self.btn["rshift"].GetId():
  442. mark = ">>"
  443. elif event.GetId() == self.btn["rshiftu"].GetId():
  444. mark = ">>>"
  445. elif event.GetId() == self.btn["gt"].GetId():
  446. mark = ">"
  447. elif event.GetId() == self.btn["gteq"].GetId():
  448. mark = ">="
  449. elif event.GetId() == self.btn["lt"].GetId():
  450. mark = "<"
  451. elif event.GetId() == self.btn["lteq"].GetId():
  452. mark = "<="
  453. elif event.GetId() == self.btn["eq"].GetId():
  454. mark = "=="
  455. elif event.GetId() == self.btn["noteq"].GetId():
  456. mark = "!="
  457. elif event.GetId() == self.btn["andbit"].GetId():
  458. mark = "&"
  459. elif event.GetId() == self.btn["orbit"].GetId():
  460. mark = "|"
  461. elif event.GetId() == self.btn["or"].GetId():
  462. mark = "||"
  463. elif event.GetId() == self.btn["ornull"].GetId():
  464. mark = "|||"
  465. elif event.GetId() == self.btn["and"].GetId():
  466. mark = "&&"
  467. elif event.GetId() == self.btn["andnull"].GetId():
  468. mark = "&&&"
  469. elif event.GetId() == self.btn["cond"].GetId():
  470. mark = " ? : "
  471. elif event.GetId() == self.btn["parenl"].GetId():
  472. mark = "("
  473. elif event.GetId() == self.btn["parenr"].GetId():
  474. mark = ")"
  475. self._addSomething(mark)
  476. # unused
  477. # def OnSelectTextEvt(self, event):
  478. # """Checks if user is typing or the event was emitted by map selection.
  479. # Prevents from changing focus.
  480. # """
  481. # item = self.mapselect.GetValue().strip()
  482. # if not (abs(len(item) - len(self.lastMapName)) == 1 and \
  483. # self.lastMapName in item or item in self.lastMapName):
  484. # self.OnSelect(event)
  485. # self.lastMapName = item
  486. def OnSelect(self, event):
  487. """Gets raster map or function selection and send it to
  488. insertion method.
  489. Checks for characters which can be in raster map name but
  490. the raster map name must be then quoted.
  491. """
  492. win = self.FindWindowById(event.GetId())
  493. item = win.GetValue().strip()
  494. if any((char in item) for char in self.charactersToQuote):
  495. item = '"' + item + '"'
  496. self._addSomething(item)
  497. win.ChangeValue("") # reset
  498. # Map selector likes to keep focus. Set it back to expression input area
  499. wx.CallAfter(self.text_mcalc.SetFocus)
  500. def OnUpdateStatusBar(self, event):
  501. """Update statusbar text"""
  502. command = self._getCommand()
  503. self.SetStatusText(command)
  504. event.Skip()
  505. def OnSeedFlag(self, event):
  506. checked = self.randomSeed.IsChecked()
  507. self.randomSeedText.Enable(not checked)
  508. self.randomSeedStaticText.Enable(not checked)
  509. event.Skip()
  510. def _getCommand(self):
  511. """Returns entire command as string."""
  512. expr = self.text_mcalc.GetValue().strip().replace("\n", " ")
  513. cmd = "r.mapcalc"
  514. if self.rast3d:
  515. cmd = "r3.mapcalc"
  516. overwrite = ""
  517. if self.overwrite.IsChecked():
  518. overwrite = " --overwrite"
  519. seed_flag = seed = ""
  520. if re.search(pattern="rand *\(.+\)", string=expr):
  521. if self.randomSeed.IsChecked():
  522. seed_flag = " -s"
  523. else:
  524. seed = " seed={val}".format(val=self.randomSeedText.GetValue().strip())
  525. return '{cmd} expression="{new} = {expr}"{seed}{seed_flag}{overwrite}'.format(
  526. cmd=cmd,
  527. expr=expr,
  528. new=self.newmaptxt.GetValue(),
  529. seed_flag=seed_flag,
  530. seed=seed,
  531. overwrite=overwrite,
  532. )
  533. def _addSomething(self, what):
  534. """Inserts operators, map names, and functions into text area"""
  535. mcalcstr = self.text_mcalc.GetValue()
  536. position = self.text_mcalc.GetInsertionPoint()
  537. newmcalcstr = mcalcstr[:position]
  538. position_offset = 0
  539. try:
  540. if newmcalcstr[-1] != " ":
  541. newmcalcstr += " "
  542. position_offset += 1
  543. except:
  544. pass
  545. newmcalcstr += what
  546. # Do not add extra space if there is already one
  547. try:
  548. if newmcalcstr[-1] != " " and mcalcstr[position] != " ":
  549. newmcalcstr += " "
  550. except:
  551. newmcalcstr += " "
  552. newmcalcstr += mcalcstr[position:]
  553. self.text_mcalc.SetValue(newmcalcstr)
  554. if len(what) > 0:
  555. match = re.search(pattern="\(.*\)", string=what)
  556. if match:
  557. position_offset += match.start() + 1
  558. else:
  559. position_offset += len(what)
  560. try:
  561. if newmcalcstr[position + position_offset] == " ":
  562. position_offset += 1
  563. except:
  564. pass
  565. self.text_mcalc.SetInsertionPoint(position + position_offset)
  566. self.text_mcalc.Update()
  567. self.text_mcalc.SetFocus()
  568. def OnMCalcRun(self, event):
  569. """Builds and runs r.mapcalc statement"""
  570. name = self.newmaptxt.GetValue().strip()
  571. if not name:
  572. GError(
  573. parent=self,
  574. message=_("You must enter the name of " "a new raster map to create."),
  575. )
  576. return
  577. if not (name[0] == '"' and name[-1] == '"') and any(
  578. (char in name) for char in self.charactersToQuote
  579. ):
  580. name = '"' + name + '"'
  581. expr = self.text_mcalc.GetValue().strip().replace("\n", " ")
  582. if not expr:
  583. GError(
  584. parent=self,
  585. message=_(
  586. "You must enter an expression " "to create a new raster map."
  587. ),
  588. )
  589. return
  590. seed_flag = seed = None
  591. if re.search(pattern="rand *\(.+\)", string=expr):
  592. if self.randomSeed.IsChecked():
  593. seed_flag = "-s"
  594. else:
  595. seed = self.randomSeedText.GetValue().strip()
  596. if self.log:
  597. cmd = [self.cmd]
  598. if seed_flag:
  599. cmd.append("-s")
  600. if seed:
  601. cmd.append("seed={val}".format(val=seed))
  602. if self.overwrite.IsChecked():
  603. cmd.append("--overwrite")
  604. cmd.append(str("expression=%s = %s" % (name, expr)))
  605. self.log.RunCmd(cmd, onDone=self.OnDone)
  606. self.parent.Raise()
  607. else:
  608. if self.overwrite.IsChecked():
  609. overwrite = True
  610. else:
  611. overwrite = False
  612. params = dict(expression="%s=%s" % (name, expr), overwrite=overwrite)
  613. if seed_flag:
  614. params["flags"] = "s"
  615. if seed:
  616. params["seed"] = seed
  617. RunCommand(self.cmd, **params)
  618. def OnDone(self, event):
  619. """Add create map to the layer tree
  620. Sends the mapCreated signal from the grass interface.
  621. """
  622. if event.returncode != 0:
  623. return
  624. name = self.newmaptxt.GetValue().strip(' "') + "@" + grass.gisenv()["MAPSET"]
  625. ltype = "raster"
  626. if self.rast3d:
  627. ltype = "raster_3d"
  628. self._giface.mapCreated.emit(
  629. name=name, ltype=ltype, add=self.addbox.IsChecked()
  630. )
  631. gisenv = grass.gisenv()
  632. self._giface.grassdbChanged.emit(
  633. grassdb=gisenv["GISDBASE"],
  634. location=gisenv["LOCATION_NAME"],
  635. mapset=gisenv["MAPSET"],
  636. action="new",
  637. map=name.split("@")[0],
  638. element=ltype,
  639. )
  640. def OnSaveExpression(self, event):
  641. """Saves expression to file"""
  642. mctxt = (
  643. self.newmaptxt.GetValue() + " = " + self.text_mcalc.GetValue() + os.linesep
  644. )
  645. # dialog
  646. dlg = wx.FileDialog(
  647. parent=self,
  648. message=_("Choose a file name to save the expression"),
  649. wildcard=_("Expression file (*)|*"),
  650. style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT,
  651. )
  652. if dlg.ShowModal() == wx.ID_OK:
  653. path = dlg.GetPath()
  654. if not path:
  655. dlg.Destroy()
  656. return
  657. try:
  658. fobj = open(path, "w")
  659. fobj.write(mctxt)
  660. finally:
  661. fobj.close()
  662. dlg.Destroy()
  663. def OnLoadExpression(self, event):
  664. """Load expression from file"""
  665. dlg = wx.FileDialog(
  666. parent=self,
  667. message=_("Choose a file name to load the expression"),
  668. wildcard=_("Expression file (*)|*"),
  669. style=wx.FD_OPEN,
  670. )
  671. if dlg.ShowModal() == wx.ID_OK:
  672. path = dlg.GetPath()
  673. if not path:
  674. dlg.Destroy()
  675. return
  676. try:
  677. fobj = open(path, "r")
  678. mctxt = fobj.read()
  679. finally:
  680. fobj.close()
  681. try:
  682. result, exp = mctxt.split("=", 1)
  683. except ValueError:
  684. result = ""
  685. exp = mctxt
  686. self.newmaptxt.SetValue(result.strip())
  687. self.text_mcalc.SetValue(exp.strip())
  688. self.text_mcalc.SetFocus()
  689. self.text_mcalc.SetInsertionPointEnd()
  690. dlg.Destroy()
  691. def OnCopyCommand(self, event):
  692. command = self._getCommand()
  693. cmddata = wx.TextDataObject()
  694. cmddata.SetText(command)
  695. if wx.TheClipboard.Open():
  696. wx.TheClipboard.SetData(cmddata)
  697. wx.TheClipboard.Close()
  698. self.SetStatusText(_("'{cmd}' copied to clipboard").format(cmd=command))
  699. def OnClear(self, event):
  700. """Clears text area"""
  701. self.text_mcalc.SetValue("")
  702. def OnHelp(self, event):
  703. """Launches r.mapcalc help"""
  704. RunCommand("g.manual", parent=self, entry=self.cmd)
  705. def OnClose(self, event):
  706. """Close window"""
  707. self.Destroy()
  708. if __name__ == "__main__":
  709. app = wx.App(0)
  710. frame = MapCalcFrame(
  711. parent=None, cmd="r.mapcalc", giface=StandaloneGrassInterface()
  712. )
  713. frame.Show()
  714. app.MainLoop()