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. import wx
  29. from wx.lib import ogl
  30. import wx.lib.flatnotebook as FN
  31. import wx.lib.mixins.listctrl as listmix
  32. if __name__ == "__main__":
  33. sys.path.append(os.path.join(os.getenv('GISBASE'), 'etc', 'gui', 'wxpython'))
  34. from core import globalvar
  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()