frame.py 71 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936
  1. """!
  2. @package gmodeler.frame
  3. @brief wxGUI Graphical Modeler for creating, editing, and managing models
  4. Classes:
  5. - frame::ModelToolbar
  6. - frame::ModelFrame
  7. - frame::ModelCanvas
  8. - frame::ModelEvtHandler
  9. - frame::ModelListCtrl
  10. - frame::VariablePanel
  11. - frame::ValiableListCtrl
  12. - frame::ItemPanel
  13. - frame::ItemListCtrl
  14. - frame::ItemCheckListCtrl
  15. (C) 2010-2011 by the GRASS Development Team
  16. This program is free software under the GNU General Public License
  17. (>=v2). Read the file COPYING that comes with GRASS for details.
  18. @author Martin Landa <landa.martin gmail.com>
  19. """
  20. import os
  21. import sys
  22. import time
  23. import stat
  24. import textwrap
  25. import tempfile
  26. import copy
  27. import re
  28. if __name__ == "__main__":
  29. sys.path.append(os.path.join(os.getenv('GISBASE'), 'etc', 'gui', 'wxpython'))
  30. from core import globalvar
  31. import wx
  32. from wx.lib import ogl
  33. import wx.lib.flatnotebook as FN
  34. import wx.lib.mixins.listctrl as listmix
  35. from gui_core.widgets import GNotebook
  36. from gui_core.goutput import GMConsole
  37. from core.debug import Debug
  38. from core.gcmd import GMessage, GException, GWarning, GError, RunCommand
  39. from gui_core.dialogs import GetImageHandlers
  40. from gui_core.preferences import PreferencesBaseDialog
  41. from core.settings import UserSettings
  42. from core.menudata import MenuData
  43. from gui_core.toolbars import BaseToolbar
  44. from gui_core.menu import Menu
  45. from gmodeler.menudata import ModelerData
  46. from icons.icon import Icons
  47. from gui_core.forms import GUI
  48. from gmodeler.preferences import PreferencesDialog
  49. from gmodeler.model import *
  50. from gmodeler.dialogs import *
  51. from grass.script import core as grass
  52. class ModelToolbar(BaseToolbar):
  53. """!Graphical modeler toolbaro (see gmodeler.py)
  54. """
  55. def __init__(self, parent):
  56. BaseToolbar.__init__(self, parent)
  57. self.InitToolbar(self._toolbarData())
  58. # realize the toolbar
  59. self.Realize()
  60. def _toolbarData(self):
  61. """!Toolbar data"""
  62. icons = Icons['modeler']
  63. return self._getToolbarData((('new', icons['new'],
  64. self.parent.OnModelNew),
  65. ('open', icons['open'],
  66. self.parent.OnModelOpen),
  67. ('save', icons['save'],
  68. self.parent.OnModelSave),
  69. ('image', icons['toImage'],
  70. self.parent.OnExportImage),
  71. ('python', icons['toPython'],
  72. self.parent.OnExportPython),
  73. (None, ),
  74. ('action', icons['actionAdd'],
  75. self.parent.OnAddAction),
  76. ('data', icons['dataAdd'],
  77. self.parent.OnAddData),
  78. ('relation', icons['relation'],
  79. self.parent.OnDefineRelation),
  80. ('loop', icons['loop'],
  81. self.parent.OnDefineLoop),
  82. (None, ),
  83. ('redraw', icons['redraw'],
  84. self.parent.OnCanvasRefresh),
  85. ('validate', icons['validate'],
  86. self.parent.OnValidateModel),
  87. ('run', icons['run'],
  88. self.parent.OnRunModel),
  89. (None, ),
  90. ("variables", icons['variables'],
  91. self.parent.OnVariables),
  92. ("settings", icons['settings'],
  93. self.parent.OnPreferences),
  94. ("help", Icons['misc']['help'],
  95. self.parent.OnHelp),
  96. (None, ),
  97. ('quit', icons['quit'],
  98. self.parent.OnCloseWindow))
  99. )
  100. class ModelFrame(wx.Frame):
  101. def __init__(self, parent, id = wx.ID_ANY,
  102. title = _("GRASS GIS Graphical Modeler"), **kwargs):
  103. """!Graphical modeler main window
  104. @param parent parent window
  105. @param id window id
  106. @param title window title
  107. @param kwargs wx.Frames' arguments
  108. """
  109. self.parent = parent
  110. self.searchDialog = None # module search dialog
  111. self.baseTitle = title
  112. self.modelFile = None # loaded model
  113. self.modelChanged = False
  114. self.cursors = {
  115. "default" : wx.StockCursor(wx.CURSOR_ARROW),
  116. "cross" : wx.StockCursor(wx.CURSOR_CROSS),
  117. }
  118. wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
  119. self.SetName("Modeler")
  120. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  121. self.menubar = Menu(parent = self, data = ModelerData())
  122. self.SetMenuBar(self.menubar)
  123. self.toolbar = ModelToolbar(parent = self)
  124. self.SetToolBar(self.toolbar)
  125. self.statusbar = self.CreateStatusBar(number = 1)
  126. self.notebook = GNotebook(parent = self,
  127. style = FN.FNB_FANCY_TABS | FN.FNB_BOTTOM |
  128. FN.FNB_NO_NAV_BUTTONS | FN.FNB_NO_X_BUTTON)
  129. self.canvas = ModelCanvas(self)
  130. self.canvas.SetBackgroundColour(wx.WHITE)
  131. self.canvas.SetCursor(self.cursors["default"])
  132. self.model = Model(self.canvas)
  133. self.variablePanel = VariablePanel(parent = self)
  134. self.itemPanel = ItemPanel(parent = self)
  135. self.goutput = GMConsole(parent = self, notebook = self.notebook)
  136. self.notebook.AddPage(page = self.canvas, text=_('Model'), name = 'model')
  137. self.notebook.AddPage(page = self.itemPanel, text=_('Items'), name = 'items')
  138. self.notebook.AddPage(page = self.variablePanel, text=_('Variables'), name = 'variables')
  139. self.notebook.AddPage(page = self.goutput, text=_('Command output'), name = 'output')
  140. wx.CallAfter(self.notebook.SetSelectionByName, 'model')
  141. wx.CallAfter(self.ModelChanged, False)
  142. self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
  143. self.Bind(wx.EVT_SIZE, self.OnSize)
  144. self._layout()
  145. self.SetMinSize((475, 300))
  146. self.SetSize((640, 480))
  147. # fix goutput's pane size
  148. if self.goutput:
  149. self.goutput.SetSashPosition(int(self.GetSize()[1] * .75))
  150. def _layout(self):
  151. """!Do layout"""
  152. sizer = wx.BoxSizer(wx.VERTICAL)
  153. sizer.Add(item = self.notebook, proportion = 1,
  154. flag = wx.EXPAND)
  155. self.SetAutoLayout(True)
  156. self.SetSizer(sizer)
  157. sizer.Fit(self)
  158. self.Layout()
  159. def _addEvent(self, item):
  160. """!Add event to item"""
  161. evthandler = ModelEvtHandler(self.statusbar,
  162. self)
  163. evthandler.SetShape(item)
  164. evthandler.SetPreviousHandler(item.GetEventHandler())
  165. item.SetEventHandler(evthandler)
  166. def GetCanvas(self):
  167. """!Get canvas"""
  168. return self.canvas
  169. def GetModel(self):
  170. """!Get model"""
  171. return self.model
  172. def ModelChanged(self, changed = True):
  173. """!Update window title"""
  174. self.modelChanged = changed
  175. if self.modelFile:
  176. if self.modelChanged:
  177. self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile) + '*')
  178. else:
  179. self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
  180. else:
  181. self.SetTitle(self.baseTitle)
  182. def OnVariables(self, event):
  183. """!Switch to variables page"""
  184. self.notebook.SetSelectionByName('variables')
  185. def OnRemoveItem(self, event):
  186. """!Remove shape
  187. """
  188. self.GetCanvas().RemoveSelected()
  189. def OnCanvasRefresh(self, event):
  190. """!Refresh canvas"""
  191. self.SetStatusText(_("Redrawing model..."), 0)
  192. self.GetCanvas().Refresh()
  193. self.SetStatusText("", 0)
  194. def OnCmdRun(self, event):
  195. """!Run command"""
  196. try:
  197. action = self.GetModel().GetItems()[event.pid]
  198. if hasattr(action, "task"):
  199. action.Update(running = True)
  200. except IndexError:
  201. pass
  202. def OnCmdPrepare(self, event):
  203. """!Prepare for running command"""
  204. event.onPrepare(item = event.userData['item'],
  205. params = event.userData['params'])
  206. def OnCmdDone(self, event):
  207. """!Command done (or aborted)"""
  208. try:
  209. action = self.GetModel().GetItems()[event.pid]
  210. if hasattr(action, "task"):
  211. action.Update(running = True)
  212. except IndexError:
  213. pass
  214. def OnCloseWindow(self, event):
  215. """!Close window"""
  216. if self.modelChanged and \
  217. UserSettings.Get(group='manager', key='askOnQuit', subkey='enabled'):
  218. if self.modelFile:
  219. message = _("Do you want to save changes in the model?")
  220. else:
  221. message = _("Do you want to store current model settings "
  222. "to model file?")
  223. # ask user to save current settings
  224. dlg = wx.MessageDialog(self,
  225. message = message,
  226. caption=_("Quit Graphical Modeler"),
  227. style = wx.YES_NO | wx.YES_DEFAULT |
  228. wx.CANCEL | wx.ICON_QUESTION | wx.CENTRE)
  229. ret = dlg.ShowModal()
  230. if ret == wx.ID_YES:
  231. if not self.modelFile:
  232. self.OnWorkspaceSaveAs()
  233. else:
  234. self.WriteModelFile(self.modelFile)
  235. elif ret == wx.ID_CANCEL:
  236. dlg.Destroy()
  237. return
  238. dlg.Destroy()
  239. self.Destroy()
  240. def OnSize(self, event):
  241. """Window resized, save to the model"""
  242. self.ModelChanged()
  243. event.Skip()
  244. def OnPreferences(self, event):
  245. """!Open preferences dialog"""
  246. dlg = PreferencesDialog(parent = self)
  247. dlg.CenterOnParent()
  248. dlg.ShowModal()
  249. self.canvas.Refresh()
  250. def OnHelp(self, event):
  251. """!Show help"""
  252. if self.parent and self.parent.GetName() == 'LayerManager':
  253. log = self.parent.GetLogWindow()
  254. log.RunCmd(['g.manual',
  255. 'entry=wxGUI.Modeler'])
  256. else:
  257. RunCommand('g.manual',
  258. quiet = True,
  259. entry = 'wxGUI.Modeler')
  260. def OnModelProperties(self, event):
  261. """!Model properties dialog"""
  262. dlg = PropertiesDialog(parent = self)
  263. dlg.CentreOnParent()
  264. properties = self.model.GetProperties()
  265. dlg.Init(properties)
  266. if dlg.ShowModal() == wx.ID_OK:
  267. self.ModelChanged()
  268. for key, value in dlg.GetValues().iteritems():
  269. properties[key] = value
  270. for action in self.model.GetItems(objType = ModelAction):
  271. action.GetTask().set_flag('overwrite', properties['overwrite'])
  272. dlg.Destroy()
  273. def OnDeleteData(self, event):
  274. """!Delete intermediate data"""
  275. rast, vect, rast3d, msg = self.model.GetIntermediateData()
  276. if not rast and not vect and not rast3d:
  277. GMessage(parent = self,
  278. message = _('Nothing to delete.'))
  279. return
  280. dlg = wx.MessageDialog(parent = self,
  281. message= _("Do you want to permanently delete data?%s" % msg),
  282. caption=_("Delete intermediate data?"),
  283. style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
  284. ret = dlg.ShowModal()
  285. if ret == wx.ID_YES:
  286. dlg.Destroy()
  287. if rast:
  288. self.goutput.RunCmd(['g.remove', 'rast=%s' %','.join(rast)])
  289. if rast3d:
  290. self.goutput.RunCmd(['g.remove', 'rast3d=%s' %','.join(rast3d)])
  291. if vect:
  292. self.goutput.RunCmd(['g.remove', 'vect=%s' %','.join(vect)])
  293. self.SetStatusText(_("%d maps deleted from current mapset") % \
  294. int(len(rast) + len(rast3d) + len(vect)))
  295. return
  296. dlg.Destroy()
  297. def OnModelNew(self, event):
  298. """!Create new model"""
  299. Debug.msg(4, "ModelFrame.OnModelNew():")
  300. # ask user to save current model
  301. if self.modelFile and self.modelChanged:
  302. self.OnModelSave()
  303. elif self.modelFile is None and \
  304. (self.model.GetNumItems() > 0 or len(self.model.GetData()) > 0):
  305. dlg = wx.MessageDialog(self, message=_("Current model is not empty. "
  306. "Do you want to store current settings "
  307. "to model file?"),
  308. caption=_("Create new model?"),
  309. style=wx.YES_NO | wx.YES_DEFAULT |
  310. wx.CANCEL | wx.ICON_QUESTION)
  311. ret = dlg.ShowModal()
  312. if ret == wx.ID_YES:
  313. self.OnModelSaveAs()
  314. elif ret == wx.ID_CANCEL:
  315. dlg.Destroy()
  316. return
  317. dlg.Destroy()
  318. # delete all items
  319. self.canvas.GetDiagram().DeleteAllShapes()
  320. self.model.Reset()
  321. self.canvas.Refresh()
  322. self.itemPanel.Update()
  323. self.variablePanel.Reset()
  324. # no model file loaded
  325. self.modelFile = None
  326. self.modelChanged = False
  327. self.SetTitle(self.baseTitle)
  328. def OnModelOpen(self, event):
  329. """!Load model from file"""
  330. filename = ''
  331. dlg = wx.FileDialog(parent = self, message=_("Choose model file"),
  332. defaultDir = os.getcwd(),
  333. wildcard=_("GRASS Model File (*.gxm)|*.gxm"))
  334. if dlg.ShowModal() == wx.ID_OK:
  335. filename = dlg.GetPath()
  336. if not filename:
  337. return
  338. Debug.msg(4, "ModelFrame.OnModelOpen(): filename=%s" % filename)
  339. # close current model
  340. self.OnModelClose()
  341. self.LoadModelFile(filename)
  342. self.modelFile = filename
  343. self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
  344. self.SetStatusText(_('%(items)d items (%(actions)d actions) loaded into model') % \
  345. { 'items' : self.model.GetNumItems(),
  346. 'actions' : self.model.GetNumItems(actionOnly = True) }, 0)
  347. def OnModelSave(self, event = None):
  348. """!Save model to file"""
  349. if self.modelFile and self.modelChanged:
  350. dlg = wx.MessageDialog(self, message=_("Model file <%s> already exists. "
  351. "Do you want to overwrite this file?") % \
  352. self.modelFile,
  353. caption=_("Save model"),
  354. style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
  355. if dlg.ShowModal() == wx.ID_NO:
  356. dlg.Destroy()
  357. else:
  358. Debug.msg(4, "ModelFrame.OnModelSave(): filename=%s" % self.modelFile)
  359. self.WriteModelFile(self.modelFile)
  360. self.SetStatusText(_('File <%s> saved') % self.modelFile, 0)
  361. self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
  362. elif not self.modelFile:
  363. self.OnModelSaveAs(None)
  364. def OnModelSaveAs(self, event):
  365. """!Create model to file as"""
  366. filename = ''
  367. dlg = wx.FileDialog(parent = self,
  368. message = _("Choose file to save current model"),
  369. defaultDir = os.getcwd(),
  370. wildcard=_("GRASS Model File (*.gxm)|*.gxm"),
  371. style=wx.FD_SAVE)
  372. if dlg.ShowModal() == wx.ID_OK:
  373. filename = dlg.GetPath()
  374. if not filename:
  375. return
  376. # check for extension
  377. if filename[-4:] != ".gxm":
  378. filename += ".gxm"
  379. if os.path.exists(filename):
  380. dlg = wx.MessageDialog(parent = self,
  381. message=_("Model file <%s> already exists. "
  382. "Do you want to overwrite this file?") % filename,
  383. caption=_("File already exists"),
  384. style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
  385. if dlg.ShowModal() != wx.ID_YES:
  386. dlg.Destroy()
  387. return
  388. Debug.msg(4, "GMFrame.OnModelSaveAs(): filename=%s" % filename)
  389. self.WriteModelFile(filename)
  390. self.modelFile = filename
  391. self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
  392. self.SetStatusText(_('File <%s> saved') % self.modelFile, 0)
  393. def OnModelClose(self, event = None):
  394. """!Close model file"""
  395. Debug.msg(4, "ModelFrame.OnModelClose(): file=%s" % self.modelFile)
  396. # ask user to save current model
  397. if self.modelFile and self.modelChanged:
  398. self.OnModelSave()
  399. elif self.modelFile is None and \
  400. (self.model.GetNumItems() > 0 or len(self.model.GetData()) > 0):
  401. dlg = wx.MessageDialog(self, message=_("Current model is not empty. "
  402. "Do you want to store current settings "
  403. "to model file?"),
  404. caption=_("Create new model?"),
  405. style=wx.YES_NO | wx.YES_DEFAULT |
  406. wx.CANCEL | wx.ICON_QUESTION)
  407. ret = dlg.ShowModal()
  408. if ret == wx.ID_YES:
  409. self.OnModelSaveAs()
  410. elif ret == wx.ID_CANCEL:
  411. dlg.Destroy()
  412. return
  413. dlg.Destroy()
  414. self.modelFile = None
  415. self.SetTitle(self.baseTitle)
  416. self.canvas.GetDiagram().DeleteAllShapes()
  417. self.model.Reset()
  418. self.canvas.Refresh()
  419. def OnRunModel(self, event):
  420. """!Run entire model"""
  421. self.model.Run(self.goutput, self.OnDone, parent = self)
  422. def OnDone(self, cmd, returncode):
  423. """!Computation finished"""
  424. self.SetStatusText('', 0)
  425. # restore original files
  426. if hasattr(self.model, "fileInput"):
  427. for finput in self.model.fileInput:
  428. data = self.model.fileInput[finput]
  429. if not data:
  430. continue
  431. fd = open(finput, "w")
  432. try:
  433. fd.write(data)
  434. finally:
  435. fd.close()
  436. del self.model.fileInput
  437. def OnValidateModel(self, event, showMsg = True):
  438. """!Validate entire model"""
  439. if self.model.GetNumItems() < 1:
  440. GMessage(parent = self,
  441. message = _('Model is empty. Nothing to validate.'))
  442. return
  443. self.SetStatusText(_('Validating model...'), 0)
  444. errList = self.model.Validate()
  445. self.SetStatusText('', 0)
  446. if errList:
  447. GWarning(parent = self,
  448. message = _('Model is not valid.\n\n%s') % '\n'.join(errList))
  449. else:
  450. GMessage(parent = self,
  451. message = _('Model is valid.'))
  452. def OnExportImage(self, event):
  453. """!Export model to image (default image)
  454. """
  455. xminImg = 0
  456. xmaxImg = 0
  457. yminImg = 0
  458. ymaxImg = 0
  459. # get current size of canvas
  460. for shape in self.canvas.GetDiagram().GetShapeList():
  461. w, h = shape.GetBoundingBoxMax()
  462. x = shape.GetX()
  463. y = shape.GetY()
  464. xmin = x - w / 2
  465. xmax = x + w / 2
  466. ymin = y - h / 2
  467. ymax = y + h / 2
  468. if xmin < xminImg:
  469. xminImg = xmin
  470. if xmax > xmaxImg:
  471. xmaxImg = xmax
  472. if ymin < yminImg:
  473. yminImg = ymin
  474. if ymax > ymaxImg:
  475. ymaxImg = ymax
  476. size = wx.Size(int(xmaxImg - xminImg) + 50,
  477. int(ymaxImg - yminImg) + 50)
  478. bitmap = wx.EmptyBitmap(width = size.width, height = size.height)
  479. filetype, ltype = GetImageHandlers(wx.ImageFromBitmap(bitmap))
  480. dlg = wx.FileDialog(parent = self,
  481. message = _("Choose a file name to save the image (no need to add extension)"),
  482. defaultDir = "",
  483. defaultFile = "",
  484. wildcard = filetype,
  485. style=wx.SAVE | wx.FD_OVERWRITE_PROMPT)
  486. if dlg.ShowModal() == wx.ID_OK:
  487. path = dlg.GetPath()
  488. if not path:
  489. dlg.Destroy()
  490. return
  491. base, ext = os.path.splitext(path)
  492. fileType = ltype[dlg.GetFilterIndex()]['type']
  493. extType = ltype[dlg.GetFilterIndex()]['ext']
  494. if ext != extType:
  495. path = base + '.' + extType
  496. dc = wx.MemoryDC(bitmap)
  497. dc.SetBackground(wx.WHITE_BRUSH)
  498. dc.SetBackgroundMode(wx.SOLID)
  499. dc.BeginDrawing()
  500. self.canvas.GetDiagram().Clear(dc)
  501. self.canvas.GetDiagram().Redraw(dc)
  502. dc.EndDrawing()
  503. bitmap.SaveFile(path, fileType)
  504. self.SetStatusText(_("Model exported to <%s>") % path)
  505. dlg.Destroy()
  506. def OnExportPython(self, event):
  507. """!Export model to Python script"""
  508. filename = ''
  509. dlg = wx.FileDialog(parent = self,
  510. message = _("Choose file to save"),
  511. defaultDir = os.getcwd(),
  512. wildcard=_("Python script (*.py)|*.py"),
  513. style=wx.FD_SAVE)
  514. if dlg.ShowModal() == wx.ID_OK:
  515. filename = dlg.GetPath()
  516. if not filename:
  517. return
  518. # check for extension
  519. if filename[-3:] != ".py":
  520. filename += ".py"
  521. if os.path.exists(filename):
  522. dlg = wx.MessageDialog(self, message=_("File <%s> already exists. "
  523. "Do you want to overwrite this file?") % filename,
  524. caption=_("Save file"),
  525. style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
  526. if dlg.ShowModal() == wx.ID_NO:
  527. dlg.Destroy()
  528. return
  529. dlg.Destroy()
  530. fd = open(filename, "w")
  531. try:
  532. WritePythonFile(fd, self.model)
  533. finally:
  534. fd.close()
  535. # executable file
  536. os.chmod(filename, stat.S_IRWXU | stat.S_IWUSR)
  537. self.SetStatusText(_("Model exported to <%s>") % filename)
  538. def OnDefineRelation(self, event):
  539. """!Define relation between data and action items"""
  540. self.canvas.SetCursor(self.cursors["cross"])
  541. self.defineRelation = { 'from' : None,
  542. 'to' : None }
  543. def OnDefineLoop(self, event):
  544. """!Define new loop in the model"""
  545. self.ModelChanged()
  546. width, height = self.canvas.GetSize()
  547. loop = ModelLoop(self, x = width/2, y = height/2,
  548. id = self.model.GetNumItems() + 1)
  549. self.canvas.diagram.AddShape(loop)
  550. loop.Show(True)
  551. self._addEvent(loop)
  552. self.model.AddItem(loop)
  553. self.canvas.Refresh()
  554. def OnDefineCondition(self, event):
  555. """!Define new condition in the model"""
  556. self.ModelChanged()
  557. width, height = self.canvas.GetSize()
  558. cond = ModelCondition(self, x = width/2, y = height/2,
  559. id = self.model.GetNumItems() + 1)
  560. self.canvas.diagram.AddShape(cond)
  561. cond.Show(True)
  562. self._addEvent(cond)
  563. self.model.AddItem(cond)
  564. self.canvas.Refresh()
  565. def OnAddAction(self, event):
  566. """!Add action to model"""
  567. if self.searchDialog is None:
  568. self.searchDialog = ModelSearchDialog(self)
  569. self.searchDialog.CentreOnParent()
  570. else:
  571. self.searchDialog.Reset()
  572. if self.searchDialog.ShowModal() == wx.ID_CANCEL:
  573. self.searchDialog.Hide()
  574. return
  575. cmd = self.searchDialog.GetCmd()
  576. self.searchDialog.Hide()
  577. self.ModelChanged()
  578. # add action to canvas
  579. width, height = self.canvas.GetSize()
  580. action = ModelAction(self.model, cmd = cmd, x = width/2, y = height/2,
  581. id = self.model.GetNextId())
  582. overwrite = self.model.GetProperties().get('overwrite', None)
  583. if overwrite is not None:
  584. action.GetTask().set_flag('overwrite', overwrite)
  585. self.canvas.diagram.AddShape(action)
  586. action.Show(True)
  587. self._addEvent(action)
  588. self.model.AddItem(action)
  589. self.itemPanel.Update()
  590. self.canvas.Refresh()
  591. time.sleep(.1)
  592. # show properties dialog
  593. win = action.GetPropDialog()
  594. if not win:
  595. if action.IsValid():
  596. self.GetOptData(dcmd = action.GetLog(string = False), layer = action,
  597. params = action.GetParams(), propwin = None)
  598. else:
  599. GUI(parent = self, show = True).ParseCommand(action.GetLog(string = False),
  600. completed = (self.GetOptData, action, action.GetParams()))
  601. elif win and not win.IsShown():
  602. win.Show()
  603. if win:
  604. win.Raise()
  605. def OnAddData(self, event):
  606. """!Add data item to model
  607. """
  608. # add action to canvas
  609. width, height = self.canvas.GetSize()
  610. data = ModelData(self, x = width/2, y = height/2)
  611. dlg = ModelDataDialog(parent = self, shape = data)
  612. data.SetPropDialog(dlg)
  613. dlg.CentreOnParent()
  614. ret = dlg.ShowModal()
  615. dlg.Destroy()
  616. if ret != wx.ID_OK:
  617. return
  618. data.Update()
  619. self.canvas.diagram.AddShape(data)
  620. data.Show(True)
  621. self.ModelChanged()
  622. self._addEvent(data)
  623. self.model.AddItem(data)
  624. self.canvas.Refresh()
  625. def OnHelp(self, event):
  626. """!Display manual page"""
  627. grass.run_command('g.manual',
  628. entry = 'wxGUI.Modeler')
  629. def OnAbout(self, event):
  630. """!Display About window"""
  631. info = wx.AboutDialogInfo()
  632. info.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  633. info.SetName(_('wxGUI Graphical Modeler'))
  634. info.SetWebSite('http://grass.osgeo.org')
  635. year = grass.version()['date']
  636. info.SetDescription(_('(C) 2010-%s by the GRASS Development Team\n\n') % year +
  637. '\n'.join(textwrap.wrap(_('This program is free software under the GNU General Public License'
  638. '(>=v2). Read the file COPYING that comes with GRASS for details.'), 75)))
  639. wx.AboutBox(info)
  640. def GetOptData(self, dcmd, layer, params, propwin):
  641. """!Process action data"""
  642. if params: # add data items
  643. width, height = self.canvas.GetSize()
  644. x = [width/2 + 200, width/2 - 200]
  645. for p in params['params']:
  646. if p.get('prompt', '') in ('raster', 'vector', 'raster3d') and \
  647. (p.get('value', None) or \
  648. (p.get('age', 'old') != 'old' and p.get('required', 'no') == 'yes')):
  649. data = layer.FindData(p.get('name', ''))
  650. if data:
  651. data.SetValue(p.get('value', ''))
  652. data.Update()
  653. continue
  654. data = self.model.FindData(p.get('value', ''),
  655. p.get('prompt', ''))
  656. if data:
  657. if p.get('age', 'old') == 'old':
  658. rel = ModelRelation(parent = self, fromShape = data,
  659. toShape = layer, param = p.get('name', ''))
  660. else:
  661. rel = ModelRelation(parent = self, fromShape = layer,
  662. toShape = data, param = p.get('name', ''))
  663. layer.AddRelation(rel)
  664. data.AddRelation(rel)
  665. self.AddLine(rel)
  666. data.Update()
  667. continue
  668. data = ModelData(self, value = p.get('value', ''),
  669. prompt = p.get('prompt', ''),
  670. x = x.pop(), y = height/2)
  671. self._addEvent(data)
  672. self.canvas.diagram.AddShape(data)
  673. data.Show(True)
  674. if p.get('age', 'old') == 'old':
  675. rel = ModelRelation(parent = self, fromShape = data,
  676. toShape = layer, param = p.get('name', ''))
  677. else:
  678. rel = ModelRelation(parent = self, fromShape = layer,
  679. toShape = data, param = p.get('name', ''))
  680. layer.AddRelation(rel)
  681. data.AddRelation(rel)
  682. self.AddLine(rel)
  683. data.Update()
  684. # valid / parameterized ?
  685. layer.SetValid(params)
  686. self.canvas.Refresh()
  687. if dcmd:
  688. layer.SetProperties(params, propwin)
  689. self.SetStatusText(layer.GetLog(), 0)
  690. def AddLine(self, rel):
  691. """!Add connection between model objects
  692. @param rel relation
  693. """
  694. fromShape = rel.GetFrom()
  695. toShape = rel.GetTo()
  696. rel.SetCanvas(self)
  697. rel.SetPen(wx.BLACK_PEN)
  698. rel.SetBrush(wx.BLACK_BRUSH)
  699. rel.AddArrow(ogl.ARROW_ARROW)
  700. points = rel.GetControlPoints()
  701. rel.MakeLineControlPoints(2)
  702. if points:
  703. for x, y in points:
  704. rel.InsertLineControlPoint(point = wx.RealPoint(x, y))
  705. self._addEvent(rel)
  706. try:
  707. fromShape.AddLine(rel, toShape)
  708. except TypeError:
  709. pass # bug when connecting ModelCondition and ModelLoop - to be fixed
  710. self.canvas.diagram.AddShape(rel)
  711. rel.Show(True)
  712. def LoadModelFile(self, filename):
  713. """!Load model definition stored in GRASS Model XML file (gxm)
  714. """
  715. try:
  716. self.model.LoadModel(filename)
  717. except GException, e:
  718. GError(parent = self,
  719. message = _("Reading model file <%s> failed.\n"
  720. "Invalid file, unable to parse XML document.") % filename)
  721. self.modelFile = filename
  722. self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
  723. self.SetStatusText(_("Please wait, loading model..."), 0)
  724. # load actions
  725. for item in self.model.GetItems(objType = ModelAction):
  726. self._addEvent(item)
  727. self.canvas.diagram.AddShape(item)
  728. item.Show(True)
  729. # relations/data
  730. for rel in item.GetRelations():
  731. if rel.GetFrom() == item:
  732. dataItem = rel.GetTo()
  733. else:
  734. dataItem = rel.GetFrom()
  735. self._addEvent(dataItem)
  736. self.canvas.diagram.AddShape(dataItem)
  737. self.AddLine(rel)
  738. dataItem.Show(True)
  739. # load loops
  740. for item in self.model.GetItems(objType = ModelLoop):
  741. self._addEvent(item)
  742. self.canvas.diagram.AddShape(item)
  743. item.Show(True)
  744. # connect items in the loop
  745. self.DefineLoop(item)
  746. # load conditions
  747. for item in self.model.GetItems(objType = ModelCondition):
  748. self._addEvent(item)
  749. self.canvas.diagram.AddShape(item)
  750. item.Show(True)
  751. # connect items in the condition
  752. self.DefineCondition(item)
  753. # load variables
  754. self.variablePanel.Update()
  755. self.itemPanel.Update()
  756. self.SetStatusText('', 0)
  757. # final updates
  758. for action in self.model.GetItems(objType = ModelAction):
  759. action.SetValid(action.GetParams())
  760. action.Update()
  761. self.canvas.Refresh(True)
  762. def WriteModelFile(self, filename):
  763. """!Save model to model file, recover original file on error.
  764. @return True on success
  765. @return False on failure
  766. """
  767. self.ModelChanged(False)
  768. tmpfile = tempfile.TemporaryFile(mode='w+b')
  769. try:
  770. WriteModelFile(fd = tmpfile, model = self.model)
  771. except StandardError:
  772. GError(parent = self,
  773. message = _("Writing current settings to model file failed."))
  774. return False
  775. try:
  776. mfile = open(filename, "w")
  777. tmpfile.seek(0)
  778. for line in tmpfile.readlines():
  779. mfile.write(line)
  780. except IOError:
  781. wx.MessageBox(parent = self,
  782. message = _("Unable to open file <%s> for writing.") % filename,
  783. caption = _("Error"),
  784. style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
  785. return False
  786. mfile.close()
  787. return True
  788. def DefineLoop(self, loop):
  789. """!Define loop with given list of items"""
  790. parent = loop
  791. items = loop.GetItems()
  792. if not items:
  793. return
  794. # remove defined relations first
  795. for rel in loop.GetRelations():
  796. self.canvas.GetDiagram().RemoveShape(rel)
  797. loop.Clear()
  798. for item in items:
  799. rel = ModelRelation(parent = self, fromShape = parent, toShape = item)
  800. dx = item.GetX() - parent.GetX()
  801. dy = item.GetY() - parent.GetY()
  802. loop.AddRelation(rel)
  803. if dx != 0:
  804. rel.SetControlPoints(((parent.GetX(), parent.GetY() + dy / 2),
  805. (parent.GetX() + dx, parent.GetY() + dy / 2)))
  806. self.AddLine(rel)
  807. parent = item
  808. # close loop
  809. item = loop.GetItems()[-1]
  810. rel = ModelRelation(parent = self, fromShape = item, toShape = loop)
  811. loop.AddRelation(rel)
  812. self.AddLine(rel)
  813. dx = (item.GetX() - loop.GetX()) + loop.GetWidth() / 2 + 50
  814. dy = item.GetHeight() / 2 + 50
  815. rel.MakeLineControlPoints(0)
  816. rel.InsertLineControlPoint(point = wx.RealPoint(loop.GetX() - loop.GetWidth() / 2 ,
  817. loop.GetY()))
  818. rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX(),
  819. item.GetY() + item.GetHeight() / 2))
  820. rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX(),
  821. item.GetY() + dy))
  822. rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - dx,
  823. item.GetY() + dy))
  824. rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - dx,
  825. loop.GetY()))
  826. self.canvas.Refresh()
  827. def DefineCondition(self, condition):
  828. """!Define if-else statement with given list of items"""
  829. parent = condition
  830. items = condition.GetItems()
  831. if not items['if'] and not items['else']:
  832. return
  833. # remove defined relations first
  834. for rel in condition.GetRelations():
  835. self.canvas.GetDiagram().RemoveShape(rel)
  836. condition.Clear()
  837. dxIf = condition.GetX() + condition.GetWidth() / 2
  838. dxElse = condition.GetX() - condition.GetWidth() / 2
  839. dy = condition.GetY()
  840. for branch in items.keys():
  841. for item in items[branch]:
  842. rel = ModelRelation(parent = self, fromShape = parent,
  843. toShape = item)
  844. condition.AddRelation(rel)
  845. self.AddLine(rel)
  846. rel.MakeLineControlPoints(0)
  847. if branch == 'if':
  848. rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - item.GetWidth() / 2, item.GetY()))
  849. rel.InsertLineControlPoint(point = wx.RealPoint(dxIf, dy))
  850. else:
  851. rel.InsertLineControlPoint(point = wx.RealPoint(dxElse, dy))
  852. rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - item.GetWidth() / 2, item.GetY()))
  853. parent = item
  854. self.canvas.Refresh()
  855. class ModelCanvas(ogl.ShapeCanvas):
  856. """!Canvas where model is drawn"""
  857. def __init__(self, parent):
  858. self.parent = parent
  859. ogl.OGLInitialize()
  860. ogl.ShapeCanvas.__init__(self, parent)
  861. self.diagram = ogl.Diagram()
  862. self.SetDiagram(self.diagram)
  863. self.diagram.SetCanvas(self)
  864. self.SetScrollbars(20, 20, 1000/20, 1000/20)
  865. self.Bind(wx.EVT_CHAR, self.OnChar)
  866. def OnChar(self, event):
  867. """!Key pressed"""
  868. kc = event.GetKeyCode()
  869. diagram = self.GetDiagram()
  870. if kc == wx.WXK_DELETE:
  871. self.RemoveSelected()
  872. def RemoveSelected(self):
  873. """!Remove selected shapes"""
  874. self.parent.ModelChanged()
  875. diagram = self.GetDiagram()
  876. for shape in diagram.GetShapeList():
  877. if not shape.Selected():
  878. continue
  879. remList, upList = self.parent.GetModel().RemoveItem(shape)
  880. shape.Select(False)
  881. diagram.RemoveShape(shape)
  882. shape.__del__()
  883. for item in remList:
  884. diagram.RemoveShape(item)
  885. item.__del__()
  886. for item in upList:
  887. item.Update()
  888. self.Refresh()
  889. class ModelEvtHandler(ogl.ShapeEvtHandler):
  890. """!Model event handler class"""
  891. def __init__(self, log, frame):
  892. ogl.ShapeEvtHandler.__init__(self)
  893. self.log = log
  894. self.frame = frame
  895. self.x = self.y = None
  896. def OnLeftClick(self, x, y, keys = 0, attachment = 0):
  897. """!Left mouse button pressed -> select item & update statusbar"""
  898. shape = self.GetShape()
  899. canvas = shape.GetCanvas()
  900. dc = wx.ClientDC(canvas)
  901. canvas.PrepareDC(dc)
  902. if hasattr(self.frame, 'defineRelation'):
  903. drel = self.frame.defineRelation
  904. if drel['from'] is None:
  905. drel['from'] = shape
  906. elif drel['to'] is None:
  907. drel['to'] = shape
  908. rel = ModelRelation(parent = self.frame, fromShape = drel['from'],
  909. toShape = drel['to'])
  910. dlg = ModelRelationDialog(parent = self.frame,
  911. shape = rel)
  912. if dlg.IsValid():
  913. ret = dlg.ShowModal()
  914. if ret == wx.ID_OK:
  915. option = dlg.GetOption()
  916. rel.SetName(option)
  917. drel['from'].AddRelation(rel)
  918. drel['to'].AddRelation(rel)
  919. drel['from'].Update()
  920. params = { 'params' : [{ 'name' : option,
  921. 'value' : drel['from'].GetValue()}] }
  922. drel['to'].MergeParams(params)
  923. self.frame.AddLine(rel)
  924. dlg.Destroy()
  925. del self.frame.defineRelation
  926. if shape.Selected():
  927. shape.Select(False, dc)
  928. else:
  929. redraw = False
  930. shapeList = canvas.GetDiagram().GetShapeList()
  931. toUnselect = list()
  932. for s in shapeList:
  933. if s.Selected():
  934. toUnselect.append(s)
  935. shape.Select(True, dc)
  936. for s in toUnselect:
  937. s.Select(False, dc)
  938. canvas.Refresh(False)
  939. if hasattr(shape, "GetLog"):
  940. self.log.SetStatusText(shape.GetLog(), 0)
  941. else:
  942. self.log.SetStatusText('', 0)
  943. def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0):
  944. """!Left mouse button pressed (double-click) -> show properties"""
  945. self.OnProperties()
  946. def OnProperties(self, event = None):
  947. """!Show properties dialog"""
  948. self.frame.ModelChanged()
  949. shape = self.GetShape()
  950. if isinstance(shape, ModelAction):
  951. module = GUI(parent = self.frame, show = True).ParseCommand(shape.GetLog(string = False),
  952. completed = (self.frame.GetOptData, shape, shape.GetParams()))
  953. elif isinstance(shape, ModelData):
  954. dlg = ModelDataDialog(parent = self.frame, shape = shape)
  955. shape.SetPropDialog(dlg)
  956. dlg.CentreOnParent()
  957. dlg.Show()
  958. elif isinstance(shape, ModelLoop):
  959. dlg = ModelLoopDialog(parent = self.frame, shape = shape)
  960. dlg.CentreOnParent()
  961. if dlg.ShowModal() == wx.ID_OK:
  962. shape.SetText(dlg.GetCondition())
  963. alist = list()
  964. ids = dlg.GetItems()
  965. for aId in ids['unchecked']:
  966. action = self.frame.GetModel().GetItem(aId)
  967. action.UnSetBlock(shape)
  968. for aId in ids['checked']:
  969. action = self.frame.GetModel().GetItem(aId)
  970. action.SetBlock(shape)
  971. if action:
  972. alist.append(action)
  973. shape.SetItems(alist)
  974. self.frame.DefineLoop(shape)
  975. self.frame.SetStatusText(shape.GetLog(), 0)
  976. self.frame.GetCanvas().Refresh()
  977. dlg.Destroy()
  978. elif isinstance(shape, ModelCondition):
  979. dlg = ModelConditionDialog(parent = self.frame, shape = shape)
  980. dlg.CentreOnParent()
  981. if dlg.ShowModal() == wx.ID_OK:
  982. shape.SetText(dlg.GetCondition())
  983. ids = dlg.GetItems()
  984. for b in ids.keys():
  985. alist = list()
  986. for aId in ids[b]['unchecked']:
  987. action = self.frame.GetModel().GetItem(aId)
  988. action.UnSetBlock(shape)
  989. for aId in ids[b]['checked']:
  990. action = self.frame.GetModel().GetItem(aId)
  991. action.SetBlock(shape)
  992. if action:
  993. alist.append(action)
  994. shape.SetItems(alist, branch = b)
  995. self.frame.DefineCondition(shape)
  996. self.frame.GetCanvas().Refresh()
  997. dlg.Destroy()
  998. def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
  999. """!Drag shape (begining)"""
  1000. self.frame.ModelChanged()
  1001. if self._previousHandler:
  1002. self._previousHandler.OnBeginDragLeft(x, y, keys, attachment)
  1003. def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
  1004. """!Drag shape (end)"""
  1005. if self._previousHandler:
  1006. self._previousHandler.OnEndDragLeft(x, y, keys, attachment)
  1007. shape = self.GetShape()
  1008. if isinstance(shape, ModelLoop):
  1009. self.frame.DefineLoop(shape)
  1010. elif isinstance(shape, ModelCondition):
  1011. self.frame.DefineCondition(shape)
  1012. for mo in shape.GetBlock():
  1013. if isinstance(mo, ModelLoop):
  1014. self.frame.DefineLoop(mo)
  1015. elif isinstance(mo, ModelCondition):
  1016. self.frame.DefineCondition(mo)
  1017. def OnEndSize(self, x, y):
  1018. """!Resize shape"""
  1019. self.frame.ModelChanged()
  1020. if self._previousHandler:
  1021. self._previousHandler.OnEndSize(x, y)
  1022. def OnRightClick(self, x, y, keys = 0, attachment = 0):
  1023. """!Right click -> pop-up menu"""
  1024. if not hasattr (self, "popupID"):
  1025. self.popupID = dict()
  1026. for key in ('remove', 'enable', 'addPoint',
  1027. 'delPoint', 'intermediate', 'props', 'id'):
  1028. self.popupID[key] = wx.NewId()
  1029. # record coordinates
  1030. self.x = x
  1031. self.y = y
  1032. shape = self.GetShape()
  1033. popupMenu = wx.Menu()
  1034. popupMenu.Append(self.popupID['remove'], text=_('Remove'))
  1035. self.frame.Bind(wx.EVT_MENU, self.OnRemove, id = self.popupID['remove'])
  1036. if isinstance(shape, ModelAction) or isinstance(shape, ModelLoop):
  1037. if shape.IsEnabled():
  1038. popupMenu.Append(self.popupID['enable'], text=_('Disable'))
  1039. self.frame.Bind(wx.EVT_MENU, self.OnDisable, id = self.popupID['enable'])
  1040. else:
  1041. popupMenu.Append(self.popupID['enable'], text=_('Enable'))
  1042. self.frame.Bind(wx.EVT_MENU, self.OnEnable, id = self.popupID['enable'])
  1043. if isinstance(shape, ModelRelation):
  1044. popupMenu.AppendSeparator()
  1045. popupMenu.Append(self.popupID['addPoint'], text=_('Add control point'))
  1046. self.frame.Bind(wx.EVT_MENU, self.OnAddPoint, id = self.popupID['addPoint'])
  1047. popupMenu.Append(self.popupID['delPoint'], text=_('Remove control point'))
  1048. self.frame.Bind(wx.EVT_MENU, self.OnRemovePoint, id = self.popupID['delPoint'])
  1049. if len(shape.GetLineControlPoints()) == 2:
  1050. popupMenu.Enable(self.popupID['delPoint'], False)
  1051. if isinstance(shape, ModelData) and '@' not in shape.GetValue():
  1052. popupMenu.AppendSeparator()
  1053. popupMenu.Append(self.popupID['intermediate'], text=_('Intermediate'),
  1054. kind = wx.ITEM_CHECK)
  1055. if self.GetShape().IsIntermediate():
  1056. popupMenu.Check(self.popupID['intermediate'], True)
  1057. self.frame.Bind(wx.EVT_MENU, self.OnIntermediate, id = self.popupID['intermediate'])
  1058. if isinstance(shape, ModelData) or \
  1059. isinstance(shape, ModelAction) or \
  1060. isinstance(shape, ModelLoop):
  1061. popupMenu.AppendSeparator()
  1062. popupMenu.Append(self.popupID['props'], text=_('Properties'))
  1063. self.frame.Bind(wx.EVT_MENU, self.OnProperties, id = self.popupID['props'])
  1064. self.frame.PopupMenu(popupMenu)
  1065. popupMenu.Destroy()
  1066. def OnDisable(self, event):
  1067. """!Disable action"""
  1068. self._onEnable(False)
  1069. def OnEnable(self, event):
  1070. """!Disable action"""
  1071. self._onEnable(True)
  1072. def _onEnable(self, enable):
  1073. shape = self.GetShape()
  1074. shape.Enable(enable)
  1075. self.frame.ModelChanged()
  1076. self.frame.canvas.Refresh()
  1077. def OnAddPoint(self, event):
  1078. """!Add control point"""
  1079. shape = self.GetShape()
  1080. shape.InsertLineControlPoint(point = wx.RealPoint(self.x, self.y))
  1081. shape.ResetShapes()
  1082. shape.Select(True)
  1083. self.frame.ModelChanged()
  1084. self.frame.canvas.Refresh()
  1085. def OnRemovePoint(self, event):
  1086. """!Remove control point"""
  1087. shape = self.GetShape()
  1088. shape.DeleteLineControlPoint()
  1089. shape.Select(False)
  1090. shape.Select(True)
  1091. self.frame.ModelChanged()
  1092. self.frame.canvas.Refresh()
  1093. def OnIntermediate(self, event):
  1094. """!Mark data as intermediate"""
  1095. self.frame.ModelChanged()
  1096. shape = self.GetShape()
  1097. shape.SetIntermediate(event.IsChecked())
  1098. self.frame.canvas.Refresh()
  1099. def OnRemove(self, event):
  1100. """!Remove shape
  1101. """
  1102. self.frame.GetCanvas().RemoveSelected()
  1103. self.frame.itemPanel.Update()
  1104. class ModelListCtrl(wx.ListCtrl,
  1105. listmix.ListCtrlAutoWidthMixin,
  1106. listmix.TextEditMixin,
  1107. listmix.ColumnSorterMixin):
  1108. def __init__(self, parent, columns, id = wx.ID_ANY,
  1109. style = wx.LC_REPORT | wx.BORDER_NONE |
  1110. wx.LC_SORT_ASCENDING |wx.LC_HRULES |
  1111. wx.LC_VRULES, **kwargs):
  1112. """!List of model variables"""
  1113. self.parent = parent
  1114. self.columns = columns
  1115. self.shape = None
  1116. try:
  1117. self.frame = parent.parent
  1118. except AttributeError:
  1119. self.frame = None
  1120. wx.ListCtrl.__init__(self, parent, id = id, style = style, **kwargs)
  1121. listmix.ListCtrlAutoWidthMixin.__init__(self)
  1122. listmix.TextEditMixin.__init__(self)
  1123. listmix.ColumnSorterMixin.__init__(self, 4)
  1124. i = 0
  1125. for col in columns:
  1126. self.InsertColumn(i, col)
  1127. self.SetColumnWidth(i, wx.LIST_AUTOSIZE_USEHEADER)
  1128. i += 1
  1129. self.itemDataMap = {} # requested by sorter
  1130. self.itemCount = 0
  1131. self.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.OnBeginEdit)
  1132. self.Bind(wx.EVT_LIST_END_LABEL_EDIT, self.OnEndEdit)
  1133. self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick)
  1134. self.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnRightUp) #wxMSW
  1135. self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp) #wxGTK
  1136. def OnBeginEdit(self, event):
  1137. """!Editing of item started"""
  1138. event.Allow()
  1139. def OnEndEdit(self, event):
  1140. """!Finish editing of item"""
  1141. pass
  1142. def OnColClick(self, event):
  1143. """!Click on column header (order by)"""
  1144. event.Skip()
  1145. class VariablePanel(wx.Panel):
  1146. def __init__(self, parent, id = wx.ID_ANY,
  1147. **kwargs):
  1148. """!Manage model variables panel
  1149. """
  1150. self.parent = parent
  1151. wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
  1152. self.listBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
  1153. label=" %s " % _("List of variables - right-click to delete"))
  1154. self.list = VariableListCtrl(parent = self,
  1155. columns = [_("Name"), _("Data type"),
  1156. _("Default value"), _("Description")])
  1157. # add new category
  1158. self.addBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
  1159. label = " %s " % _("Add new variable"))
  1160. self.name = wx.TextCtrl(parent = self, id = wx.ID_ANY)
  1161. wx.CallAfter(self.name.SetFocus)
  1162. self.type = wx.Choice(parent = self, id = wx.ID_ANY,
  1163. choices = [_("integer"),
  1164. _("float"),
  1165. _("string"),
  1166. _("raster"),
  1167. _("vector"),
  1168. _("mapset"),
  1169. _("file")])
  1170. self.type.SetSelection(2) # string
  1171. self.value = wx.TextCtrl(parent = self, id = wx.ID_ANY)
  1172. self.desc = wx.TextCtrl(parent = self, id = wx.ID_ANY)
  1173. # buttons
  1174. self.btnAdd = wx.Button(parent = self, id = wx.ID_ADD)
  1175. self.btnAdd.SetToolTipString(_("Add new variable to the model"))
  1176. self.btnAdd.Enable(False)
  1177. # bindings
  1178. self.name.Bind(wx.EVT_TEXT, self.OnText)
  1179. self.value.Bind(wx.EVT_TEXT, self.OnText)
  1180. self.desc.Bind(wx.EVT_TEXT, self.OnText)
  1181. self.btnAdd.Bind(wx.EVT_BUTTON, self.OnAdd)
  1182. self._layout()
  1183. def _layout(self):
  1184. """!Layout dialog"""
  1185. listSizer = wx.StaticBoxSizer(self.listBox, wx.VERTICAL)
  1186. listSizer.Add(item = self.list, proportion = 1,
  1187. flag = wx.EXPAND)
  1188. addSizer = wx.StaticBoxSizer(self.addBox, wx.VERTICAL)
  1189. gridSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
  1190. gridSizer.AddGrowableCol(1)
  1191. gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
  1192. label = "%s:" % _("Name")),
  1193. flag = wx.ALIGN_CENTER_VERTICAL,
  1194. pos = (0, 0))
  1195. gridSizer.Add(item = self.name,
  1196. pos = (0, 1),
  1197. flag = wx.EXPAND)
  1198. gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
  1199. label = "%s:" % _("Data type")),
  1200. flag = wx.ALIGN_CENTER_VERTICAL,
  1201. pos = (0, 2))
  1202. gridSizer.Add(item = self.type,
  1203. pos = (0, 3))
  1204. gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
  1205. label = "%s:" % _("Default value")),
  1206. flag = wx.ALIGN_CENTER_VERTICAL,
  1207. pos = (1, 0))
  1208. gridSizer.Add(item = self.value,
  1209. pos = (1, 1), span = (1, 3),
  1210. flag = wx.EXPAND)
  1211. gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
  1212. label = "%s:" % _("Description")),
  1213. flag = wx.ALIGN_CENTER_VERTICAL,
  1214. pos = (2, 0))
  1215. gridSizer.Add(item = self.desc,
  1216. pos = (2, 1), span = (1, 3),
  1217. flag = wx.EXPAND)
  1218. addSizer.Add(item = gridSizer,
  1219. flag = wx.EXPAND)
  1220. addSizer.Add(item = self.btnAdd, proportion = 0,
  1221. flag = wx.TOP | wx.ALIGN_RIGHT, border = 5)
  1222. mainSizer = wx.BoxSizer(wx.VERTICAL)
  1223. mainSizer.Add(item = listSizer, proportion = 1,
  1224. flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
  1225. mainSizer.Add(item = addSizer, proportion = 0,
  1226. flag = wx.EXPAND | wx.ALIGN_CENTER |
  1227. wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
  1228. self.SetSizer(mainSizer)
  1229. mainSizer.Fit(self)
  1230. def OnText(self, event):
  1231. """!Text entered"""
  1232. if self.name.GetValue():
  1233. self.btnAdd.Enable()
  1234. else:
  1235. self.btnAdd.Enable(False)
  1236. def OnAdd(self, event):
  1237. """!Add new variable to the list"""
  1238. msg = self.list.Append(self.name.GetValue(),
  1239. self.type.GetStringSelection(),
  1240. self.value.GetValue(),
  1241. self.desc.GetValue())
  1242. self.name.SetValue('')
  1243. self.name.SetFocus()
  1244. if msg:
  1245. GError(parent = self,
  1246. message = msg)
  1247. else:
  1248. self.type.SetSelection(2) # string
  1249. self.value.SetValue('')
  1250. self.desc.SetValue('')
  1251. self.UpdateModelVariables()
  1252. def UpdateModelVariables(self):
  1253. """!Update model variables"""
  1254. variables = dict()
  1255. for values in self.list.GetData().itervalues():
  1256. name = values[0]
  1257. variables[name] = { 'type' : str(values[1]) }
  1258. if values[2]:
  1259. variables[name]['value'] = values[2]
  1260. if values[3]:
  1261. variables[name]['description'] = values[3]
  1262. self.parent.GetModel().SetVariables(variables)
  1263. self.parent.ModelChanged()
  1264. def Update(self):
  1265. """!Reload list of variables"""
  1266. self.list.OnReload(None)
  1267. def Reset(self):
  1268. """!Remove all variables"""
  1269. self.list.DeleteAllItems()
  1270. self.parent.GetModel().SetVariables([])
  1271. class VariableListCtrl(ModelListCtrl):
  1272. def __init__(self, parent, columns, **kwargs):
  1273. """!List of model variables"""
  1274. ModelListCtrl.__init__(self, parent, columns, **kwargs)
  1275. self.SetColumnWidth(2, 200) # default value
  1276. def GetListCtrl(self):
  1277. """!Used by ColumnSorterMixin"""
  1278. return self
  1279. def GetData(self):
  1280. """!Get list data"""
  1281. return self.itemDataMap
  1282. def Populate(self, data):
  1283. """!Populate the list"""
  1284. self.itemDataMap = dict()
  1285. i = 0
  1286. for name, values in data.iteritems():
  1287. self.itemDataMap[i] = [name, values['type'],
  1288. values.get('value', ''),
  1289. values.get('description', '')]
  1290. i += 1
  1291. self.itemCount = len(self.itemDataMap.keys())
  1292. self.DeleteAllItems()
  1293. i = 0
  1294. for name, vtype, value, desc in self.itemDataMap.itervalues():
  1295. index = self.InsertStringItem(sys.maxint, name)
  1296. self.SetStringItem(index, 0, name)
  1297. self.SetStringItem(index, 1, vtype)
  1298. self.SetStringItem(index, 2, value)
  1299. self.SetStringItem(index, 3, desc)
  1300. self.SetItemData(index, i)
  1301. i += 1
  1302. def Append(self, name, vtype, value, desc):
  1303. """!Append new item to the list
  1304. @return None on success
  1305. @return error string
  1306. """
  1307. for iname, ivtype, ivalue, idesc in self.itemDataMap.itervalues():
  1308. if iname == name:
  1309. return _("Variable <%s> already exists in the model. "
  1310. "Adding variable failed.") % name
  1311. index = self.InsertStringItem(sys.maxint, name)
  1312. self.SetStringItem(index, 0, name)
  1313. self.SetStringItem(index, 1, vtype)
  1314. self.SetStringItem(index, 2, value)
  1315. self.SetStringItem(index, 3, desc)
  1316. self.SetItemData(index, self.itemCount)
  1317. self.itemDataMap[self.itemCount] = [name, vtype, value, desc]
  1318. self.itemCount += 1
  1319. return None
  1320. def OnRemove(self, event):
  1321. """!Remove selected variable(s) from the model"""
  1322. item = self.GetFirstSelected()
  1323. while item != -1:
  1324. self.DeleteItem(item)
  1325. del self.itemDataMap[item]
  1326. item = self.GetFirstSelected()
  1327. self.parent.UpdateModelVariables()
  1328. event.Skip()
  1329. def OnRemoveAll(self, event):
  1330. """!Remove all variable(s) from the model"""
  1331. dlg = wx.MessageBox(parent=self,
  1332. message=_("Do you want to delete all variables from "
  1333. "the model?"),
  1334. caption=_("Delete variables"),
  1335. style=wx.YES_NO | wx.CENTRE)
  1336. if dlg != wx.YES:
  1337. return
  1338. self.DeleteAllItems()
  1339. self.itemDataMap = dict()
  1340. self.parent.UpdateModelVariables()
  1341. def OnEndEdit(self, event):
  1342. """!Finish editing of item"""
  1343. itemIndex = event.GetIndex()
  1344. columnIndex = event.GetColumn()
  1345. nameOld = self.GetItem(itemIndex, 0).GetText()
  1346. if columnIndex == 0: # TODO
  1347. event.Veto()
  1348. self.itemDataMap[itemIndex][columnIndex] = event.GetText()
  1349. self.parent.UpdateModelVariables()
  1350. def OnReload(self, event):
  1351. """!Reload list of variables"""
  1352. self.Populate(self.parent.parent.GetModel().GetVariables())
  1353. def OnRightUp(self, event):
  1354. """!Mouse right button up"""
  1355. if not hasattr(self, "popupID1"):
  1356. self.popupID1 = wx.NewId()
  1357. self.popupID2 = wx.NewId()
  1358. self.popupID3 = wx.NewId()
  1359. self.Bind(wx.EVT_MENU, self.OnRemove, id = self.popupID1)
  1360. self.Bind(wx.EVT_MENU, self.OnRemoveAll, id = self.popupID2)
  1361. self.Bind(wx.EVT_MENU, self.OnReload, id = self.popupID3)
  1362. # generate popup-menu
  1363. menu = wx.Menu()
  1364. menu.Append(self.popupID1, _("Delete selected"))
  1365. menu.Append(self.popupID2, _("Delete all"))
  1366. if self.GetFirstSelected() == -1:
  1367. menu.Enable(self.popupID1, False)
  1368. menu.Enable(self.popupID2, False)
  1369. menu.AppendSeparator()
  1370. menu.Append(self.popupID3, _("Reload"))
  1371. self.PopupMenu(menu)
  1372. menu.Destroy()
  1373. class ItemPanel(wx.Panel):
  1374. def __init__(self, parent, id = wx.ID_ANY,
  1375. **kwargs):
  1376. """!Manage model items
  1377. """
  1378. self.parent = parent
  1379. wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
  1380. self.listBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
  1381. label=" %s " % _("List of items - right-click to delete"))
  1382. self.list = ItemListCtrl(parent = self,
  1383. columns = [_("ID"), _("Name"), _("In block"),
  1384. _("Command / Condition")])
  1385. self._layout()
  1386. def _layout(self):
  1387. """!Layout dialog"""
  1388. listSizer = wx.StaticBoxSizer(self.listBox, wx.VERTICAL)
  1389. listSizer.Add(item = self.list, proportion = 1,
  1390. flag = wx.EXPAND)
  1391. mainSizer = wx.BoxSizer(wx.VERTICAL)
  1392. mainSizer.Add(item = listSizer, proportion = 1,
  1393. flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
  1394. self.SetSizer(mainSizer)
  1395. mainSizer.Fit(self)
  1396. def Update(self):
  1397. """!Reload list of variables"""
  1398. self.list.OnReload(None)
  1399. class ItemListCtrl(ModelListCtrl):
  1400. def __init__(self, parent, columns, disablePopup = False, **kwargs):
  1401. """!List of model actions"""
  1402. self.disablePopup = disablePopup
  1403. ModelListCtrl.__init__(self, parent, columns, **kwargs)
  1404. self.SetColumnWidth(1, 100)
  1405. self.SetColumnWidth(2, 65)
  1406. def GetListCtrl(self):
  1407. """!Used by ColumnSorterMixin"""
  1408. return self
  1409. def GetData(self):
  1410. """!Get list data"""
  1411. return self.itemDataMap
  1412. def Populate(self, data):
  1413. """!Populate the list"""
  1414. self.itemDataMap = dict()
  1415. if self.shape:
  1416. if isinstance(self.shape, ModelCondition):
  1417. if self.GetName() == 'ElseBlockList':
  1418. shapeItems = map(lambda x: x.GetId(), self.shape.GetItems()['else'])
  1419. else:
  1420. shapeItems = map(lambda x: x.GetId(), self.shape.GetItems()['if'])
  1421. else:
  1422. shapeItems = map(lambda x: x.GetId(), self.shape.GetItems())
  1423. else:
  1424. shapeItems = list()
  1425. i = 0
  1426. if len(self.columns) == 3: # ItemCheckList
  1427. checked = list()
  1428. for action in data:
  1429. if isinstance(action, ModelData) or \
  1430. action == self.shape:
  1431. continue
  1432. if len(self.columns) == 3:
  1433. self.itemDataMap[i] = [str(action.GetId()),
  1434. action.GetName(),
  1435. action.GetLog()]
  1436. aId = action.GetBlockId()
  1437. if action.GetId() in shapeItems:
  1438. checked.append(aId)
  1439. else:
  1440. checked.append(None)
  1441. else:
  1442. bId = action.GetBlockId()
  1443. if not bId:
  1444. bId = ''
  1445. self.itemDataMap[i] = [str(action.GetId()),
  1446. action.GetName(),
  1447. ','.join(map(str, bId)),
  1448. action.GetLog()]
  1449. i += 1
  1450. self.itemCount = len(self.itemDataMap.keys())
  1451. self.DeleteAllItems()
  1452. i = 0
  1453. if len(self.columns) == 3:
  1454. for aid, name, desc in self.itemDataMap.itervalues():
  1455. index = self.InsertStringItem(sys.maxint, aid)
  1456. self.SetStringItem(index, 0, aid)
  1457. self.SetStringItem(index, 1, name)
  1458. self.SetStringItem(index, 2, desc)
  1459. self.SetItemData(index, i)
  1460. if checked[i]:
  1461. self.CheckItem(index, True)
  1462. i += 1
  1463. else:
  1464. for aid, name, inloop, desc in self.itemDataMap.itervalues():
  1465. index = self.InsertStringItem(sys.maxint, aid)
  1466. self.SetStringItem(index, 0, aid)
  1467. self.SetStringItem(index, 1, name)
  1468. self.SetStringItem(index, 2, inloop)
  1469. self.SetStringItem(index, 3, desc)
  1470. self.SetItemData(index, i)
  1471. i += 1
  1472. def OnRemove(self, event):
  1473. """!Remove selected action(s) from the model"""
  1474. model = self.frame.GetModel()
  1475. canvas = self.frame.GetCanvas()
  1476. item = self.GetFirstSelected()
  1477. while item != -1:
  1478. self.DeleteItem(item)
  1479. del self.itemDataMap[item]
  1480. aId = self.GetItem(item, 0).GetText()
  1481. action = model.GetItem(int(aId))
  1482. if not action:
  1483. item = self.GetFirstSelected()
  1484. continue
  1485. model.RemoveItem(action)
  1486. canvas.GetDiagram().RemoveShape(action)
  1487. self.frame.ModelChanged()
  1488. item = self.GetFirstSelected()
  1489. canvas.Refresh()
  1490. event.Skip()
  1491. def OnRemoveAll(self, event):
  1492. """!Remove all variable(s) from the model"""
  1493. deleteDialog = wx.MessageBox(parent=self,
  1494. message=_("Selected data records (%d) will permanently deleted "
  1495. "from table. Do you want to delete them?") % \
  1496. (len(self.listOfSQLStatements)),
  1497. caption=_("Delete records"),
  1498. style=wx.YES_NO | wx.CENTRE)
  1499. if deleteDialog != wx.YES:
  1500. return False
  1501. self.DeleteAllItems()
  1502. self.itemDataMap = dict()
  1503. self.parent.UpdateModelVariables()
  1504. def OnEndEdit(self, event):
  1505. """!Finish editing of item"""
  1506. itemIndex = event.GetIndex()
  1507. columnIndex = event.GetColumn()
  1508. self.itemDataMap[itemIndex][columnIndex] = event.GetText()
  1509. aId = int(self.GetItem(itemIndex, 0).GetText())
  1510. action = self.frame.GetModel().GetItem(aId)
  1511. if not action:
  1512. event.Veto()
  1513. if columnIndex == 0:
  1514. action.SetId(int(event.GetText()))
  1515. self.frame.ModelChanged()
  1516. def OnReload(self, event = None):
  1517. """!Reload list of actions"""
  1518. self.Populate(self.frame.GetModel().GetItems())
  1519. def OnRightUp(self, event):
  1520. """!Mouse right button up"""
  1521. if self.disablePopup:
  1522. return
  1523. if not hasattr(self, "popupID1"):
  1524. self.popupID1 = wx.NewId()
  1525. self.popupID2 = wx.NewId()
  1526. self.popupID3 = wx.NewId()
  1527. self.popupID4 = wx.NewId()
  1528. self.Bind(wx.EVT_MENU, self.OnRemove, id = self.popupID1)
  1529. self.Bind(wx.EVT_MENU, self.OnRemoveAll, id = self.popupID2)
  1530. self.Bind(wx.EVT_MENU, self.OnReload, id = self.popupID3)
  1531. self.Bind(wx.EVT_MENU, self.OnNormalize, id = self.popupID4)
  1532. # generate popup-menu
  1533. menu = wx.Menu()
  1534. menu.Append(self.popupID1, _("Delete selected"))
  1535. menu.Append(self.popupID2, _("Delete all"))
  1536. if self.GetFirstSelected() == -1:
  1537. menu.Enable(self.popupID1, False)
  1538. menu.Enable(self.popupID2, False)
  1539. menu.AppendSeparator()
  1540. menu.Append(self.popupID4, _("Normalize"))
  1541. menu.Append(self.popupID3, _("Reload"))
  1542. self.PopupMenu(menu)
  1543. menu.Destroy()
  1544. def OnNormalize(self, event):
  1545. """!Update id of actions"""
  1546. model = self.frame.GetModel()
  1547. aId = 1
  1548. for item in model.GetItems():
  1549. item.SetId(aId)
  1550. aId += 1
  1551. self.OnReload(None)
  1552. self.frame.GetCanvas().Refresh()
  1553. self.frame.ModelChanged()
  1554. class ItemCheckListCtrl(ItemListCtrl, listmix.CheckListCtrlMixin):
  1555. def __init__(self, parent, shape, columns, window = None, **kwargs):
  1556. self.parent = parent
  1557. self.window = window
  1558. ItemListCtrl.__init__(self, parent, columns, disablePopup = True, **kwargs)
  1559. listmix.CheckListCtrlMixin.__init__(self)
  1560. self.SetColumnWidth(0, 50)
  1561. self.shape = shape
  1562. def OnBeginEdit(self, event):
  1563. """!Disable editing"""
  1564. event.Veto()
  1565. def OnCheckItem(self, index, flag):
  1566. """!Item checked/unchecked"""
  1567. name = self.GetName()
  1568. if name == 'IfBlockList' and self.window:
  1569. self.window.OnCheckItemIf(index, flag)
  1570. elif name == 'ElseBlockList' and self.window:
  1571. self.window.OnCheckItemElse(index, flag)
  1572. def GetItems(self):
  1573. """!Get list of selected actions"""
  1574. ids = { 'checked' : list(),
  1575. 'unchecked' : list() }
  1576. for i in range(self.GetItemCount()):
  1577. iId = int(self.GetItem(i, 0).GetText())
  1578. if self.IsChecked(i):
  1579. ids['checked'].append(iId)
  1580. else:
  1581. ids['unchecked'].append(iId)
  1582. return ids
  1583. def CheckItemById(self, aId, flag):
  1584. """!Check/uncheck given item by id"""
  1585. for i in range(self.GetItemCount()):
  1586. iId = int(self.GetItem(i, 0).GetText())
  1587. if iId == aId:
  1588. self.CheckItem(i, flag)
  1589. break
  1590. def main():
  1591. import gettext
  1592. gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
  1593. app = wx.PySimpleApp()
  1594. wx.InitAllImageHandlers()
  1595. frame = ModelFrame(parent = None)
  1596. if len(sys.argv) > 1:
  1597. frame.LoadModelFile(sys.argv[1])
  1598. frame.Show()
  1599. app.MainLoop()
  1600. if __name__ == "__main__":
  1601. main()