gmodeler.py 13 KB

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