frame.py 54 KB

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