gmodeler.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974
  1. """!
  2. @package gmodeler.py
  3. @brief Graphical modeler to create edit, and manage models
  4. Classes:
  5. - ModelFrame
  6. - ModelCanvas
  7. - ModelAction
  8. - ModelSearchDialog
  9. - ModelData
  10. - ProcessModelFile
  11. - WriteModelFile
  12. (C) 2010 by the GRASS Development Team
  13. This program is free software under the GNU General Public License
  14. (>=v2). Read the file COPYING that comes with GRASS for details.
  15. @author Martin Landa <landa.martin gmail.com>
  16. """
  17. import os
  18. import sys
  19. import shlex
  20. import time
  21. import traceback
  22. try:
  23. import xml.etree.ElementTree as etree
  24. except ImportError:
  25. import elementtree.ElementTree as etree # Python <= 2.4
  26. import globalvar
  27. if not os.getenv("GRASS_WXBUNDLED"):
  28. globalvar.CheckForWx()
  29. import wx
  30. import wx.lib.ogl as ogl
  31. import wx.lib.flatnotebook as FN
  32. import menu
  33. import menudata
  34. import toolbars
  35. import menuform
  36. import prompt
  37. import utils
  38. import goutput
  39. from debug import Debug
  40. from gcmd import GMessage
  41. from grass.script import core as grass
  42. class ModelFrame(wx.Frame):
  43. def __init__(self, parent, id = wx.ID_ANY, title = _("Graphical modeler (under development)"), **kwargs):
  44. """!Graphical modeler main window
  45. @param parent parent window
  46. @param id window id
  47. @param title window title
  48. @param kwargs wx.Frames' arguments
  49. """
  50. self.parent = parent
  51. self.searchDialog = None # module search dialog
  52. self.actions = list() # list of recorded actions
  53. self.data = list() # list of recorded data items
  54. self.baseTitle = title
  55. self.modelFile = None # loaded model
  56. wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
  57. self.SetName("Modeler")
  58. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  59. self.menubar = menu.Menu(parent = self, data = menudata.ModelerData())
  60. self.SetMenuBar(self.menubar)
  61. self.toolbar = toolbars.ModelToolbar(parent = self)
  62. self.SetToolBar(self.toolbar)
  63. self.statusbar = self.CreateStatusBar(number = 1)
  64. self.notebook = FN.FlatNotebook(parent = self, id = wx.ID_ANY,
  65. style = FN.FNB_FANCY_TABS | FN.FNB_BOTTOM |
  66. FN.FNB_NO_NAV_BUTTONS | FN.FNB_NO_X_BUTTON)
  67. self.canvas = ModelCanvas(self)
  68. self.canvas.SetBackgroundColour(wx.WHITE)
  69. self.goutput = goutput.GMConsole(parent = self, pageid = 1)
  70. self.modelPage = self.notebook.AddPage(self.canvas, text=_('Model'))
  71. self.commandPage = self.notebook.AddPage(self.goutput, text=_('Command output'))
  72. self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
  73. self._layout()
  74. self.SetMinSize((640, 480))
  75. def _layout(self):
  76. """!Do layout"""
  77. sizer = wx.BoxSizer(wx.VERTICAL)
  78. sizer.Add(item = self.notebook, proportion = 1,
  79. flag = wx.EXPAND)
  80. self.SetAutoLayout(True)
  81. self.SetSizer(sizer)
  82. sizer.Fit(self)
  83. self.Layout()
  84. def _addEvent(self, item):
  85. """!Add event to item"""
  86. evthandler = ModelEvtHandler(self.statusbar,
  87. self)
  88. evthandler.SetShape(item)
  89. evthandler.SetPreviousHandler(item.GetEventHandler())
  90. item.SetEventHandler(evthandler)
  91. def OnCloseWindow(self, event):
  92. """!Close window"""
  93. self.Destroy()
  94. def OnModelNew(self, event):
  95. """!Create new model"""
  96. pass
  97. def OnModelOpen(self, event):
  98. """!Load model from file"""
  99. filename = ''
  100. dlg = wx.FileDialog(parent = self, message=_("Choose model file"),
  101. defaultDir = os.getcwd(),
  102. wildcard=_("GRASS Model File (*.gxm)|*.gxm"))
  103. if dlg.ShowModal() == wx.ID_OK:
  104. filename = dlg.GetPath()
  105. if not filename:
  106. return
  107. Debug.msg(4, "ModelFrame.OnModelOpen(): filename=%s" % filename)
  108. # close current model
  109. ### self.OnModelClose()
  110. self.LoadModelFile(filename)
  111. self.modelFile = filename
  112. self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
  113. self.SetStatusText(_('%d actions loaded into model') % len(self.actions), 0)
  114. def OnModelSave(self, event):
  115. """!Save model to file"""
  116. if self.modelFile:
  117. dlg = wx.MessageDialog(self, message=_("Model file <%s> already exists. "
  118. "Do you want to overwrite this file?") % \
  119. self.modelFile,
  120. caption=_("Save model"),
  121. style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
  122. if dlg.ShowModal() == wx.ID_NO:
  123. dlg.Destroy()
  124. else:
  125. Debug.msg(4, "ModelFrame.OnModelSave(): filename=%s" % self.modelFile)
  126. self.WriteModelFile(self.modelFile)
  127. self.SetStatusText(_('File <%s> saved') % self.modelFile, 0)
  128. else:
  129. self.OnModelSaveAs(None)
  130. def OnModelSaveAs(self, event):
  131. """!Create model to file as"""
  132. filename = ''
  133. dlg = wx.FileDialog(parent = self,
  134. message = _("Choose file to save current model"),
  135. defaultDir = os.getcwd(),
  136. wildcard=_("GRASS Model File (*.gxm)|*.gxm"),
  137. style=wx.FD_SAVE)
  138. if dlg.ShowModal() == wx.ID_OK:
  139. filename = dlg.GetPath()
  140. if not filename:
  141. return
  142. # check for extension
  143. if filename[-4:] != ".gxm":
  144. filename += ".gxm"
  145. if os.path.exists(filename):
  146. dlg = wx.MessageDialog(parent = self,
  147. message=_("Model file <%s> already exists. "
  148. "Do you want to overwrite this file?") % filename,
  149. caption=_("File already exists"),
  150. style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
  151. if dlg.ShowModal() != wx.ID_YES:
  152. dlg.Destroy()
  153. return
  154. Debug.msg(4, "GMFrame.OnModelSaveAs(): filename=%s" % filename)
  155. self.WriteModelFile(filename)
  156. self.modelFile = filename
  157. self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
  158. self.SetStatusText(_('File <%s> saved') % self.modelFile, 0)
  159. def OnRunModel(self, event):
  160. """!Run entire model"""
  161. if len(self.actions) < 1:
  162. GMessage(parent = self,
  163. message = _('Model is empty. Nothing to run.'),
  164. msgType = 'info')
  165. return
  166. errList = self._validateModel()
  167. if errList:
  168. dlg = wx.MessageDialog(parent = self,
  169. message = _('Model is not valid. Do you want to '
  170. 'run the model anyway?\n\n%s') % '\n'.join(errList),
  171. caption=_("Run model?"),
  172. style = wx.YES_NO | wx.NO_DEFAULT |
  173. wx.ICON_QUESTION | wx.CENTRE)
  174. ret = dlg.ShowModal()
  175. if ret != wx.ID_YES:
  176. return
  177. for action in self.actions:
  178. self.SetStatusText(_('Running model...'), 0)
  179. self.goutput.RunCmd(command = action.GetLog(string = False),
  180. onDone = self.OnDone)
  181. def OnDone(self, returncode):
  182. """!Computation finished"""
  183. self.SetStatusText('', 0)
  184. def OnValidateModel(self, event, showMsg = True):
  185. """!Validate entire model"""
  186. if len(self.actions) < 1:
  187. GMessage(parent = self,
  188. message = _('Model is empty. Nothing to validate.'),
  189. msgType = 'info')
  190. return
  191. errList = self._validateModel()
  192. if errList:
  193. GMessage(parent = self,
  194. message = _('Model is not valid.\n\n%s') % '\n'.join(errList),
  195. msgType = 'warning')
  196. else:
  197. GMessage(parent = self,
  198. message = _('Model is valid.'),
  199. msgType = 'info')
  200. def _validateModel(self):
  201. """!Validate model"""
  202. self.SetStatusText(_('Validating model...'), 0)
  203. errList = list()
  204. for action in self.actions:
  205. task = menuform.GUI().ParseCommand(cmd = action.GetLog(string = False),
  206. show = None)
  207. errList += task.getCmdError()
  208. self.SetStatusText('', 0)
  209. return errList
  210. def OnRemoveItem(self, event):
  211. """!Remove item from model"""
  212. pass
  213. def OnAddAction(self, event):
  214. """!Add action to model"""
  215. if self.searchDialog is None:
  216. self.searchDialog = ModelSearchDialog(self)
  217. self.searchDialog.CentreOnParent()
  218. else:
  219. self.searchDialog.Reset()
  220. if self.searchDialog.ShowModal() == wx.ID_CANCEL:
  221. self.searchDialog.Hide()
  222. return
  223. cmd = self.searchDialog.GetCmd()
  224. self.searchDialog.Hide()
  225. # add action to canvas
  226. width, height = self.canvas.GetSize()
  227. action = ModelAction(self, cmd = cmd, x = width/2, y = height/2)
  228. self.canvas.diagram.AddShape(action)
  229. action.Show(True)
  230. self._addEvent(action)
  231. self.actions.append(action)
  232. self.canvas.Refresh()
  233. time.sleep(.1)
  234. # show properties dialog
  235. win = action.GetPropDialog()
  236. if not win:
  237. module = menuform.GUI().ParseCommand(action.GetLog(string = False),
  238. completed = (self.GetOptData, action, None),
  239. parentframe = self, show = True)
  240. elif not win.IsShown():
  241. win.Show()
  242. if win:
  243. win.Raise()
  244. def OnAddData(self, event):
  245. """!Add data item to model"""
  246. # add action to canvas
  247. width, height = self.canvas.GetSize()
  248. data = ModelData(self, x = width/2, y = height/2)
  249. self.canvas.diagram.AddShape(data)
  250. data.Show(True)
  251. self._addEvent(data)
  252. self.data.append(data)
  253. self.canvas.Refresh()
  254. def OnHelp(self, event):
  255. """!Display manual page"""
  256. grass.run_command('g.manual',
  257. entry = 'wxGUI.Modeler')
  258. def OnAbout(self, event):
  259. """!Display About window"""
  260. info = wx.AboutDialogInfo()
  261. info.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  262. info.SetName(_('wxGUI Graphical Modeler'))
  263. info.SetWebSite('http://grass.osgeo.org')
  264. info.SetDescription(_('(C) 2010 by the GRASS Development Team\n\n'
  265. 'This program is free software under the GNU General Public License'
  266. '(>=v2). Read the file COPYING that comes with GRASS for details.'))
  267. wx.AboutBox(info)
  268. def GetOptData(self, dcmd, layer, params, propwin):
  269. """!Process action data"""
  270. layer.SetProperties(dcmd, params, propwin)
  271. if params: # add data items
  272. width, height = self.canvas.GetSize()
  273. x = [width/2 + 200, width/2 - 200]
  274. for p in params['params']:
  275. if p.get('value', None) and \
  276. p.get('prompt', '') in ('raster', 'vector', 'raster3d'):
  277. # create data item
  278. data = ModelData(self, name = p.get('name', ''),
  279. value = p.get('value', ''),
  280. prompt = p.get('prompt', ''),
  281. x = x.pop(), y = height/2)
  282. self.canvas.diagram.AddShape(data)
  283. data.Show(True)
  284. self._addEvent(data)
  285. self.data.append(data)
  286. if p.get('age', 'old') == 'old':
  287. self._addLine(data, layer)
  288. data.AddAction(layer, direction = 'from')
  289. else:
  290. self._addLine(layer, data)
  291. data.AddAction(layer, direction = 'to')
  292. self.canvas.Refresh()
  293. self.SetStatusText(layer.GetLog(), 0)
  294. def _addLine(self, fromShape, toShape):
  295. """!Add connection
  296. @param fromShape from
  297. @param toShape to
  298. """
  299. line = ogl.LineShape()
  300. line.SetCanvas(self)
  301. line.SetPen(wx.BLACK_PEN)
  302. line.SetBrush(wx.BLACK_BRUSH)
  303. line.AddArrow(ogl.ARROW_ARROW)
  304. line.MakeLineControlPoints(2)
  305. fromShape.AddLine(line, toShape)
  306. self.canvas.diagram.AddShape(line)
  307. line.Show(True)
  308. def LoadModelFile(self, filename):
  309. """!Load model definition stored in GRASS Model XML file (gxm)
  310. @todo Validate against DTD
  311. Raise exception on error.
  312. """
  313. ### dtdFilename = os.path.join(globalvar.ETCWXDIR, "xml", "grass-gxm.dtd")
  314. # parse workspace file
  315. try:
  316. gxmXml = ProcessModelFile(etree.parse(filename))
  317. except:
  318. GMessage(parent = self,
  319. message = _("Reading model file <%s> failed.\n"
  320. "Invalid file, unable to parse XML document.") % filename)
  321. return
  322. busy = wx.BusyInfo(message=_("Please wait, loading model..."),
  323. parent=self)
  324. wx.Yield()
  325. # load actions
  326. for action in gxmXml.actions:
  327. actionShape = ModelAction(parent = self,
  328. x = action['pos'][0],
  329. y = action['pos'][1],
  330. width = action['size'][0],
  331. height = action['size'][1],
  332. cmd = action['cmd'])
  333. self.canvas.diagram.AddShape(actionShape)
  334. actionShape.Show(True)
  335. self._addEvent(actionShape)
  336. self.actions.append(actionShape)
  337. # load data & connections
  338. for data in gxmXml.data:
  339. dataShape = ModelData(parent = self,
  340. x = data['pos'][0],
  341. y = data['pos'][1],
  342. width = data['size'][0],
  343. height = data['size'][1],
  344. name = data['name'],
  345. prompt = data['prompt'],
  346. value = data['value'])
  347. self.canvas.diagram.AddShape(dataShape)
  348. dataShape.Show(True)
  349. self._addEvent(dataShape)
  350. self.data.append(dataShape)
  351. actionShape = self.actions[0]
  352. if data['from'] is True:
  353. self._addLine(dataShape, actionShape)
  354. elif data['from'] is False:
  355. self._addLine(actionShape, dataShape)
  356. self.canvas.Refresh(True)
  357. def WriteModelFile(self, filename):
  358. """!Save model to model file
  359. @return True on success
  360. @return False on failure
  361. """
  362. try:
  363. file = open(filename, "w")
  364. except IOError:
  365. wx.MessageBox(parent = self,
  366. message = _("Unable to open file <%s> for writing.") % filename,
  367. caption = _("Error"),
  368. style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
  369. return False
  370. try:
  371. WriteModelFile(fd = file, actions = self.actions, data = self.data)
  372. except StandardError:
  373. file.close()
  374. GMessage(parent = self,
  375. message = _("Writing current settings to model file failed."))
  376. return False
  377. file.close()
  378. return True
  379. class ModelCanvas(ogl.ShapeCanvas):
  380. """!Canvas where model is drawn"""
  381. def __init__(self, parent):
  382. ogl.OGLInitialize()
  383. ogl.ShapeCanvas.__init__(self, parent)
  384. self.diagram = ogl.Diagram()
  385. self.SetDiagram(self.diagram)
  386. self.diagram.SetCanvas(self)
  387. self.SetScrollbars(20, 20, 1000/20, 1000/20)
  388. class ModelAction(ogl.RectangleShape):
  389. """!Action class (GRASS module)"""
  390. def __init__(self, parent, x, y, cmd = None, width = 100, height = 50):
  391. self.parent = parent
  392. self.cmd = cmd
  393. self.params = None
  394. self.propWin = None
  395. ogl.RectangleShape.__init__(self, width, height)
  396. # self.Draggable(True)
  397. self.SetCanvas(self.parent)
  398. self.SetX(x)
  399. self.SetY(y)
  400. self.SetPen(wx.BLACK_PEN)
  401. self.SetBrush(wx.LIGHT_GREY_BRUSH)
  402. if self.cmd and len(self.cmd) > 0:
  403. self.AddText(self.cmd[0])
  404. else:
  405. self.AddText('<<module>>')
  406. def SetProperties(self, dcmd, params, propwin):
  407. """!Record properties dialog"""
  408. self.cmd = dcmd
  409. self.params = params
  410. self.propWin = propwin
  411. def GetPropDialog(self):
  412. """!Get properties dialog"""
  413. return self.propWin
  414. def GetLog(self, string = True):
  415. """!Get logging info"""
  416. if string:
  417. if self.cmd is None:
  418. return ''
  419. else:
  420. return ' '.join(self.cmd)
  421. return self.cmd
  422. def GetName(self):
  423. """!Get name"""
  424. if self.cmd and len(self.cmd) > 0:
  425. return self.cmd[0]
  426. return _('unknown')
  427. def GetParams(self):
  428. """!Get dictionary of parameters"""
  429. return self.params
  430. class ModelData(ogl.EllipseShape):
  431. """!Data item class"""
  432. def __init__(self, parent, x, y, name = '', value = '', prompt = '', width = 175, height = 50):
  433. self.parent = parent
  434. self.name = name
  435. self.value = value
  436. self.prompt = prompt
  437. self.actions = { 'from' : list(), 'to' : list() }
  438. ogl.EllipseShape.__init__(self, width, height)
  439. # self.Draggable(True)
  440. self.SetCanvas(self.parent)
  441. self.SetX(x)
  442. self.SetY(y)
  443. self.SetPen(wx.BLACK_PEN)
  444. if self.prompt == 'raster':
  445. self.SetBrush(wx.Brush(wx.Colour(215, 215, 248)))
  446. elif self.prompt == 'vector':
  447. self.SetBrush(wx.Brush(wx.Colour(248, 215, 215)))
  448. else:
  449. self.SetBrush(wx.LIGHT_GREY_BRUSH)
  450. if name:
  451. self.AddText(name)
  452. self.AddText(value)
  453. else:
  454. self.AddText(_('unknown'))
  455. def GetLog(self, string = True):
  456. """!Get logging info"""
  457. if self.name:
  458. return self.name + '=' + self.value + ' (' + self.prompt + ')'
  459. else:
  460. return _('unknown')
  461. def GetName(self):
  462. """!Get name"""
  463. return self.name
  464. def GetPrompt(self):
  465. """!Get prompt"""
  466. return self.prompt
  467. def GetValue(self):
  468. """!Get value"""
  469. return self.value
  470. def GetActions(self, direction):
  471. """!Get related actions
  472. @param direction direction - 'from' or 'to'
  473. """
  474. return self.actions[direction]
  475. def AddAction(self, action, direction):
  476. """!Record related actions
  477. @param action action to be recoreded
  478. @param direction direction of relation
  479. """
  480. self.actions[direction].append(action)
  481. def GetPropDialog(self):
  482. """!Get properties dialog"""
  483. return None
  484. class ModelEvtHandler(ogl.ShapeEvtHandler):
  485. """!Model event handler class"""
  486. def __init__(self, log, frame):
  487. ogl.ShapeEvtHandler.__init__(self)
  488. self.log = log
  489. self.frame = frame
  490. def OnLeftClick(self, x, y, keys = 0, attachment = 0):
  491. """!Left mouse button pressed -> select item & update statusbar"""
  492. shape = self.GetShape()
  493. canvas = shape.GetCanvas()
  494. dc = wx.ClientDC(canvas)
  495. canvas.PrepareDC(dc)
  496. if shape.Selected():
  497. shape.Select(False, dc)
  498. else:
  499. redraw = False
  500. shapeList = canvas.GetDiagram().GetShapeList()
  501. toUnselect = list()
  502. for s in shapeList:
  503. if s.Selected():
  504. toUnselect.append(s)
  505. shape.Select(True, dc)
  506. for s in toUnselect:
  507. s.Select(False, dc)
  508. canvas.Refresh(False)
  509. self.log.SetStatusText(shape.GetLog(), 0)
  510. def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0):
  511. """!Left mouse button pressed (double-click) -> show properties"""
  512. shape = self.GetShape()
  513. win = shape.GetPropDialog()
  514. if isinstance(shape, ModelAction) and not win:
  515. module = menuform.GUI().ParseCommand(shape.cmd,
  516. completed = (self.frame.GetOptData, shape, None),
  517. parentframe = self.frame, show = True)
  518. elif win and not win.IsShown():
  519. win.Show()
  520. if win:
  521. win.Raise()
  522. class ModelSearchDialog(wx.Dialog):
  523. def __init__(self, parent, id = wx.ID_ANY, title = _("Find GRASS module"),
  524. style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
  525. """!Graphical modeler module search window
  526. @param parent parent window
  527. @param id window id
  528. @param title window title
  529. @param kwargs wx.Dialogs' arguments
  530. """
  531. self.parent = parent
  532. wx.Dialog.__init__(self, parent = parent, id = id, title = title, **kwargs)
  533. self.SetName("ModelerDialog")
  534. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  535. self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
  536. self.searchBy = wx.Choice(parent = self.panel, id = wx.ID_ANY,
  537. choices = [_("description"),
  538. _("keywords")])
  539. self.search = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY,
  540. value = "", size = (-1, 25))
  541. self.cmd_prompt = prompt.GPromptSTC(parent = self)
  542. self.btnCancel = wx.Button(self.panel, wx.ID_CANCEL)
  543. self.btnOk = wx.Button(self.panel, wx.ID_OK)
  544. self.btnOk.SetDefault()
  545. self._layout()
  546. def _layout(self):
  547. btnSizer = wx.StdDialogButtonSizer()
  548. btnSizer.AddButton(self.btnCancel)
  549. btnSizer.AddButton(self.btnOk)
  550. btnSizer.Realize()
  551. bodyBox = wx.StaticBox(parent=self.panel, id=wx.ID_ANY,
  552. label=" %s " % _("Find GRASS module"))
  553. bodySizer = wx.StaticBoxSizer(bodyBox, wx.VERTICAL)
  554. searchSizer = wx.BoxSizer(wx.HORIZONTAL)
  555. searchSizer.Add(item = self.searchBy,
  556. proportion = 0, flag = wx.LEFT, border = 3)
  557. searchSizer.Add(item = self.search,
  558. proportion = 1, flag = wx.LEFT | wx.EXPAND, border = 3)
  559. bodySizer.Add(item=searchSizer, proportion=0,
  560. flag=wx.EXPAND | wx.ALL, border=1)
  561. bodySizer.Add(item=self.cmd_prompt, proportion=1,
  562. flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border=3)
  563. mainSizer = wx.BoxSizer(wx.VERTICAL)
  564. mainSizer.Add(item=bodySizer, proportion=1,
  565. flag=wx.EXPAND | wx.ALL, border=5)
  566. mainSizer.Add(item=btnSizer, proportion=0,
  567. flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)
  568. self.panel.SetSizer(mainSizer)
  569. mainSizer.Fit(self.panel)
  570. def GetPanel(self):
  571. """!Get dialog panel"""
  572. return self.panel
  573. def GetCmd(self):
  574. """!Get command"""
  575. line = self.cmd_prompt.GetCurLine()[0].strip()
  576. if len(line) == 0:
  577. list()
  578. try:
  579. cmd = shlex.split(str(line))
  580. except UnicodeError:
  581. cmd = shlex.split(utils.EncodeString((line)))
  582. return cmd
  583. def OnOk(self, event):
  584. self.btnOk.SetFocus()
  585. def Reset(self):
  586. """!Reset dialog"""
  587. self.searchBy.SetSelection(0)
  588. self.search.SetValue('')
  589. self.cmd_prompt.OnCmdErase(None)
  590. class ProcessModelFile:
  591. """!Process GRASS model file (gxm)"""
  592. def __init__(self, tree):
  593. """!A ElementTree handler for the GXM XML file, as defined in
  594. grass-gxm.dtd.
  595. """
  596. self.tree = tree
  597. self.root = self.tree.getroot()
  598. # list of actions, data
  599. self.actions = list()
  600. self.data = list()
  601. self._processActions()
  602. self._processData()
  603. def _filterValue(self, value):
  604. """!Filter value
  605. @param value
  606. """
  607. value = value.replace('&lt;', '<')
  608. value = value.replace('&gt;', '>')
  609. return value
  610. def _getNodeText(self, node, tag, default = ''):
  611. """!Get node text"""
  612. p = node.find(tag)
  613. if p is not None:
  614. return utils.normalize_whitespace(p.text)
  615. return default
  616. def _processActions(self):
  617. """!Process model file"""
  618. for action in self.root.findall('action'):
  619. pos, size = self._getDim(action)
  620. task = action.find('task')
  621. if task:
  622. cmd = self._processTask(task)
  623. else:
  624. cmd = None
  625. self.actions.append({ 'pos' : pos,
  626. 'size': size,
  627. 'cmd' : cmd })
  628. def _getDim(self, node):
  629. """!Get position and size of shape"""
  630. pos = size = None
  631. posAttr = node.get('pos', None)
  632. if posAttr:
  633. posVal = map(int, posAttr.split(','))
  634. try:
  635. pos = (posVal[0], posVal[1])
  636. except:
  637. pos = None
  638. sizeAttr = node.get('size', None)
  639. if sizeAttr:
  640. sizeVal = map(int, sizeAttr.split(','))
  641. try:
  642. size = (sizeVal[0], sizeVal[1])
  643. except:
  644. size = None
  645. return pos, size
  646. def _processData(self):
  647. """!Process model file"""
  648. for data in self.root.findall('data'):
  649. pos, size = self._getDim(data)
  650. param = data.find('parameter')
  651. name = prompt = value = None
  652. if param is not None:
  653. name = param.get('name', None)
  654. prompt = param.get('prompt', None)
  655. value = self._filterValue(self._getNodeText(param, 'value'))
  656. action = data.find('action')
  657. aId = fromDir = None
  658. if action is not None:
  659. aId = int(action.get('id', None))
  660. if action.get('dir', 'to') == 'to':
  661. fromDir = False
  662. else:
  663. fromDir = True
  664. self.data.append({ 'pos' : pos,
  665. 'size': size,
  666. 'name' : name,
  667. 'prompt' : prompt,
  668. 'value' : value,
  669. 'id' : aId,
  670. 'from' : fromDir })
  671. def _processTask(self, node):
  672. """!Process task"""
  673. cmd = list()
  674. name = node.get('name', None)
  675. if not name:
  676. return cmd
  677. cmd.append(name)
  678. # flags
  679. for p in node.findall('flag'):
  680. flag = p.get('name', '')
  681. if len(flag) > 1:
  682. cmd.append('--' + flag)
  683. else:
  684. cmd.append('-' + flag)
  685. # parameters
  686. for p in node.findall('parameter'):
  687. cmd.append('%s=%s' % (p.get('name', ''),
  688. self._filterValue(self._getNodeText(p, 'value'))))
  689. return cmd
  690. class WriteModelFile:
  691. """!Generic class for writing model file"""
  692. def __init__(self, fd, actions, data):
  693. self.fd = fd
  694. self.actions = actions
  695. self.data = data
  696. self.indent = 0
  697. self._header()
  698. self._actions()
  699. self._data()
  700. self._footer()
  701. def _filterValue(self, value):
  702. """!Make value XML-valid"""
  703. value = value.replace('<', '&lt;')
  704. value = value.replace('>', '&gt;')
  705. return value
  706. def _header(self):
  707. """!Write header"""
  708. self.fd.write('<?xml version="1.0" encoding="UTF-8"?>\n')
  709. self.fd.write('<!DOCTYPE gxm SYSTEM "grass-gxm.dtd">\n')
  710. self.fd.write('%s<gxm>\n' % (' ' * self.indent))
  711. def _footer(self):
  712. """!Write footer"""
  713. self.fd.write('%s</gxm>\n' % (' ' * self.indent))
  714. def _actions(self):
  715. """!Write actions"""
  716. id = 1
  717. self.indent += 4
  718. for action in self.actions:
  719. self.fd.write('%s<action id="%d" name="%s" pos="%d,%d" size="%d,%d">\n' % \
  720. (' ' * self.indent, id, action.GetName(), action.GetX(), action.GetY(),
  721. action.GetWidth(), action.GetHeight()))
  722. self.indent += 4
  723. self.fd.write('%s<task name="%s">\n' % (' ' * self.indent, action.GetLog(string = False)[0]))
  724. self.indent += 4
  725. for key, val in action.GetParams().iteritems():
  726. if key == 'flags':
  727. for f in val:
  728. if f.get('value', False):
  729. self.fd.write('%s<flag name="%s" />\n' %
  730. (' ' * self.indent, f.get('name', '')))
  731. else: # parameter
  732. for p in val:
  733. if not p.get('value', ''):
  734. continue
  735. self.fd.write('%s<parameter name="%s">\n' %
  736. (' ' * self.indent, p.get('name', '')))
  737. self.indent += 4
  738. self.fd.write('%s<value>%s</value>\n' %
  739. (' ' * self.indent, self._filterValue(p.get('value', ''))))
  740. self.indent -= 4
  741. self.fd.write('%s</parameter>\n' % (' ' * self.indent))
  742. self.indent -= 4
  743. self.fd.write('%s</task>\n' % (' ' * self.indent))
  744. self.indent -= 4
  745. self.fd.write('%s</action>\n' % (' ' * self.indent))
  746. id += 1
  747. self.indent -= 4
  748. def _data(self):
  749. """!Write data"""
  750. self.indent += 4
  751. for data in self.data:
  752. self.fd.write('%s<data pos="%d,%d" size="%d,%d">\n' % \
  753. (' ' * self.indent, data.GetX(), data.GetY(),
  754. data.GetWidth(), data.GetHeight()))
  755. self.indent += 4
  756. self.fd.write('%s<parameter name="%s" prompt="%s">\n' % \
  757. (' ' * self.indent, data.GetName(), data.GetPrompt()))
  758. self.indent += 4
  759. self.fd.write('%s<value>%s</value>\n' %
  760. (' ' * self.indent, self._filterValue(data.GetValue())))
  761. self.indent -= 4
  762. self.fd.write('%s</parameter>\n' % (' ' * self.indent))
  763. self.indent -= 4
  764. for action in data.GetActions('from'):
  765. self.fd.write('%s<action id="1" dir="from" />\n' % \
  766. (' ' * self.indent))
  767. for action in data.GetActions('to'):
  768. self.fd.write('%s<action id="1" dir="to" />\n' % \
  769. (' ' * self.indent))
  770. self.fd.write('%s</data>\n' % (' ' * self.indent))
  771. self.indent -= 4
  772. def main():
  773. app = wx.PySimpleApp()
  774. frame = ModelFrame(parent = None)
  775. if len(sys.argv) > 1:
  776. frame.LoadModelFile(sys.argv[1])
  777. # frame.CentreOnScreen()
  778. frame.Show()
  779. app.MainLoop()
  780. if __name__ == "__main__":
  781. main()