gmodeler.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  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. (C) 2010 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 shlex
  17. import time
  18. import globalvar
  19. if not os.getenv("GRASS_WXBUNDLED"):
  20. globalvar.CheckForWx()
  21. import wx
  22. import wx.lib.ogl as ogl
  23. import menu
  24. import menudata
  25. import toolbars
  26. import menuform
  27. import prompt
  28. from grass.script import core as grass
  29. class ModelFrame(wx.Frame):
  30. def __init__(self, parent, id = wx.ID_ANY, title = _("Graphical modeler (under development)"), **kwargs):
  31. """!Graphical modeler main window
  32. @param parent parent window
  33. @param id window id
  34. @param title window title
  35. @param kwargs wx.Frames' arguments
  36. """
  37. self.parent = parent
  38. self.searchDialog = None # module search dialog
  39. self.actions = list() # list of recorded actions
  40. self.data = list() # list of recorded data items
  41. wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
  42. self.SetName("Modeler")
  43. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  44. self.menubar = menu.Menu(parent = self, data = menudata.ModelerData())
  45. self.SetMenuBar(self.menubar)
  46. self.toolbar = toolbars.ModelToolbar(parent = self)
  47. self.SetToolBar(self.toolbar)
  48. self.statusbar = self.CreateStatusBar(number = 1)
  49. self.canvas = ModelCanvas(self)
  50. self.canvas.SetBackgroundColour(wx.WHITE)
  51. self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
  52. self._layout()
  53. self.SetMinSize((640, 480))
  54. def _layout(self):
  55. """!Do layout"""
  56. sizer = wx.BoxSizer(wx.VERTICAL)
  57. sizer.Add(item = self.canvas, proportion = 1,
  58. flag = wx.EXPAND)
  59. self.SetAutoLayout(True)
  60. self.SetSizer(sizer)
  61. sizer.Fit(self)
  62. self.Layout()
  63. def _addEvent(self, item):
  64. """!Add event to item"""
  65. evthandler = ModelEvtHandler(self.statusbar,
  66. self)
  67. evthandler.SetShape(item)
  68. evthandler.SetPreviousHandler(item.GetEventHandler())
  69. item.SetEventHandler(evthandler)
  70. def OnCloseWindow(self, event):
  71. """!Close window"""
  72. self.Destroy()
  73. def OnModelNew(self, event):
  74. """!Create new model"""
  75. pass
  76. def OnModelOpen(self, event):
  77. """!Load model from file"""
  78. pass
  79. def OnModelSave(self, event):
  80. """!Save model to file"""
  81. pass
  82. def OnModelSaveAs(self, event):
  83. """!Create model to file as"""
  84. pass
  85. def OnRunModel(self, event):
  86. """!Run entire model"""
  87. pass
  88. def OnValidateModel(self, event):
  89. """!Validate entire model"""
  90. for s in self.actions:
  91. print s
  92. def OnRemoveItem(self, event):
  93. """!Remove item from model"""
  94. pass
  95. def OnAddAction(self, event):
  96. """!Add action to model"""
  97. debug = False
  98. if debug == False:
  99. if self.searchDialog is None:
  100. self.searchDialog = ModelSearchDialog(self)
  101. self.searchDialog.CentreOnParent()
  102. else:
  103. self.searchDialog.Reset()
  104. if self.searchDialog.ShowModal() == wx.ID_CANCEL:
  105. self.searchDialog.Hide()
  106. return
  107. cmd = self.searchDialog.GetCmd()
  108. self.searchDialog.Hide()
  109. else:
  110. cmd = ['r.buffer']
  111. # add action to canvas
  112. width, height = self.canvas.GetSize()
  113. action = ModelAction(self, cmd = cmd, x = width/2, y = height/2)
  114. self.canvas.diagram.AddShape(action)
  115. action.Show(True)
  116. self._addEvent(action)
  117. self.actions.append(action)
  118. self.canvas.Refresh()
  119. time.sleep(.1)
  120. # show properties dialog
  121. win = action.GetPropDialog()
  122. if not win:
  123. module = menuform.GUI().ParseCommand(action.GetLog(string = False),
  124. completed = (self.GetOptData, action, None),
  125. parentframe = self, show = True)
  126. elif not win.IsShown():
  127. win.Show()
  128. if win:
  129. win.Raise()
  130. def OnAddData(self, event):
  131. """!Add data item to model"""
  132. # add action to canvas
  133. width, height = self.canvas.GetSize()
  134. data = ModelData(self, x = width/2, y = height/2)
  135. self.canvas.diagram.AddShape(data)
  136. data.Show(True)
  137. self._addEvent(data)
  138. self.data.append(data)
  139. self.canvas.Refresh()
  140. def OnHelp(self, event):
  141. """!Display manual page"""
  142. grass.run_command('g.manual',
  143. entry = 'wxGUI.Modeler')
  144. def GetOptData(self, dcmd, layer, params, propwin):
  145. """!Process action data"""
  146. layer.SetProperties(dcmd, params, propwin)
  147. if params: # add data items
  148. width, height = self.canvas.GetSize()
  149. x = [width/2 + 200, width/2 - 200]
  150. for p in params['params']:
  151. if p.get('value', None) and \
  152. p.get('prompt', '') in ('raster', 'vector', 'raster3d'):
  153. # create data item
  154. data = ModelData(self, name = p.get('name', ''),
  155. value = p.get('value', ''),
  156. prompt = p.get('prompt', ''),
  157. x = x.pop(), y = height/2)
  158. self.canvas.diagram.AddShape(data)
  159. data.Show(True)
  160. self._addEvent(data)
  161. self.data.append(data)
  162. # connect with action
  163. line = ogl.LineShape()
  164. line.SetCanvas(self)
  165. line.SetPen(wx.BLACK_PEN)
  166. line.SetBrush(wx.BLACK_BRUSH)
  167. line.AddArrow(ogl.ARROW_ARROW)
  168. line.MakeLineControlPoints(2)
  169. if p.get('age', 'old') == 'old':
  170. data.AddLine(line, layer)
  171. else:
  172. layer.AddLine(line, data)
  173. self.canvas.diagram.AddShape(line)
  174. line.Show(True)
  175. self.canvas.Refresh()
  176. self.SetStatusText(layer.GetLog(), 0)
  177. class ModelCanvas(ogl.ShapeCanvas):
  178. """!Canvas where model is drawn"""
  179. def __init__(self, parent):
  180. ogl.OGLInitialize()
  181. ogl.ShapeCanvas.__init__(self, parent)
  182. self.diagram = ogl.Diagram()
  183. self.SetDiagram(self.diagram)
  184. self.diagram.SetCanvas(self)
  185. self.SetScrollbars(20, 20, 1000/20, 1000/20)
  186. class ModelAction(ogl.RectangleShape):
  187. """!Action class (GRASS module)"""
  188. def __init__(self, parent, x, y, cmd = None, width = 100, height = 50):
  189. self.parent = parent
  190. self.cmd = cmd
  191. self.params = None
  192. self.propWin = None
  193. ogl.RectangleShape.__init__(self, width, height)
  194. # self.Draggable(True)
  195. self.SetCanvas(self.parent)
  196. self.SetX(x)
  197. self.SetY(y)
  198. self.SetPen(wx.BLACK_PEN)
  199. self.SetBrush(wx.LIGHT_GREY_BRUSH)
  200. if self.cmd and len(self.cmd) > 0:
  201. self.AddText(self.cmd[0])
  202. else:
  203. self.AddText('<<module>>')
  204. def SetProperties(self, dcmd, params, propwin):
  205. """!Record properties dialog"""
  206. self.cmd = dcmd
  207. self.params = params
  208. self.propWin = propwin
  209. def GetPropDialog(self):
  210. """!Get properties dialog"""
  211. return self.propWin
  212. def GetLog(self, string = True):
  213. """!Get logging info"""
  214. if string:
  215. if self.cmd is None:
  216. return ''
  217. else:
  218. return ' '.join(self.cmd)
  219. return self.cmd
  220. class ModelData(ogl.EllipseShape):
  221. """!Data item class"""
  222. def __init__(self, parent, x, y, name = '', value = '', prompt = '', width = 175, height = 50):
  223. self.parent = parent
  224. self.name = name
  225. self.value = value
  226. self.prompt = prompt
  227. ogl.EllipseShape.__init__(self, width, height)
  228. # self.Draggable(True)
  229. self.SetCanvas(self.parent)
  230. self.SetX(x)
  231. self.SetY(y)
  232. self.SetPen(wx.BLACK_PEN)
  233. if self.prompt == 'raster':
  234. self.SetBrush(wx.Brush(wx.Colour(215, 215, 248)))
  235. elif self.prompt == 'vector':
  236. self.SetBrush(wx.Brush(wx.Colour(248, 215, 215)))
  237. else:
  238. self.SetBrush(wx.LIGHT_GREY_BRUSH)
  239. if name:
  240. self.AddText(name)
  241. self.AddText(value)
  242. else:
  243. self.AddText(_('unknown'))
  244. def GetLog(self, string = True):
  245. """!Get logging info"""
  246. if self.name:
  247. return self.name + '=' + self.value + ' (' + self.prompt + ')'
  248. else:
  249. return _('unknown')
  250. class ModelEvtHandler(ogl.ShapeEvtHandler):
  251. """!Model event handler class"""
  252. def __init__(self, log, frame):
  253. ogl.ShapeEvtHandler.__init__(self)
  254. self.log = log
  255. self.frame = frame
  256. def OnLeftClick(self, x, y, keys = 0, attachment = 0):
  257. """!Left mouse button pressed -> select item & update statusbar"""
  258. shape = self.GetShape()
  259. canvas = shape.GetCanvas()
  260. dc = wx.ClientDC(canvas)
  261. canvas.PrepareDC(dc)
  262. if shape.Selected():
  263. shape.Select(False, dc)
  264. else:
  265. redraw = False
  266. shapeList = canvas.GetDiagram().GetShapeList()
  267. toUnselect = list()
  268. for s in shapeList:
  269. if s.Selected():
  270. toUnselect.append(s)
  271. shape.Select(True, dc)
  272. for s in toUnselect:
  273. s.Select(False, dc)
  274. canvas.Refresh(False)
  275. self.log.SetStatusText(shape.GetLog(), 0)
  276. def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0):
  277. """!Left mouse button pressed (double-click) -> show properties"""
  278. shape = self.GetShape()
  279. win = shape.GetPropDialog()
  280. if not win:
  281. module = menuform.GUI().ParseCommand(shape.cmd,
  282. completed = (self.frame.GetOptData, shape, None),
  283. parentframe = self.frame, show = True)
  284. elif not win.IsShown():
  285. win.Show()
  286. if win:
  287. win.Raise()
  288. class ModelSearchDialog(wx.Dialog):
  289. def __init__(self, parent, id = wx.ID_ANY, title = _("Find GRASS module"),
  290. style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
  291. """!Graphical modeler module search window
  292. @param parent parent window
  293. @param id window id
  294. @param title window title
  295. @param kwargs wx.Dialogs' arguments
  296. """
  297. self.parent = parent
  298. wx.Dialog.__init__(self, parent = parent, id = id, title = title, **kwargs)
  299. self.SetName("ModelerDialog")
  300. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  301. self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
  302. self.searchBy = wx.Choice(parent = self.panel, id = wx.ID_ANY,
  303. choices = [_("description"),
  304. _("keywords")])
  305. self.search = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY,
  306. value = "", size = (-1, 25))
  307. self.cmd_prompt = prompt.GPromptSTC(parent = self)
  308. self.btnCancel = wx.Button(self.panel, wx.ID_CANCEL)
  309. self.btnOk = wx.Button(self.panel, wx.ID_OK)
  310. self.btnOk.SetDefault()
  311. self._layout()
  312. def _layout(self):
  313. btnSizer = wx.StdDialogButtonSizer()
  314. btnSizer.AddButton(self.btnCancel)
  315. btnSizer.AddButton(self.btnOk)
  316. btnSizer.Realize()
  317. bodyBox = wx.StaticBox(parent=self.panel, id=wx.ID_ANY,
  318. label=" %s " % _("Find GRASS module"))
  319. bodySizer = wx.StaticBoxSizer(bodyBox, wx.VERTICAL)
  320. searchSizer = wx.BoxSizer(wx.HORIZONTAL)
  321. searchSizer.Add(item = self.searchBy,
  322. proportion = 0, flag = wx.LEFT, border = 3)
  323. searchSizer.Add(item = self.search,
  324. proportion = 1, flag = wx.LEFT | wx.EXPAND, border = 3)
  325. bodySizer.Add(item=searchSizer, proportion=0,
  326. flag=wx.EXPAND | wx.ALL, border=1)
  327. bodySizer.Add(item=self.cmd_prompt, proportion=1,
  328. flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border=3)
  329. mainSizer = wx.BoxSizer(wx.VERTICAL)
  330. mainSizer.Add(item=bodySizer, proportion=1,
  331. flag=wx.EXPAND | wx.ALL, border=5)
  332. mainSizer.Add(item=btnSizer, proportion=0,
  333. flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)
  334. self.panel.SetSizer(mainSizer)
  335. mainSizer.Fit(self.panel)
  336. def GetPanel(self):
  337. """!Get dialog panel"""
  338. return self.panel
  339. def GetCmd(self):
  340. """!Get command"""
  341. line = self.cmd_prompt.GetCurLine()[0].strip()
  342. if len(line) == 0:
  343. list()
  344. try:
  345. cmd = shlex.split(str(line))
  346. except UnicodeError:
  347. cmd = shlex.split(utils.EncodeString((line)))
  348. return cmd
  349. def OnOk(self, event):
  350. self.btnOk.SetFocus()
  351. def Reset(self):
  352. """!Reset dialog"""
  353. self.searchBy.SetSelection(0)
  354. self.search.SetValue('')
  355. self.cmd_prompt.OnCmdErase(None)
  356. def main():
  357. app = wx.PySimpleApp()
  358. frame = ModelFrame(parent = None)
  359. # frame.CentreOnScreen()
  360. frame.Show()
  361. app.MainLoop()
  362. if __name__ == "__main__":
  363. main()