gmodeler.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  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 OnAddAction(self, event):
  86. """!Add action to model"""
  87. debug = False
  88. if debug == False:
  89. if self.searchDialog is None:
  90. self.searchDialog = ModelSearchDialog(self)
  91. self.searchDialog.CentreOnParent()
  92. else:
  93. self.searchDialog.Reset()
  94. if self.searchDialog.ShowModal() == wx.ID_CANCEL:
  95. self.searchDialog.Hide()
  96. return
  97. cmd = self.searchDialog.GetCmd()
  98. self.searchDialog.Hide()
  99. else:
  100. cmd = ['r.buffer']
  101. # add action to canvas
  102. width, height = self.canvas.GetSize()
  103. action = ModelAction(self, cmd = cmd, x = width/2, y = height/2)
  104. self.canvas.diagram.AddShape(action)
  105. action.Show(True)
  106. self._addEvent(action)
  107. self.actions.append(action)
  108. self.canvas.Refresh()
  109. time.sleep(.1)
  110. # show properties dialog
  111. win = action.GetPropDialog()
  112. if not win:
  113. module = menuform.GUI().ParseCommand(action.GetLog(string = False),
  114. completed = (self.GetOptData, action, None),
  115. parentframe = self, show = True)
  116. elif not win.IsShown():
  117. win.Show()
  118. if win:
  119. win.Raise()
  120. def OnAddData(self, event):
  121. """!Add data item to model"""
  122. # add action to canvas
  123. width, height = self.canvas.GetSize()
  124. data = ModelData(self, x = width/2, y = height/2)
  125. self.canvas.diagram.AddShape(data)
  126. data.Show(True)
  127. self._addEvent(data)
  128. self.data.append(data)
  129. self.canvas.Refresh()
  130. def OnHelp(self, event):
  131. """!Display manual page"""
  132. grass.run_command('g.manual',
  133. entry = 'wxGUI.Modeler')
  134. def GetOptData(self, dcmd, layer, params, propwin):
  135. """!Process action data"""
  136. layer.SetProperties(dcmd, params, propwin)
  137. if params: # add data items
  138. width, height = self.canvas.GetSize()
  139. x = [width/2 + 200, width/2 - 200]
  140. for p in params['params']:
  141. if p.get('value', None) and \
  142. p.get('prompt', '') in ('raster', 'vector', 'raster3d'):
  143. # create data item
  144. data = ModelData(self, name = p.get('name', ''),
  145. value = p.get('value', ''),
  146. prompt = p.get('prompt', ''),
  147. x = x.pop(), y = height/2)
  148. self.canvas.diagram.AddShape(data)
  149. data.Show(True)
  150. self._addEvent(data)
  151. self.data.append(data)
  152. # connect with action
  153. line = ogl.LineShape()
  154. line.SetCanvas(self)
  155. line.SetPen(wx.BLACK_PEN)
  156. line.SetBrush(wx.BLACK_BRUSH)
  157. line.AddArrow(ogl.ARROW_ARROW)
  158. line.MakeLineControlPoints(2)
  159. if p.get('age', 'old') == 'old':
  160. data.AddLine(line, layer)
  161. else:
  162. layer.AddLine(line, data)
  163. self.canvas.diagram.AddShape(line)
  164. line.Show(True)
  165. self.canvas.Refresh()
  166. self.SetStatusText(layer.GetLog(), 0)
  167. class ModelCanvas(ogl.ShapeCanvas):
  168. """!Canvas where model is drawn"""
  169. def __init__(self, parent):
  170. ogl.OGLInitialize()
  171. ogl.ShapeCanvas.__init__(self, parent)
  172. self.diagram = ogl.Diagram()
  173. self.SetDiagram(self.diagram)
  174. self.diagram.SetCanvas(self)
  175. self.SetScrollbars(20, 20, 1000/20, 1000/20)
  176. class ModelAction(ogl.RectangleShape):
  177. """!Action class (GRASS module)"""
  178. def __init__(self, parent, x, y, cmd = None, width = 100, height = 50):
  179. self.parent = parent
  180. self.cmd = cmd
  181. self.params = None
  182. self.propWin = None
  183. ogl.RectangleShape.__init__(self, width, height)
  184. # self.Draggable(True)
  185. self.SetCanvas(self.parent)
  186. self.SetX(x)
  187. self.SetY(y)
  188. self.SetPen(wx.BLACK_PEN)
  189. self.SetBrush(wx.LIGHT_GREY_BRUSH)
  190. if self.cmd and len(self.cmd) > 0:
  191. self.AddText(self.cmd[0])
  192. else:
  193. self.AddText('<<module>>')
  194. def SetProperties(self, dcmd, params, propwin):
  195. """!Record properties dialog"""
  196. self.cmd = dcmd
  197. self.params = params
  198. self.propWin = propwin
  199. def GetPropDialog(self):
  200. """!Get properties dialog"""
  201. return self.propWin
  202. def GetLog(self, string = True):
  203. """!Get logging info"""
  204. if string:
  205. if self.cmd is None:
  206. return ''
  207. else:
  208. return ' '.join(self.cmd)
  209. return self.cmd
  210. class ModelData(ogl.EllipseShape):
  211. """!Data item class"""
  212. def __init__(self, parent, x, y, name = '', value = '', prompt = '', width = 175, height = 50):
  213. self.parent = parent
  214. self.name = name
  215. self.value = value
  216. self.prompt = prompt
  217. ogl.EllipseShape.__init__(self, width, height)
  218. # self.Draggable(True)
  219. self.SetCanvas(self.parent)
  220. self.SetX(x)
  221. self.SetY(y)
  222. self.SetPen(wx.BLACK_PEN)
  223. if self.prompt == 'raster':
  224. self.SetBrush(wx.Brush(wx.Colour(215, 215, 248)))
  225. elif self.prompt == 'vector':
  226. self.SetBrush(wx.Brush(wx.Colour(248, 215, 215)))
  227. else:
  228. self.SetBrush(wx.LIGHT_GREY_BRUSH)
  229. if name:
  230. self.AddText(name)
  231. self.AddText(value)
  232. else:
  233. self.AddText(_('unknown'))
  234. def GetLog(self, string = True):
  235. """!Get logging info"""
  236. if self.name:
  237. return self.name + '=' + self.value + ' (' + self.prompt + ')'
  238. else:
  239. return _('unknown')
  240. class ModelEvtHandler(ogl.ShapeEvtHandler):
  241. """!Model event handler class"""
  242. def __init__(self, log, frame):
  243. ogl.ShapeEvtHandler.__init__(self)
  244. self.log = log
  245. self.frame = frame
  246. def OnLeftClick(self, x, y, keys = 0, attachment = 0):
  247. """!Left mouse button pressed -> select item & update statusbar"""
  248. shape = self.GetShape()
  249. canvas = shape.GetCanvas()
  250. dc = wx.ClientDC(canvas)
  251. canvas.PrepareDC(dc)
  252. if shape.Selected():
  253. shape.Select(False, dc)
  254. else:
  255. redraw = False
  256. shapeList = canvas.GetDiagram().GetShapeList()
  257. toUnselect = list()
  258. for s in shapeList:
  259. if s.Selected():
  260. toUnselect.append(s)
  261. shape.Select(True, dc)
  262. for s in toUnselect:
  263. s.Select(False, dc)
  264. canvas.Refresh(False)
  265. self.log.SetStatusText(shape.GetLog(), 0)
  266. def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0):
  267. """!Left mouse button pressed (double-click) -> show properties"""
  268. shape = self.GetShape()
  269. win = shape.GetPropDialog()
  270. if not win:
  271. module = menuform.GUI().ParseCommand(shape.cmd,
  272. completed = (self.frame.GetOptData, shape, None),
  273. parentframe = self.frame, show = True)
  274. elif not win.IsShown():
  275. win.Show()
  276. if win:
  277. win.Raise()
  278. class ModelSearchDialog(wx.Dialog):
  279. def __init__(self, parent, id = wx.ID_ANY, title = _("Find GRASS module"),
  280. style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
  281. """!Graphical modeler module search window
  282. @param parent parent window
  283. @param id window id
  284. @param title window title
  285. @param kwargs wx.Dialogs' arguments
  286. """
  287. self.parent = parent
  288. wx.Dialog.__init__(self, parent = parent, id = id, title = title, **kwargs)
  289. self.SetName("ModelerDialog")
  290. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  291. self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
  292. self.searchBy = wx.Choice(parent = self.panel, id = wx.ID_ANY,
  293. choices = [_("description"),
  294. _("keywords")])
  295. self.search = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY,
  296. value = "", size = (-1, 25))
  297. self.cmd_prompt = prompt.GPromptSTC(parent = self)
  298. self.btnCancel = wx.Button(self.panel, wx.ID_CANCEL)
  299. self.btnOk = wx.Button(self.panel, wx.ID_OK)
  300. self.btnOk.SetDefault()
  301. self._layout()
  302. def _layout(self):
  303. btnSizer = wx.StdDialogButtonSizer()
  304. btnSizer.AddButton(self.btnCancel)
  305. btnSizer.AddButton(self.btnOk)
  306. btnSizer.Realize()
  307. bodyBox = wx.StaticBox(parent=self.panel, id=wx.ID_ANY,
  308. label=" %s " % _("Find GRASS module"))
  309. bodySizer = wx.StaticBoxSizer(bodyBox, wx.VERTICAL)
  310. searchSizer = wx.BoxSizer(wx.HORIZONTAL)
  311. searchSizer.Add(item = self.searchBy,
  312. proportion = 0, flag = wx.LEFT, border = 3)
  313. searchSizer.Add(item = self.search,
  314. proportion = 1, flag = wx.LEFT | wx.EXPAND, border = 3)
  315. bodySizer.Add(item=searchSizer, proportion=0,
  316. flag=wx.EXPAND | wx.ALL, border=1)
  317. bodySizer.Add(item=self.cmd_prompt, proportion=1,
  318. flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border=3)
  319. mainSizer = wx.BoxSizer(wx.VERTICAL)
  320. mainSizer.Add(item=bodySizer, proportion=1,
  321. flag=wx.EXPAND | wx.ALL, border=5)
  322. mainSizer.Add(item=btnSizer, proportion=0,
  323. flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)
  324. self.panel.SetSizer(mainSizer)
  325. mainSizer.Fit(self.panel)
  326. def GetPanel(self):
  327. """!Get dialog panel"""
  328. return self.panel
  329. def GetCmd(self):
  330. """!Get command"""
  331. line = self.cmd_prompt.GetCurLine()[0].strip()
  332. if len(line) == 0:
  333. list()
  334. try:
  335. cmd = shlex.split(str(line))
  336. except UnicodeError:
  337. cmd = shlex.split(utils.EncodeString((line)))
  338. return cmd
  339. def OnOk(self, event):
  340. self.btnOk.SetFocus()
  341. def Reset(self):
  342. """!Reset dialog"""
  343. self.searchBy.SetSelection(0)
  344. self.search.SetValue('')
  345. self.cmd_prompt.OnCmdErase(None)
  346. def main():
  347. app = wx.PySimpleApp()
  348. frame = ModelFrame(parent = None)
  349. # frame.CentreOnScreen()
  350. frame.Show()
  351. app.MainLoop()
  352. if __name__ == "__main__":
  353. main()