frame.py 61 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654
  1. """!
  2. @package gmodeler.frame
  3. @brief wxGUI Graphical Modeler for creating, editing, and managing models
  4. Classes:
  5. - frame::ModelFrame
  6. - frame::ModelCanvas
  7. - frame::ModelEvtHandler
  8. - frame::VariablePanel
  9. - frame::ItemPanel
  10. - frame::PythonPanel
  11. (C) 2010-2012 by the GRASS Development Team
  12. This program is free software under the GNU General Public License
  13. (>=v2). Read the file COPYING that comes with GRASS for details.
  14. @author Martin Landa <landa.martin gmail.com>
  15. """
  16. import os
  17. import sys
  18. import time
  19. import stat
  20. import textwrap
  21. import tempfile
  22. import copy
  23. import re
  24. import wx
  25. from wx.lib import ogl
  26. import wx.lib.flatnotebook as FN
  27. from core import globalvar
  28. from gui_core.widgets import GNotebook
  29. from gui_core.goutput import GConsole, \
  30. EVT_CMD_RUN, EVT_CMD_DONE, EVT_CMD_PREPARE, EVT_OUTPUT_TEXT
  31. from core.events import EVT_MAP_CREATED, EVT_SHOW_NOTIFICATION
  32. from core.debug import Debug
  33. from core.gcmd import GMessage, GException, GWarning, GError, RunCommand
  34. from gui_core.dialogs import GetImageHandlers
  35. from gui_core.preferences import PreferencesBaseDialog
  36. from core.settings import UserSettings
  37. from core.menudata import MenuData
  38. from gui_core.menu import Menu
  39. from gmodeler.menudata import ModelerMenuData
  40. from gui_core.forms import GUI
  41. from gmodeler.preferences import PreferencesDialog, PropertiesDialog
  42. from gmodeler.toolbars import ModelerToolbar
  43. from gmodeler.model import *
  44. from gmodeler.dialogs import *
  45. from gmodeler.pystc import PyStc
  46. from grass.script import core as grass
  47. class ModelFrame(wx.Frame):
  48. def __init__(self, parent, giface, id = wx.ID_ANY,
  49. title = _("GRASS GIS Graphical Modeler"), **kwargs):
  50. """!Graphical modeler main window
  51. @param parent parent window
  52. @param id window id
  53. @param title window title
  54. @param kwargs wx.Frames' arguments
  55. """
  56. self.parent = parent
  57. self._giface = giface
  58. self.searchDialog = None # module search dialog
  59. self.baseTitle = title
  60. self.modelFile = None # loaded model
  61. self.modelChanged = False
  62. self.cursors = {
  63. "default" : wx.StockCursor(wx.CURSOR_ARROW),
  64. "cross" : wx.StockCursor(wx.CURSOR_CROSS),
  65. }
  66. wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
  67. self.SetName("Modeler")
  68. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  69. self.menubar = Menu(parent = self, data = ModelerMenuData())
  70. self.SetMenuBar(self.menubar)
  71. self.toolbar = ModelerToolbar(parent = self)
  72. self.SetToolBar(self.toolbar)
  73. self.statusbar = self.CreateStatusBar(number = 1)
  74. self.notebook = GNotebook(parent = self,
  75. style = FN.FNB_FANCY_TABS | FN.FNB_BOTTOM |
  76. FN.FNB_NO_NAV_BUTTONS | FN.FNB_NO_X_BUTTON)
  77. self.canvas = ModelCanvas(self)
  78. self.canvas.SetBackgroundColour(wx.WHITE)
  79. self.canvas.SetCursor(self.cursors["default"])
  80. self.model = Model(self.canvas)
  81. self.variablePanel = VariablePanel(parent = self)
  82. self.itemPanel = ItemPanel(parent = self)
  83. self.pythonPanel = PythonPanel(parent = self)
  84. self.goutput = GConsole(parent = self)
  85. self.goutput.Bind(EVT_OUTPUT_TEXT, self.OnOutputText)
  86. self.Bind(EVT_CMD_RUN, self.OnCmdRun)
  87. self.Bind(EVT_CMD_DONE, self.OnCmdDone)
  88. self.Bind(EVT_CMD_PREPARE, self.OnCmdPrepare)
  89. self.Bind(EVT_MAP_CREATED, self.OnMapCreated)
  90. self.Bind(EVT_SHOW_NOTIFICATION,
  91. lambda event: self.SetStatusText(event.message))
  92. self.notebook.AddPage(page = self.canvas, text=_('Model'), name = 'model')
  93. self.notebook.AddPage(page = self.itemPanel, text=_('Items'), name = 'items')
  94. self.notebook.AddPage(page = self.variablePanel, text=_('Variables'), name = 'variables')
  95. self.notebook.AddPage(page = self.pythonPanel, text=_('Python editor'), name = 'python')
  96. self.notebook.AddPage(page = self.goutput, text=_('Command output'), name = 'output')
  97. wx.CallAfter(self.notebook.SetSelectionByName, 'model')
  98. wx.CallAfter(self.ModelChanged, False)
  99. self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
  100. self.Bind(wx.EVT_SIZE, self.OnSize)
  101. self.notebook.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnPageChanged)
  102. self._layout()
  103. self.SetMinSize((640, 300))
  104. self.SetSize((800, 600))
  105. # fix goutput's pane size
  106. if self.goutput:
  107. self.goutput.SetSashPosition(int(self.GetSize()[1] * .75))
  108. def _layout(self):
  109. """!Do layout"""
  110. sizer = wx.BoxSizer(wx.VERTICAL)
  111. sizer.Add(item = self.notebook, proportion = 1,
  112. flag = wx.EXPAND)
  113. self.SetAutoLayout(True)
  114. self.SetSizer(sizer)
  115. sizer.Fit(self)
  116. self.Layout()
  117. def _addEvent(self, item):
  118. """!Add event to item"""
  119. evthandler = ModelEvtHandler(self.statusbar,
  120. self)
  121. evthandler.SetShape(item)
  122. evthandler.SetPreviousHandler(item.GetEventHandler())
  123. item.SetEventHandler(evthandler)
  124. def GetCanvas(self):
  125. """!Get canvas"""
  126. return self.canvas
  127. def GetModel(self):
  128. """!Get model"""
  129. return self.model
  130. def ModelChanged(self, changed = True):
  131. """!Update window title"""
  132. self.modelChanged = changed
  133. if self.modelFile:
  134. if self.modelChanged:
  135. self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile) + '*')
  136. else:
  137. self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
  138. else:
  139. self.SetTitle(self.baseTitle)
  140. def OnPageChanged(self, event):
  141. """!Page in notebook changed"""
  142. page = event.GetSelection()
  143. if page == self.notebook.GetPageIndexByName('python'):
  144. if self.pythonPanel.IsEmpty():
  145. self.pythonPanel.RefreshScript()
  146. if self.pythonPanel.IsModified():
  147. self.SetStatusText(_('Python script contains local modifications'), 0)
  148. else:
  149. self.SetStatusText(_('Python script is up-to-date'), 0)
  150. event.Skip()
  151. def OnVariables(self, event):
  152. """!Switch to variables page"""
  153. self.notebook.SetSelectionByName('variables')
  154. def OnRemoveItem(self, event):
  155. """!Remove shape
  156. """
  157. self.GetCanvas().RemoveSelected()
  158. def OnCanvasRefresh(self, event):
  159. """!Refresh canvas"""
  160. self.SetStatusText(_("Redrawing model..."), 0)
  161. self.GetCanvas().Refresh()
  162. self.SetStatusText("", 0)
  163. def OnCmdRun(self, event):
  164. """!Run command"""
  165. try:
  166. action = self.GetModel().GetItems()[event.pid]
  167. if hasattr(action, "task"):
  168. action.Update(running = True)
  169. except IndexError:
  170. pass
  171. def OnCmdPrepare(self, event):
  172. """!Prepare for running command"""
  173. if not event.userData:
  174. return
  175. event.onPrepare(item = event.userData['item'],
  176. params = event.userData['params'])
  177. def OnCmdDone(self, event):
  178. """!Command done (or aborted)"""
  179. try:
  180. action = self.GetModel().GetItems()[event.pid]
  181. if hasattr(action, "task"):
  182. action.Update(running = True)
  183. except IndexError:
  184. pass
  185. def OnMapCreated(self, event):
  186. """!Map was created but we don't want to add it to layer tree.
  187. """
  188. # remove this method if you want to add it to layer tree
  189. # or see gui_core.forms.TaskFrame.OnMapCreated
  190. event.add = False
  191. event.Skip()
  192. def OnCloseWindow(self, event):
  193. """!Close window"""
  194. if self.modelChanged and \
  195. UserSettings.Get(group='manager', key='askOnQuit', subkey='enabled'):
  196. if self.modelFile:
  197. message = _("Do you want to save changes in the model?")
  198. else:
  199. message = _("Do you want to store current model settings "
  200. "to model file?")
  201. # ask user to save current settings
  202. dlg = wx.MessageDialog(self,
  203. message = message,
  204. caption=_("Quit Graphical Modeler"),
  205. style = wx.YES_NO | wx.YES_DEFAULT |
  206. wx.CANCEL | wx.ICON_QUESTION | wx.CENTRE)
  207. ret = dlg.ShowModal()
  208. if ret == wx.ID_YES:
  209. if not self.modelFile:
  210. self.OnWorkspaceSaveAs()
  211. else:
  212. self.WriteModelFile(self.modelFile)
  213. elif ret == wx.ID_CANCEL:
  214. dlg.Destroy()
  215. return
  216. dlg.Destroy()
  217. self.Destroy()
  218. def OnSize(self, event):
  219. """!Window resized, save to the model"""
  220. self.ModelChanged()
  221. event.Skip()
  222. def OnPreferences(self, event):
  223. """!Open preferences dialog"""
  224. dlg = PreferencesDialog(parent = self, giface = self._giface)
  225. dlg.CenterOnParent()
  226. dlg.ShowModal()
  227. self.canvas.Refresh()
  228. def OnHelp(self, event):
  229. """!Show help"""
  230. self._giface.Help(entry = 'wxGUI.Modeler')
  231. def OnModelProperties(self, event):
  232. """!Model properties dialog"""
  233. dlg = PropertiesDialog(parent = self)
  234. dlg.CentreOnParent()
  235. properties = self.model.GetProperties()
  236. dlg.Init(properties)
  237. if dlg.ShowModal() == wx.ID_OK:
  238. self.ModelChanged()
  239. for key, value in dlg.GetValues().iteritems():
  240. properties[key] = value
  241. for action in self.model.GetItems(objType = ModelAction):
  242. action.GetTask().set_flag('overwrite', properties['overwrite'])
  243. dlg.Destroy()
  244. def OnDeleteData(self, event):
  245. """!Delete intermediate data"""
  246. rast, vect, rast3d, msg = self.model.GetIntermediateData()
  247. if not rast and not vect and not rast3d:
  248. GMessage(parent = self,
  249. message = _('No intermediate data to delete.'))
  250. return
  251. dlg = wx.MessageDialog(parent = self,
  252. message= _("Do you want to permanently delete data?%s" % msg),
  253. caption=_("Delete intermediate data?"),
  254. style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
  255. ret = dlg.ShowModal()
  256. if ret == wx.ID_YES:
  257. dlg.Destroy()
  258. if rast:
  259. self.goutput.RunCmd(['g.remove', 'rast=%s' %','.join(rast)])
  260. if rast3d:
  261. self.goutput.RunCmd(['g.remove', 'rast3d=%s' %','.join(rast3d)])
  262. if vect:
  263. self.goutput.RunCmd(['g.remove', 'vect=%s' %','.join(vect)])
  264. self.SetStatusText(_("%d maps deleted from current mapset") % \
  265. int(len(rast) + len(rast3d) + len(vect)))
  266. return
  267. dlg.Destroy()
  268. def OnModelNew(self, event):
  269. """!Create new model"""
  270. Debug.msg(4, "ModelFrame.OnModelNew():")
  271. # ask user to save current model
  272. if self.modelFile and self.modelChanged:
  273. self.OnModelSave()
  274. elif self.modelFile is None and \
  275. (self.model.GetNumItems() > 0 or len(self.model.GetData()) > 0):
  276. dlg = wx.MessageDialog(self, message=_("Current model is not empty. "
  277. "Do you want to store current settings "
  278. "to model file?"),
  279. caption=_("Create new model?"),
  280. style=wx.YES_NO | wx.YES_DEFAULT |
  281. wx.CANCEL | wx.ICON_QUESTION)
  282. ret = dlg.ShowModal()
  283. if ret == wx.ID_YES:
  284. self.OnModelSaveAs()
  285. elif ret == wx.ID_CANCEL:
  286. dlg.Destroy()
  287. return
  288. dlg.Destroy()
  289. # delete all items
  290. self.canvas.GetDiagram().DeleteAllShapes()
  291. self.model.Reset()
  292. self.canvas.Refresh()
  293. self.itemPanel.Update()
  294. self.variablePanel.Reset()
  295. # no model file loaded
  296. self.modelFile = None
  297. self.modelChanged = False
  298. self.SetTitle(self.baseTitle)
  299. def OnModelOpen(self, event):
  300. """!Load model from file"""
  301. filename = ''
  302. dlg = wx.FileDialog(parent = self, message=_("Choose model file"),
  303. defaultDir = os.getcwd(),
  304. wildcard=_("GRASS Model File (*.gxm)|*.gxm"))
  305. if dlg.ShowModal() == wx.ID_OK:
  306. filename = dlg.GetPath()
  307. if not filename:
  308. return
  309. Debug.msg(4, "ModelFrame.OnModelOpen(): filename=%s" % filename)
  310. # close current model
  311. self.OnModelClose()
  312. self.LoadModelFile(filename)
  313. self.modelFile = filename
  314. self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
  315. self.SetStatusText(_('%(items)d items (%(actions)d actions) loaded into model') % \
  316. { 'items' : self.model.GetNumItems(),
  317. 'actions' : self.model.GetNumItems(actionOnly = True) }, 0)
  318. def OnModelSave(self, event = None):
  319. """!Save model to file"""
  320. if self.modelFile and self.modelChanged:
  321. dlg = wx.MessageDialog(self, message=_("Model file <%s> already exists. "
  322. "Do you want to overwrite this file?") % \
  323. self.modelFile,
  324. caption=_("Save model"),
  325. style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
  326. if dlg.ShowModal() == wx.ID_NO:
  327. dlg.Destroy()
  328. else:
  329. Debug.msg(4, "ModelFrame.OnModelSave(): filename=%s" % self.modelFile)
  330. self.WriteModelFile(self.modelFile)
  331. self.SetStatusText(_('File <%s> saved') % self.modelFile, 0)
  332. self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
  333. elif not self.modelFile:
  334. self.OnModelSaveAs(None)
  335. def OnModelSaveAs(self, event):
  336. """!Create model to file as"""
  337. filename = ''
  338. dlg = wx.FileDialog(parent = self,
  339. message = _("Choose file to save current model"),
  340. defaultDir = os.getcwd(),
  341. wildcard=_("GRASS Model File (*.gxm)|*.gxm"),
  342. style=wx.FD_SAVE)
  343. if dlg.ShowModal() == wx.ID_OK:
  344. filename = dlg.GetPath()
  345. if not filename:
  346. return
  347. # check for extension
  348. if filename[-4:] != ".gxm":
  349. filename += ".gxm"
  350. if os.path.exists(filename):
  351. dlg = wx.MessageDialog(parent = self,
  352. message=_("Model file <%s> already exists. "
  353. "Do you want to overwrite this file?") % filename,
  354. caption=_("File already exists"),
  355. style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
  356. if dlg.ShowModal() != wx.ID_YES:
  357. dlg.Destroy()
  358. return
  359. Debug.msg(4, "GMFrame.OnModelSaveAs(): filename=%s" % filename)
  360. self.WriteModelFile(filename)
  361. self.modelFile = filename
  362. self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
  363. self.SetStatusText(_('File <%s> saved') % self.modelFile, 0)
  364. def OnModelClose(self, event = None):
  365. """!Close model file"""
  366. Debug.msg(4, "ModelFrame.OnModelClose(): file=%s" % self.modelFile)
  367. # ask user to save current model
  368. if self.modelFile and self.modelChanged:
  369. self.OnModelSave()
  370. elif self.modelFile is None and \
  371. (self.model.GetNumItems() > 0 or len(self.model.GetData()) > 0):
  372. dlg = wx.MessageDialog(self, message=_("Current model is not empty. "
  373. "Do you want to store current settings "
  374. "to model file?"),
  375. caption=_("Create new model?"),
  376. style=wx.YES_NO | wx.YES_DEFAULT |
  377. wx.CANCEL | wx.ICON_QUESTION)
  378. ret = dlg.ShowModal()
  379. if ret == wx.ID_YES:
  380. self.OnModelSaveAs()
  381. elif ret == wx.ID_CANCEL:
  382. dlg.Destroy()
  383. return
  384. dlg.Destroy()
  385. self.modelFile = None
  386. self.SetTitle(self.baseTitle)
  387. self.canvas.GetDiagram().DeleteAllShapes()
  388. self.model.Reset()
  389. self.canvas.Refresh()
  390. def OnRunModel(self, event):
  391. """!Run entire model"""
  392. self.model.Run(self.goutput, self.OnDone, parent = self)
  393. def OnDone(self, cmd, returncode):
  394. """!Computation finished"""
  395. self.SetStatusText('', 0)
  396. # restore original files
  397. if hasattr(self.model, "fileInput"):
  398. for finput in self.model.fileInput:
  399. data = self.model.fileInput[finput]
  400. if not data:
  401. continue
  402. fd = open(finput, "w")
  403. try:
  404. fd.write(data)
  405. finally:
  406. fd.close()
  407. del self.model.fileInput
  408. def OnValidateModel(self, event, showMsg = True):
  409. """!Validate entire model"""
  410. if self.model.GetNumItems() < 1:
  411. GMessage(parent = self,
  412. message = _('Model is empty. Nothing to validate.'))
  413. return
  414. self.SetStatusText(_('Validating model...'), 0)
  415. errList = self.model.Validate()
  416. self.SetStatusText('', 0)
  417. if errList:
  418. GWarning(parent = self,
  419. message = _('Model is not valid.\n\n%s') % '\n'.join(errList))
  420. else:
  421. GMessage(parent = self,
  422. message = _('Model is valid.'))
  423. def OnExportImage(self, event):
  424. """!Export model to image (default image)
  425. """
  426. xminImg = 0
  427. xmaxImg = 0
  428. yminImg = 0
  429. ymaxImg = 0
  430. # get current size of canvas
  431. for shape in self.canvas.GetDiagram().GetShapeList():
  432. w, h = shape.GetBoundingBoxMax()
  433. x = shape.GetX()
  434. y = shape.GetY()
  435. xmin = x - w / 2
  436. xmax = x + w / 2
  437. ymin = y - h / 2
  438. ymax = y + h / 2
  439. if xmin < xminImg:
  440. xminImg = xmin
  441. if xmax > xmaxImg:
  442. xmaxImg = xmax
  443. if ymin < yminImg:
  444. yminImg = ymin
  445. if ymax > ymaxImg:
  446. ymaxImg = ymax
  447. size = wx.Size(int(xmaxImg - xminImg) + 50,
  448. int(ymaxImg - yminImg) + 50)
  449. bitmap = wx.EmptyBitmap(width = size.width, height = size.height)
  450. filetype, ltype = GetImageHandlers(wx.ImageFromBitmap(bitmap))
  451. dlg = wx.FileDialog(parent = self,
  452. message = _("Choose a file name to save the image (no need to add extension)"),
  453. defaultDir = "",
  454. defaultFile = "",
  455. wildcard = filetype,
  456. style=wx.SAVE | wx.FD_OVERWRITE_PROMPT)
  457. if dlg.ShowModal() == wx.ID_OK:
  458. path = dlg.GetPath()
  459. if not path:
  460. dlg.Destroy()
  461. return
  462. base, ext = os.path.splitext(path)
  463. fileType = ltype[dlg.GetFilterIndex()]['type']
  464. extType = ltype[dlg.GetFilterIndex()]['ext']
  465. if ext != extType:
  466. path = base + '.' + extType
  467. dc = wx.MemoryDC(bitmap)
  468. dc.SetBackground(wx.WHITE_BRUSH)
  469. dc.SetBackgroundMode(wx.SOLID)
  470. dc.BeginDrawing()
  471. self.canvas.GetDiagram().Clear(dc)
  472. self.canvas.GetDiagram().Redraw(dc)
  473. dc.EndDrawing()
  474. bitmap.SaveFile(path, fileType)
  475. self.SetStatusText(_("Model exported to <%s>") % path)
  476. dlg.Destroy()
  477. def OnExportPython(self, event = None, text = None):
  478. """!Export model to Python script"""
  479. filename = self.pythonPanel.SaveAs(force = True)
  480. self.SetStatusText(_("Model exported to <%s>") % filename)
  481. def OnDefineRelation(self, event):
  482. """!Define relation between data and action items"""
  483. self.canvas.SetCursor(self.cursors["cross"])
  484. self.defineRelation = { 'from' : None,
  485. 'to' : None }
  486. def OnDefineLoop(self, event):
  487. """!Define new loop in the model"""
  488. self.ModelChanged()
  489. width, height = self.canvas.GetSize()
  490. loop = ModelLoop(self, x = width/2, y = height/2,
  491. id = self.model.GetNumItems() + 1)
  492. self.canvas.diagram.AddShape(loop)
  493. loop.Show(True)
  494. self._addEvent(loop)
  495. self.model.AddItem(loop)
  496. self.canvas.Refresh()
  497. def OnDefineCondition(self, event):
  498. """!Define new condition in the model"""
  499. self.ModelChanged()
  500. width, height = self.canvas.GetSize()
  501. cond = ModelCondition(self, x = width/2, y = height/2,
  502. id = self.model.GetNumItems() + 1)
  503. self.canvas.diagram.AddShape(cond)
  504. cond.Show(True)
  505. self._addEvent(cond)
  506. self.model.AddItem(cond)
  507. self.canvas.Refresh()
  508. def OnAddAction(self, event):
  509. """!Add action to model"""
  510. if self.searchDialog is None:
  511. self.searchDialog = ModelSearchDialog(self)
  512. self.searchDialog.CentreOnParent()
  513. else:
  514. self.searchDialog.Reset()
  515. if self.searchDialog.ShowModal() == wx.ID_CANCEL:
  516. self.searchDialog.Hide()
  517. return
  518. cmd = self.searchDialog.GetCmd()
  519. self.searchDialog.Hide()
  520. self.ModelChanged()
  521. # add action to canvas
  522. x, y = self.canvas.GetNewShapePos()
  523. action = ModelAction(self.model, cmd = cmd, x = x, y = y,
  524. id = self.model.GetNextId())
  525. overwrite = self.model.GetProperties().get('overwrite', None)
  526. if overwrite is not None:
  527. action.GetTask().set_flag('overwrite', overwrite)
  528. self.canvas.diagram.AddShape(action)
  529. action.Show(True)
  530. self._addEvent(action)
  531. self.model.AddItem(action)
  532. self.itemPanel.Update()
  533. self.canvas.Refresh()
  534. time.sleep(.1)
  535. # show properties dialog
  536. win = action.GetPropDialog()
  537. if not win:
  538. if action.IsValid():
  539. self.GetOptData(dcmd = action.GetLog(string = False), layer = action,
  540. params = action.GetParams(), propwin = None)
  541. else:
  542. GUI(parent = self, show = True).ParseCommand(action.GetLog(string = False),
  543. completed = (self.GetOptData, action, action.GetParams()))
  544. elif win and not win.IsShown():
  545. win.Show()
  546. if win:
  547. win.Raise()
  548. def OnAddData(self, event):
  549. """!Add data item to model
  550. """
  551. # add action to canvas
  552. width, height = self.canvas.GetSize()
  553. data = ModelData(self, x = width/2, y = height/2)
  554. dlg = ModelDataDialog(parent = self, shape = data)
  555. data.SetPropDialog(dlg)
  556. dlg.CentreOnParent()
  557. ret = dlg.ShowModal()
  558. dlg.Destroy()
  559. if ret != wx.ID_OK:
  560. return
  561. data.Update()
  562. self.canvas.diagram.AddShape(data)
  563. data.Show(True)
  564. self.ModelChanged()
  565. self._addEvent(data)
  566. self.model.AddItem(data)
  567. self.canvas.Refresh()
  568. def OnOutputText(self, event):
  569. """!Manages @c 'output' notebook page according to event priority."""
  570. if event.priority == 1:
  571. self.notebook.HighlightPageByName('output')
  572. if event.priority >= 2:
  573. self.notebook.SetSelectionByName('output')
  574. if event.priority >= 3:
  575. self.SetFocus()
  576. self.Raise()
  577. def OnAbout(self, event):
  578. """!Display About window"""
  579. info = wx.AboutDialogInfo()
  580. info.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  581. info.SetName(_('wxGUI Graphical Modeler'))
  582. info.SetWebSite('http://grass.osgeo.org')
  583. year = grass.version()['date']
  584. info.SetDescription(_('(C) 2010-%s by the GRASS Development Team\n\n') % year +
  585. '\n'.join(textwrap.wrap(_('This program is free software under the GNU General Public License'
  586. '(>=v2). Read the file COPYING that comes with GRASS for details.'), 75)))
  587. wx.AboutBox(info)
  588. def GetOptData(self, dcmd, layer, params, propwin):
  589. """!Process action data"""
  590. if params: # add data items
  591. width, height = self.canvas.GetSize()
  592. x = [width/2 + 200, width/2 - 200]
  593. for p in params['params']:
  594. if p.get('prompt', '') in ('raster', 'vector', 'raster3d') and \
  595. (p.get('value', None) or \
  596. (p.get('age', 'old') != 'old' and p.get('required', 'no') == 'yes')):
  597. data = layer.FindData(p.get('name', ''))
  598. if data:
  599. data.SetValue(p.get('value', ''))
  600. data.Update()
  601. continue
  602. data = self.model.FindData(p.get('value', ''),
  603. p.get('prompt', ''))
  604. if data:
  605. if p.get('age', 'old') == 'old':
  606. rel = ModelRelation(parent = self, fromShape = data,
  607. toShape = layer, param = p.get('name', ''))
  608. else:
  609. rel = ModelRelation(parent = self, fromShape = layer,
  610. toShape = data, param = p.get('name', ''))
  611. layer.AddRelation(rel)
  612. data.AddRelation(rel)
  613. self.AddLine(rel)
  614. data.Update()
  615. continue
  616. data = ModelData(self, value = p.get('value', ''),
  617. prompt = p.get('prompt', ''),
  618. x = x.pop(), y = height/2)
  619. self._addEvent(data)
  620. self.canvas.diagram.AddShape(data)
  621. data.Show(True)
  622. if p.get('age', 'old') == 'old':
  623. rel = ModelRelation(parent = self, fromShape = data,
  624. toShape = layer, param = p.get('name', ''))
  625. else:
  626. rel = ModelRelation(parent = self, fromShape = layer,
  627. toShape = data, param = p.get('name', ''))
  628. layer.AddRelation(rel)
  629. data.AddRelation(rel)
  630. self.AddLine(rel)
  631. data.Update()
  632. # valid / parameterized ?
  633. layer.SetValid(params)
  634. self.canvas.Refresh()
  635. if dcmd:
  636. layer.SetProperties(params, propwin)
  637. self.SetStatusText(layer.GetLog(), 0)
  638. def AddLine(self, rel):
  639. """!Add connection between model objects
  640. @param rel relation
  641. """
  642. fromShape = rel.GetFrom()
  643. toShape = rel.GetTo()
  644. rel.SetCanvas(self)
  645. rel.SetPen(wx.BLACK_PEN)
  646. rel.SetBrush(wx.BLACK_BRUSH)
  647. rel.AddArrow(ogl.ARROW_ARROW)
  648. points = rel.GetControlPoints()
  649. rel.MakeLineControlPoints(2)
  650. if points:
  651. for x, y in points:
  652. rel.InsertLineControlPoint(point = wx.RealPoint(x, y))
  653. self._addEvent(rel)
  654. try:
  655. fromShape.AddLine(rel, toShape)
  656. except TypeError:
  657. pass # bug when connecting ModelCondition and ModelLoop - to be fixed
  658. self.canvas.diagram.AddShape(rel)
  659. rel.Show(True)
  660. def LoadModelFile(self, filename):
  661. """!Load model definition stored in GRASS Model XML file (gxm)
  662. """
  663. try:
  664. self.model.LoadModel(filename)
  665. except GException, e:
  666. GError(parent = self,
  667. message = _("Reading model file <%s> failed.\n"
  668. "Invalid file, unable to parse XML document.\n\n%s") % \
  669. (filename, e),
  670. showTraceback = False)
  671. return
  672. self.modelFile = filename
  673. self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
  674. self.SetStatusText(_("Please wait, loading model..."), 0)
  675. # load actions
  676. for item in self.model.GetItems(objType = ModelAction):
  677. self._addEvent(item)
  678. self.canvas.diagram.AddShape(item)
  679. item.Show(True)
  680. # relations/data
  681. for rel in item.GetRelations():
  682. if rel.GetFrom() == item:
  683. dataItem = rel.GetTo()
  684. else:
  685. dataItem = rel.GetFrom()
  686. self._addEvent(dataItem)
  687. self.canvas.diagram.AddShape(dataItem)
  688. self.AddLine(rel)
  689. dataItem.Show(True)
  690. # load loops
  691. for item in self.model.GetItems(objType = ModelLoop):
  692. self._addEvent(item)
  693. self.canvas.diagram.AddShape(item)
  694. item.Show(True)
  695. # connect items in the loop
  696. self.DefineLoop(item)
  697. # load conditions
  698. for item in self.model.GetItems(objType = ModelCondition):
  699. self._addEvent(item)
  700. self.canvas.diagram.AddShape(item)
  701. item.Show(True)
  702. # connect items in the condition
  703. self.DefineCondition(item)
  704. # load variables
  705. self.variablePanel.Update()
  706. self.itemPanel.Update()
  707. self.SetStatusText('', 0)
  708. # final updates
  709. for action in self.model.GetItems(objType = ModelAction):
  710. action.SetValid(action.GetParams())
  711. action.Update()
  712. self.canvas.Refresh(True)
  713. def WriteModelFile(self, filename):
  714. """!Save model to model file, recover original file on error.
  715. @return True on success
  716. @return False on failure
  717. """
  718. self.ModelChanged(False)
  719. tmpfile = tempfile.TemporaryFile(mode='w+b')
  720. try:
  721. WriteModelFile(fd = tmpfile, model = self.model)
  722. except StandardError:
  723. GError(parent = self,
  724. message = _("Writing current settings to model file failed."))
  725. return False
  726. try:
  727. mfile = open(filename, "w")
  728. tmpfile.seek(0)
  729. for line in tmpfile.readlines():
  730. mfile.write(line)
  731. except IOError:
  732. wx.MessageBox(parent = self,
  733. message = _("Unable to open file <%s> for writing.") % filename,
  734. caption = _("Error"),
  735. style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
  736. return False
  737. mfile.close()
  738. return True
  739. def DefineLoop(self, loop):
  740. """!Define loop with given list of items"""
  741. parent = loop
  742. items = loop.GetItems()
  743. if not items:
  744. return
  745. # remove defined relations first
  746. for rel in loop.GetRelations():
  747. self.canvas.GetDiagram().RemoveShape(rel)
  748. loop.Clear()
  749. for item in items:
  750. rel = ModelRelation(parent = self, fromShape = parent, toShape = item)
  751. dx = item.GetX() - parent.GetX()
  752. dy = item.GetY() - parent.GetY()
  753. loop.AddRelation(rel)
  754. if dx != 0:
  755. rel.SetControlPoints(((parent.GetX(), parent.GetY() + dy / 2),
  756. (parent.GetX() + dx, parent.GetY() + dy / 2)))
  757. self.AddLine(rel)
  758. parent = item
  759. # close loop
  760. item = loop.GetItems()[-1]
  761. rel = ModelRelation(parent = self, fromShape = item, toShape = loop)
  762. loop.AddRelation(rel)
  763. self.AddLine(rel)
  764. dx = (item.GetX() - loop.GetX()) + loop.GetWidth() / 2 + 50
  765. dy = item.GetHeight() / 2 + 50
  766. rel.MakeLineControlPoints(0)
  767. rel.InsertLineControlPoint(point = wx.RealPoint(loop.GetX() - loop.GetWidth() / 2 ,
  768. loop.GetY()))
  769. rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX(),
  770. item.GetY() + item.GetHeight() / 2))
  771. rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX(),
  772. item.GetY() + dy))
  773. rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - dx,
  774. item.GetY() + dy))
  775. rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - dx,
  776. loop.GetY()))
  777. self.canvas.Refresh()
  778. def DefineCondition(self, condition):
  779. """!Define if-else statement with given list of items"""
  780. parent = condition
  781. items = condition.GetItems()
  782. if not items['if'] and not items['else']:
  783. return
  784. # remove defined relations first
  785. for rel in condition.GetRelations():
  786. self.canvas.GetDiagram().RemoveShape(rel)
  787. condition.Clear()
  788. dxIf = condition.GetX() + condition.GetWidth() / 2
  789. dxElse = condition.GetX() - condition.GetWidth() / 2
  790. dy = condition.GetY()
  791. for branch in items.keys():
  792. for item in items[branch]:
  793. rel = ModelRelation(parent = self, fromShape = parent,
  794. toShape = item)
  795. condition.AddRelation(rel)
  796. self.AddLine(rel)
  797. rel.MakeLineControlPoints(0)
  798. if branch == 'if':
  799. rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - item.GetWidth() / 2, item.GetY()))
  800. rel.InsertLineControlPoint(point = wx.RealPoint(dxIf, dy))
  801. else:
  802. rel.InsertLineControlPoint(point = wx.RealPoint(dxElse, dy))
  803. rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - item.GetWidth() / 2, item.GetY()))
  804. parent = item
  805. self.canvas.Refresh()
  806. class ModelCanvas(ogl.ShapeCanvas):
  807. """!Canvas where model is drawn"""
  808. def __init__(self, parent):
  809. self.parent = parent
  810. ogl.OGLInitialize()
  811. ogl.ShapeCanvas.__init__(self, parent)
  812. self.diagram = ogl.Diagram()
  813. self.SetDiagram(self.diagram)
  814. self.diagram.SetCanvas(self)
  815. self.SetScrollbars(20, 20, 1000/20, 1000/20)
  816. self.Bind(wx.EVT_CHAR, self.OnChar)
  817. def OnChar(self, event):
  818. """!Key pressed"""
  819. kc = event.GetKeyCode()
  820. diagram = self.GetDiagram()
  821. if kc == wx.WXK_DELETE:
  822. self.RemoveSelected()
  823. def RemoveSelected(self):
  824. """!Remove selected shapes"""
  825. self.parent.ModelChanged()
  826. diagram = self.GetDiagram()
  827. for shape in diagram.GetShapeList():
  828. if not shape.Selected():
  829. continue
  830. remList, upList = self.parent.GetModel().RemoveItem(shape)
  831. shape.Select(False)
  832. diagram.RemoveShape(shape)
  833. shape.__del__()
  834. for item in remList:
  835. diagram.RemoveShape(item)
  836. item.__del__()
  837. for item in upList:
  838. item.Update()
  839. self.Refresh()
  840. def GetNewShapePos(self):
  841. """!Determine optimal position for newly added object
  842. @return x,y
  843. """
  844. xNew, yNew = map(lambda x: x / 2, self.GetSize())
  845. diagram = self.GetDiagram()
  846. for shape in diagram.GetShapeList():
  847. y = shape.GetY()
  848. yBox = shape.GetBoundingBoxMin()[1] / 2
  849. if yBox > 0 and y < yNew + yBox and y > yNew - yBox:
  850. yNew += yBox * 3
  851. return xNew, yNew
  852. class ModelEvtHandler(ogl.ShapeEvtHandler):
  853. """!Model event handler class"""
  854. def __init__(self, log, frame):
  855. ogl.ShapeEvtHandler.__init__(self)
  856. self.log = log
  857. self.frame = frame
  858. self.x = self.y = None
  859. def OnLeftClick(self, x, y, keys = 0, attachment = 0):
  860. """!Left mouse button pressed -> select item & update statusbar"""
  861. shape = self.GetShape()
  862. canvas = shape.GetCanvas()
  863. dc = wx.ClientDC(canvas)
  864. canvas.PrepareDC(dc)
  865. if hasattr(self.frame, 'defineRelation'):
  866. drel = self.frame.defineRelation
  867. if drel['from'] is None:
  868. drel['from'] = shape
  869. elif drel['to'] is None:
  870. drel['to'] = shape
  871. rel = ModelRelation(parent = self.frame, fromShape = drel['from'],
  872. toShape = drel['to'])
  873. dlg = ModelRelationDialog(parent = self.frame,
  874. shape = rel)
  875. if dlg.IsValid():
  876. ret = dlg.ShowModal()
  877. if ret == wx.ID_OK:
  878. option = dlg.GetOption()
  879. rel.SetName(option)
  880. drel['from'].AddRelation(rel)
  881. drel['to'].AddRelation(rel)
  882. drel['from'].Update()
  883. params = { 'params' : [{ 'name' : option,
  884. 'value' : drel['from'].GetValue()}] }
  885. drel['to'].MergeParams(params)
  886. self.frame.AddLine(rel)
  887. dlg.Destroy()
  888. del self.frame.defineRelation
  889. # select object
  890. self._onSelectShape(shape)
  891. if hasattr(shape, "GetLog"):
  892. self.log.SetStatusText(shape.GetLog(), 0)
  893. else:
  894. self.log.SetStatusText('', 0)
  895. def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0):
  896. """!Left mouse button pressed (double-click) -> show properties"""
  897. self.OnProperties()
  898. def OnProperties(self, event = None):
  899. """!Show properties dialog"""
  900. self.frame.ModelChanged()
  901. shape = self.GetShape()
  902. if isinstance(shape, ModelAction):
  903. module = GUI(parent = self.frame, show = True).ParseCommand(shape.GetLog(string = False),
  904. completed = (self.frame.GetOptData, shape, shape.GetParams()))
  905. elif isinstance(shape, ModelData):
  906. dlg = ModelDataDialog(parent = self.frame, shape = shape)
  907. shape.SetPropDialog(dlg)
  908. dlg.CentreOnParent()
  909. dlg.Show()
  910. elif isinstance(shape, ModelLoop):
  911. dlg = ModelLoopDialog(parent = self.frame, shape = shape)
  912. dlg.CentreOnParent()
  913. if dlg.ShowModal() == wx.ID_OK:
  914. shape.SetText(dlg.GetCondition())
  915. alist = list()
  916. ids = dlg.GetItems()
  917. for aId in ids['unchecked']:
  918. action = self.frame.GetModel().GetItem(aId)
  919. action.UnSetBlock(shape)
  920. for aId in ids['checked']:
  921. action = self.frame.GetModel().GetItem(aId)
  922. action.SetBlock(shape)
  923. if action:
  924. alist.append(action)
  925. shape.SetItems(alist)
  926. self.frame.DefineLoop(shape)
  927. self.frame.SetStatusText(shape.GetLog(), 0)
  928. self.frame.GetCanvas().Refresh()
  929. dlg.Destroy()
  930. elif isinstance(shape, ModelCondition):
  931. dlg = ModelConditionDialog(parent = self.frame, shape = shape)
  932. dlg.CentreOnParent()
  933. if dlg.ShowModal() == wx.ID_OK:
  934. shape.SetText(dlg.GetCondition())
  935. ids = dlg.GetItems()
  936. for b in ids.keys():
  937. alist = list()
  938. for aId in ids[b]['unchecked']:
  939. action = self.frame.GetModel().GetItem(aId)
  940. action.UnSetBlock(shape)
  941. for aId in ids[b]['checked']:
  942. action = self.frame.GetModel().GetItem(aId)
  943. action.SetBlock(shape)
  944. if action:
  945. alist.append(action)
  946. shape.SetItems(alist, branch = b)
  947. self.frame.DefineCondition(shape)
  948. self.frame.GetCanvas().Refresh()
  949. dlg.Destroy()
  950. def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
  951. """!Drag shape (begining)"""
  952. self.frame.ModelChanged()
  953. if self._previousHandler:
  954. self._previousHandler.OnBeginDragLeft(x, y, keys, attachment)
  955. def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
  956. """!Drag shape (end)"""
  957. if self._previousHandler:
  958. self._previousHandler.OnEndDragLeft(x, y, keys, attachment)
  959. shape = self.GetShape()
  960. if isinstance(shape, ModelLoop):
  961. self.frame.DefineLoop(shape)
  962. elif isinstance(shape, ModelCondition):
  963. self.frame.DefineCondition(shape)
  964. for mo in shape.GetBlock():
  965. if isinstance(mo, ModelLoop):
  966. self.frame.DefineLoop(mo)
  967. elif isinstance(mo, ModelCondition):
  968. self.frame.DefineCondition(mo)
  969. def OnEndSize(self, x, y):
  970. """!Resize shape"""
  971. self.frame.ModelChanged()
  972. if self._previousHandler:
  973. self._previousHandler.OnEndSize(x, y)
  974. def OnRightClick(self, x, y, keys = 0, attachment = 0):
  975. """!Right click -> pop-up menu"""
  976. if not hasattr (self, "popupID"):
  977. self.popupID = dict()
  978. for key in ('remove', 'enable', 'addPoint',
  979. 'delPoint', 'intermediate', 'props', 'id'):
  980. self.popupID[key] = wx.NewId()
  981. # record coordinates
  982. self.x = x
  983. self.y = y
  984. # select object
  985. shape = self.GetShape()
  986. self._onSelectShape(shape)
  987. popupMenu = wx.Menu()
  988. popupMenu.Append(self.popupID['remove'], text=_('Remove'))
  989. self.frame.Bind(wx.EVT_MENU, self.OnRemove, id = self.popupID['remove'])
  990. if isinstance(shape, ModelAction) or isinstance(shape, ModelLoop):
  991. if shape.IsEnabled():
  992. popupMenu.Append(self.popupID['enable'], text=_('Disable'))
  993. self.frame.Bind(wx.EVT_MENU, self.OnDisable, id = self.popupID['enable'])
  994. else:
  995. popupMenu.Append(self.popupID['enable'], text=_('Enable'))
  996. self.frame.Bind(wx.EVT_MENU, self.OnEnable, id = self.popupID['enable'])
  997. if isinstance(shape, ModelRelation):
  998. popupMenu.AppendSeparator()
  999. popupMenu.Append(self.popupID['addPoint'], text=_('Add control point'))
  1000. self.frame.Bind(wx.EVT_MENU, self.OnAddPoint, id = self.popupID['addPoint'])
  1001. popupMenu.Append(self.popupID['delPoint'], text=_('Remove control point'))
  1002. self.frame.Bind(wx.EVT_MENU, self.OnRemovePoint, id = self.popupID['delPoint'])
  1003. if len(shape.GetLineControlPoints()) == 2:
  1004. popupMenu.Enable(self.popupID['delPoint'], False)
  1005. if isinstance(shape, ModelData) and '@' not in shape.GetValue():
  1006. popupMenu.AppendSeparator()
  1007. popupMenu.Append(self.popupID['intermediate'], text=_('Intermediate'),
  1008. kind = wx.ITEM_CHECK)
  1009. if self.GetShape().IsIntermediate():
  1010. popupMenu.Check(self.popupID['intermediate'], True)
  1011. self.frame.Bind(wx.EVT_MENU, self.OnIntermediate, id = self.popupID['intermediate'])
  1012. if isinstance(shape, ModelData) or \
  1013. isinstance(shape, ModelAction) or \
  1014. isinstance(shape, ModelLoop):
  1015. popupMenu.AppendSeparator()
  1016. popupMenu.Append(self.popupID['props'], text=_('Properties'))
  1017. self.frame.Bind(wx.EVT_MENU, self.OnProperties, id = self.popupID['props'])
  1018. self.frame.PopupMenu(popupMenu)
  1019. popupMenu.Destroy()
  1020. def OnDisable(self, event):
  1021. """!Disable action"""
  1022. self._onEnable(False)
  1023. def OnEnable(self, event):
  1024. """!Disable action"""
  1025. self._onEnable(True)
  1026. def _onEnable(self, enable):
  1027. shape = self.GetShape()
  1028. shape.Enable(enable)
  1029. self.frame.ModelChanged()
  1030. self.frame.canvas.Refresh()
  1031. def _onSelectShape(self, shape):
  1032. canvas = shape.GetCanvas()
  1033. dc = wx.ClientDC(canvas)
  1034. if shape.Selected():
  1035. shape.Select(False, dc)
  1036. else:
  1037. redraw = False
  1038. shapeList = canvas.GetDiagram().GetShapeList()
  1039. toUnselect = list()
  1040. for s in shapeList:
  1041. if s.Selected():
  1042. toUnselect.append(s)
  1043. shape.Select(True, dc)
  1044. for s in toUnselect:
  1045. s.Select(False, dc)
  1046. canvas.Refresh(False)
  1047. def OnAddPoint(self, event):
  1048. """!Add control point"""
  1049. shape = self.GetShape()
  1050. shape.InsertLineControlPoint(point = wx.RealPoint(self.x, self.y))
  1051. shape.ResetShapes()
  1052. shape.Select(True)
  1053. self.frame.ModelChanged()
  1054. self.frame.canvas.Refresh()
  1055. def OnRemovePoint(self, event):
  1056. """!Remove control point"""
  1057. shape = self.GetShape()
  1058. shape.DeleteLineControlPoint()
  1059. shape.Select(False)
  1060. shape.Select(True)
  1061. self.frame.ModelChanged()
  1062. self.frame.canvas.Refresh()
  1063. def OnIntermediate(self, event):
  1064. """!Mark data as intermediate"""
  1065. self.frame.ModelChanged()
  1066. shape = self.GetShape()
  1067. shape.SetIntermediate(event.IsChecked())
  1068. self.frame.canvas.Refresh()
  1069. def OnRemove(self, event):
  1070. """!Remove shape
  1071. """
  1072. self.frame.GetCanvas().RemoveSelected()
  1073. self.frame.itemPanel.Update()
  1074. class VariablePanel(wx.Panel):
  1075. def __init__(self, parent, id = wx.ID_ANY,
  1076. **kwargs):
  1077. """!Manage model variables panel
  1078. """
  1079. self.parent = parent
  1080. wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
  1081. self.listBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
  1082. label=" %s " % _("List of variables - right-click to delete"))
  1083. self.list = VariableListCtrl(parent = self,
  1084. columns = [_("Name"), _("Data type"),
  1085. _("Default value"), _("Description")])
  1086. # add new category
  1087. self.addBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
  1088. label = " %s " % _("Add new variable"))
  1089. self.name = wx.TextCtrl(parent = self, id = wx.ID_ANY)
  1090. wx.CallAfter(self.name.SetFocus)
  1091. self.type = wx.Choice(parent = self, id = wx.ID_ANY,
  1092. choices = [_("integer"),
  1093. _("float"),
  1094. _("string"),
  1095. _("raster"),
  1096. _("vector"),
  1097. _("mapset"),
  1098. _("file")])
  1099. self.type.SetSelection(2) # string
  1100. self.value = wx.TextCtrl(parent = self, id = wx.ID_ANY)
  1101. self.desc = wx.TextCtrl(parent = self, id = wx.ID_ANY)
  1102. # buttons
  1103. self.btnAdd = wx.Button(parent = self, id = wx.ID_ADD)
  1104. self.btnAdd.SetToolTipString(_("Add new variable to the model"))
  1105. self.btnAdd.Enable(False)
  1106. # bindings
  1107. self.name.Bind(wx.EVT_TEXT, self.OnText)
  1108. self.value.Bind(wx.EVT_TEXT, self.OnText)
  1109. self.desc.Bind(wx.EVT_TEXT, self.OnText)
  1110. self.btnAdd.Bind(wx.EVT_BUTTON, self.OnAdd)
  1111. self._layout()
  1112. def _layout(self):
  1113. """!Layout dialog"""
  1114. listSizer = wx.StaticBoxSizer(self.listBox, wx.VERTICAL)
  1115. listSizer.Add(item = self.list, proportion = 1,
  1116. flag = wx.EXPAND)
  1117. addSizer = wx.StaticBoxSizer(self.addBox, wx.VERTICAL)
  1118. gridSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
  1119. gridSizer.AddGrowableCol(1)
  1120. gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
  1121. label = "%s:" % _("Name")),
  1122. flag = wx.ALIGN_CENTER_VERTICAL,
  1123. pos = (0, 0))
  1124. gridSizer.Add(item = self.name,
  1125. pos = (0, 1),
  1126. flag = wx.EXPAND)
  1127. gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
  1128. label = "%s:" % _("Data type")),
  1129. flag = wx.ALIGN_CENTER_VERTICAL,
  1130. pos = (0, 2))
  1131. gridSizer.Add(item = self.type,
  1132. pos = (0, 3))
  1133. gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
  1134. label = "%s:" % _("Default value")),
  1135. flag = wx.ALIGN_CENTER_VERTICAL,
  1136. pos = (1, 0))
  1137. gridSizer.Add(item = self.value,
  1138. pos = (1, 1), span = (1, 3),
  1139. flag = wx.EXPAND)
  1140. gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
  1141. label = "%s:" % _("Description")),
  1142. flag = wx.ALIGN_CENTER_VERTICAL,
  1143. pos = (2, 0))
  1144. gridSizer.Add(item = self.desc,
  1145. pos = (2, 1), span = (1, 3),
  1146. flag = wx.EXPAND)
  1147. addSizer.Add(item = gridSizer,
  1148. flag = wx.EXPAND)
  1149. addSizer.Add(item = self.btnAdd, proportion = 0,
  1150. flag = wx.TOP | wx.ALIGN_RIGHT, border = 5)
  1151. mainSizer = wx.BoxSizer(wx.VERTICAL)
  1152. mainSizer.Add(item = listSizer, proportion = 1,
  1153. flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
  1154. mainSizer.Add(item = addSizer, proportion = 0,
  1155. flag = wx.EXPAND | wx.ALIGN_CENTER |
  1156. wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
  1157. self.SetSizer(mainSizer)
  1158. mainSizer.Fit(self)
  1159. def OnText(self, event):
  1160. """!Text entered"""
  1161. if self.name.GetValue():
  1162. self.btnAdd.Enable()
  1163. else:
  1164. self.btnAdd.Enable(False)
  1165. def OnAdd(self, event):
  1166. """!Add new variable to the list"""
  1167. msg = self.list.Append(self.name.GetValue(),
  1168. self.type.GetStringSelection(),
  1169. self.value.GetValue(),
  1170. self.desc.GetValue())
  1171. self.name.SetValue('')
  1172. self.name.SetFocus()
  1173. if msg:
  1174. GError(parent = self,
  1175. message = msg)
  1176. else:
  1177. self.type.SetSelection(2) # string
  1178. self.value.SetValue('')
  1179. self.desc.SetValue('')
  1180. self.UpdateModelVariables()
  1181. def UpdateModelVariables(self):
  1182. """!Update model variables"""
  1183. variables = dict()
  1184. for values in self.list.GetData().itervalues():
  1185. name = values[0]
  1186. variables[name] = { 'type' : str(values[1]) }
  1187. if values[2]:
  1188. variables[name]['value'] = values[2]
  1189. if values[3]:
  1190. variables[name]['description'] = values[3]
  1191. self.parent.GetModel().SetVariables(variables)
  1192. self.parent.ModelChanged()
  1193. def Update(self):
  1194. """!Reload list of variables"""
  1195. self.list.OnReload(None)
  1196. def Reset(self):
  1197. """!Remove all variables"""
  1198. self.list.DeleteAllItems()
  1199. self.parent.GetModel().SetVariables([])
  1200. class ItemPanel(wx.Panel):
  1201. def __init__(self, parent, id = wx.ID_ANY,
  1202. **kwargs):
  1203. """!Manage model items
  1204. """
  1205. self.parent = parent
  1206. wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
  1207. self.listBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
  1208. label=" %s " % _("List of items - right-click to delete"))
  1209. self.list = ItemListCtrl(parent = self,
  1210. columns = [_("ID"), _("Name"), _("In block"),
  1211. _("Command / Condition")])
  1212. self._layout()
  1213. def _layout(self):
  1214. """!Layout dialog"""
  1215. listSizer = wx.StaticBoxSizer(self.listBox, wx.VERTICAL)
  1216. listSizer.Add(item = self.list, proportion = 1,
  1217. flag = wx.EXPAND)
  1218. mainSizer = wx.BoxSizer(wx.VERTICAL)
  1219. mainSizer.Add(item = listSizer, proportion = 1,
  1220. flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
  1221. self.SetSizer(mainSizer)
  1222. mainSizer.Fit(self)
  1223. def Update(self):
  1224. """!Reload list of variables"""
  1225. self.list.OnReload(None)
  1226. class PythonPanel(wx.Panel):
  1227. def __init__(self, parent, id = wx.ID_ANY,
  1228. **kwargs):
  1229. """!Model as python script
  1230. """
  1231. self.parent = parent
  1232. wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
  1233. self.filename = None # temp file to run
  1234. self.bodyBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
  1235. label = " %s " % _("Python script"))
  1236. self.body = PyStc(parent = self, statusbar = self.parent.GetStatusBar())
  1237. self.btnRun = wx.Button(parent = self, id = wx.ID_ANY, label = _("&Run"))
  1238. self.btnRun.SetToolTipString(_("Run python script"))
  1239. self.Bind(wx.EVT_BUTTON, self.OnRun, self.btnRun)
  1240. self.btnSaveAs = wx.Button(parent = self, id = wx.ID_SAVEAS)
  1241. self.btnSaveAs.SetToolTipString(_("Save python script to file"))
  1242. self.Bind(wx.EVT_BUTTON, self.OnSaveAs, self.btnSaveAs)
  1243. self.btnRefresh = wx.Button(parent = self, id = wx.ID_REFRESH)
  1244. self.btnRefresh.SetToolTipString(_("Refresh python script based on the model.\n"
  1245. "It will discards all local changes."))
  1246. self.Bind(wx.EVT_BUTTON, self.OnRefresh, self.btnRefresh)
  1247. self._layout()
  1248. def _layout(self):
  1249. sizer = wx.BoxSizer(wx.VERTICAL)
  1250. bodySizer = wx.StaticBoxSizer(self.bodyBox, wx.HORIZONTAL)
  1251. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  1252. bodySizer.Add(item = self.body, proportion = 1,
  1253. flag = wx.EXPAND | wx.ALL, border = 3)
  1254. btnSizer.Add(item = self.btnRefresh, proportion = 0,
  1255. flag = wx.LEFT | wx.RIGHT, border = 5)
  1256. btnSizer.AddStretchSpacer()
  1257. btnSizer.Add(item = self.btnSaveAs, proportion = 0,
  1258. flag = wx.RIGHT | wx.ALIGN_RIGHT, border = 5)
  1259. btnSizer.Add(item = self.btnRun, proportion = 0,
  1260. flag = wx.RIGHT | wx.ALIGN_RIGHT, border = 5)
  1261. sizer.Add(item = bodySizer, proportion = 1,
  1262. flag = wx.EXPAND | wx.ALL, border = 3)
  1263. sizer.Add(item = btnSizer, proportion = 0,
  1264. flag = wx.EXPAND | wx.ALL, border = 3)
  1265. sizer.Fit(self)
  1266. sizer.SetSizeHints(self)
  1267. self.SetSizer(sizer)
  1268. def OnRun(self, event):
  1269. """!Run Python script"""
  1270. self.filename = grass.tempfile()
  1271. try:
  1272. fd = open(self.filename, "w")
  1273. fd.write(self.body.GetText())
  1274. except IOError, e:
  1275. GError(_("Unable to launch Python script. %s") % e,
  1276. parent = self)
  1277. return
  1278. finally:
  1279. fd.close()
  1280. mode = stat.S_IMODE(os.lstat(self.filename)[stat.ST_MODE])
  1281. os.chmod(self.filename, mode | stat.S_IXUSR)
  1282. self.parent.goutput.RunCmd([fd.name], switchPage = True,
  1283. skipInterface = True, onDone = self.OnDone)
  1284. event.Skip()
  1285. def OnDone(self, cmd, returncode):
  1286. """!Python script finished"""
  1287. grass.try_remove(self.filename)
  1288. self.filename = None
  1289. def SaveAs(self, force = False):
  1290. """!Save python script to file
  1291. @return filename
  1292. """
  1293. filename = ''
  1294. dlg = wx.FileDialog(parent = self,
  1295. message = _("Choose file to save"),
  1296. defaultDir = os.getcwd(),
  1297. wildcard = _("Python script (*.py)|*.py"),
  1298. style = wx.FD_SAVE)
  1299. if dlg.ShowModal() == wx.ID_OK:
  1300. filename = dlg.GetPath()
  1301. if not filename:
  1302. return ''
  1303. # check for extension
  1304. if filename[-3:] != ".py":
  1305. filename += ".py"
  1306. if os.path.exists(filename):
  1307. dlg = wx.MessageDialog(self, message=_("File <%s> already exists. "
  1308. "Do you want to overwrite this file?") % filename,
  1309. caption=_("Save file"),
  1310. style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
  1311. if dlg.ShowModal() == wx.ID_NO:
  1312. dlg.Destroy()
  1313. return ''
  1314. dlg.Destroy()
  1315. fd = open(filename, "w")
  1316. try:
  1317. if force:
  1318. WritePythonFile(fd, self.parent.GetModel())
  1319. else:
  1320. fd.write(self.body.GetText())
  1321. finally:
  1322. fd.close()
  1323. # executable file
  1324. os.chmod(filename, stat.S_IRWXU | stat.S_IWUSR)
  1325. return filename
  1326. def OnSaveAs(self, event):
  1327. """!Save python script to file"""
  1328. self.SaveAs(force = False)
  1329. event.Skip()
  1330. def RefreshScript(self):
  1331. """!Refresh Python script
  1332. @return True on refresh
  1333. @return False script hasn't been updated
  1334. """
  1335. if self.body.modified:
  1336. dlg = wx.MessageDialog(self,
  1337. message = _("Python script is locally modificated. "
  1338. "Refresh will discard all changes. "
  1339. "Do you really want to continue?"),
  1340. caption=_("Update"),
  1341. style = wx.YES_NO | wx.NO_DEFAULT |
  1342. wx.ICON_QUESTION | wx.CENTRE)
  1343. ret = dlg.ShowModal()
  1344. dlg.Destroy()
  1345. if ret == wx.ID_NO:
  1346. return False
  1347. fd = tempfile.TemporaryFile()
  1348. WritePythonFile(fd, self.parent.GetModel())
  1349. fd.seek(0)
  1350. self.body.SetText(fd.read())
  1351. fd.close()
  1352. self.body.modified = False
  1353. return True
  1354. def OnRefresh(self, event):
  1355. """!Refresh Python script"""
  1356. if self.RefreshScript():
  1357. self.parent.SetStatusText(_('Python script is up-to-date'), 0)
  1358. event.Skip()
  1359. def IsModified(self):
  1360. """!Check if python script has been modified"""
  1361. return self.body.modified
  1362. def IsEmpty(self):
  1363. """!Check if python script is empty"""
  1364. return len(self.body.GetText()) == 0