GRASS Programmer's Manual  6.4.2(2012)
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
gmodeler.py
Go to the documentation of this file.
1 """!
2 @package gmodeler.py
3 
4 @brief wxGUI Graphical Modeler for creating, editing, and managing models
5 
6 Classes:
7  - Model
8  - ModelFrame
9  - ModelCanvas
10  - ModelObject
11  - ModelAction
12  - ModelSearchDialog
13  - ModelData
14  - ModelDataDialog
15  - ModelRelation
16  - ModelRelationDialog
17  - ProcessModelFile
18  - WriteModelFile
19  - PreferencesDialog
20  - PropertiesDialog
21  - ModelParamDialog
22  - ModelListCtrl
23  - VariablePanel
24  - ValiableListCtrl
25  - ModelItem
26  - ModelItemDialog
27  - ModelLoop
28  - ModelLoopDialog
29  - ItemPanel
30  - ItemListCtrl
31  - ItemCheckListCtrl
32  - ModelCondition
33  - ModelConditionDialog
34  - WritePythonFile
35 
36 (C) 2010-2011 by the GRASS Development Team
37 This program is free software under the GNU General Public License
38 (>=v2). Read the file COPYING that comes with GRASS for details.
39 
40 @author Martin Landa <landa.martin gmail.com>
41 """
42 
43 import os
44 import sys
45 import time
46 import traceback
47 import getpass
48 import stat
49 import textwrap
50 import tempfile
51 import copy
52 import re
53 
54 try:
55  import xml.etree.ElementTree as etree
56 except ImportError:
57  import elementtree.ElementTree as etree # Python <= 2.4
58 
59 import globalvar
60 import wx
61 import wx.lib.ogl as ogl
62 import wx.lib.flatnotebook as FN
63 import wx.lib.colourselect as csel
64 import wx.lib.mixins.listctrl as listmix
65 
66 import menu
67 import menudata
68 import toolbars
69 import menuform
70 import prompt
71 import utils
72 import goutput
73 import gselect
74 from debug import Debug
75 from gcmd import GMessage, GException, GWarning, GError, RunCommand
76 from gdialogs import ElementDialog, GetImageHandlers
77 from preferences import PreferencesBaseDialog, globalSettings as UserSettings
78 from ghelp import SearchModuleWindow
79 
80 from grass.script import core as grass
81 from grass.script import task as gtask
82 
83 class Model(object):
84  """!Class representing the model"""
85  def __init__(self, canvas = None):
86  self.items = list() # list of actions/loops/...
87 
88  # model properties
89  self.properties = { 'name' : _("model"),
90  'description' : _("Script generated by wxGUI Graphical Modeler."),
91  'author' : getpass.getuser() }
92  # model variables
93  self.variables = dict()
94  self.variablesParams = dict()
95 
96  self.canvas = canvas
97 
98  def GetCanvas(self):
99  """!Get canvas or None"""
100  return self.canvas
101 
102  def GetItems(self, objType = None):
103  """!Get list of model items
104 
105  @param objType Object type to filter model objects
106  """
107  if not objType:
108  return self.items
109 
110  result = list()
111  for item in self.items:
112  if isinstance(item, objType):
113  result.append(item)
114 
115  return result
116 
117  def GetItem(self, aId):
118  """!Get item of given id
119 
120  @param aId item id
121 
122  @return Model* instance
123  @return None if no item found
124  """
125  ilist = self.GetItems()
126  for item in ilist:
127  if item.GetId() == aId:
128  return item
129 
130  return None
131 
132  def GetNumItems(self, actionOnly = False):
133  """!Get number of items"""
134  if actionOnly:
135  return len(self.GetItems(objType = ModelAction))
136 
137  return len(self.GetItems())
138 
139  def GetNextId(self):
140  """!Get next id (data ignored)
141 
142  @return next id to be used (default: 1)
143  """
144  if len(self.items) < 1:
145  return 1
146 
147  currId = self.items[-1].GetId()
148  if currId > 0:
149  return currId + 1
150 
151  return 1
152 
153  def GetProperties(self):
154  """!Get model properties"""
155  return self.properties
156 
157  def GetVariables(self, params = False):
158  """!Get model variables"""
159  if params:
160  return self.variablesParams
161 
162  return self.variables
163 
164  def SetVariables(self, data):
165  """!Set model variables"""
166  self.variables = data
167 
168  def Reset(self):
169  """!Reset model"""
170  self.items = list()
171 
172  def RemoveItem(self, item):
173  """!Remove item from model
174 
175  @return list of related items to remove/update
176  """
177  relList = list()
178  upList = list()
179 
180  if not isinstance(item, ModelData):
181  self.items.remove(item)
182 
183  if isinstance(item, ModelAction):
184  for rel in item.GetRelations():
185  relList.append(rel)
186  data = rel.GetData()
187  if len(data.GetRelations()) < 2:
188  relList.append(data)
189  else:
190  upList.append(data)
191 
192  elif isinstance(item, ModelData):
193  for rel in item.GetRelations():
194  relList.append(rel)
195  if rel.GetFrom() == self:
196  relList.append(rel.GetTo())
197  else:
198  relList.append(rel.GetFrom())
199 
200  elif isinstance(item, ModelLoop):
201  for rel in item.GetRelations():
202  relList.append(rel)
203  for action in self.GetItems():
204  action.UnSetBlock(item)
205 
206  return relList, upList
207 
208  def FindAction(self, aId):
209  """!Find action by id"""
210  alist = self.GetItems(objType = ModelAction)
211  for action in alist:
212  if action.GetId() == aId:
213  return action
214 
215  return None
216 
217  def GetData(self):
218  """!Get list of data items"""
219  result = list()
220  dataItems = self.GetItems(objType = ModelData)
221 
222  for action in self.GetItems(objType = ModelAction):
223  for rel in action.GetRelations():
224  dataItem = rel.GetData()
225  if dataItem not in result:
226  result.append(dataItem)
227  if dataItem in dataItems:
228  dataItems.remove(dataItem)
229 
230  # standalone data
231  if dataItems:
232  result += dataItems
233 
234  return result
235 
236  def FindData(self, value, prompt):
237  """!Find data item in the model
238 
239  @param value value
240  @param prompt prompt
241 
242  @return ModelData instance
243  @return None if not found
244  """
245  for data in self.GetData():
246  if data.GetValue() == value and \
247  data.GetPrompt() == prompt:
248  return data
249 
250  return None
251 
252  def LoadModel(self, filename):
253  """!Load model definition stored in GRASS Model XML file (gxm)
254 
255  @todo Validate against DTD
256 
257  Raise exception on error.
258  """
259  dtdFilename = os.path.join(globalvar.ETCWXDIR, "xml", "grass-gxm.dtd")
260 
261  # parse workspace file
262  try:
263  gxmXml = ProcessModelFile(etree.parse(filename))
264  except StandardError, e:
265  raise GException(e)
266 
267  if self.canvas:
268  win = self.canvas.parent
269  if gxmXml.pos:
270  win.SetPosition(gxmXml.pos)
271  if gxmXml.size:
272  win.SetSize(gxmXml.size)
273 
274  # load properties
275  self.properties = gxmXml.properties
276  self.variables = gxmXml.variables
277 
278  # load model.GetActions()
279  for action in gxmXml.actions:
280  actionItem = ModelAction(parent = self,
281  x = action['pos'][0],
282  y = action['pos'][1],
283  width = action['size'][0],
284  height = action['size'][1],
285  task = action['task'],
286  id = action['id'])
287 
288  if action['disabled']:
289  actionItem.Enable(False)
290 
291  self.AddItem(actionItem)
292 
293  task = actionItem.GetTask()
294  parameterized = False
295  valid = True
296  for f in task.get_options()['flags']:
297  if f.get('parameterized', False):
298  parameterized = True
299  break
300  for p in task.get_options()['params']:
301  if p.get('required', 'no') != 'no' and \
302  p.get('value', '') == '' and \
303  p.get('default', '') == '':
304  valid = False
305  if p.get('parameterized', False):
306  parameterized = True
307 
308  actionItem.SetValid(valid)
309  actionItem.SetParameterized(parameterized)
310  actionItem.GetLog() # substitute variables (-> valid/invalid)
311 
312  # load data & relations
313  for data in gxmXml.data:
314  dataItem = ModelData(parent = self,
315  x = data['pos'][0],
316  y = data['pos'][1],
317  width = data['size'][0],
318  height = data['size'][1],
319  prompt = data['prompt'],
320  value = data['value'])
321  dataItem.SetIntermediate(data['intermediate'])
322 
323  for rel in data['rels']:
324  actionItem = self.FindAction(rel['id'])
325  if rel['dir'] == 'from':
326  relation = ModelRelation(parent = self, fromShape = dataItem,
327  toShape = actionItem, param = rel['name'])
328  else:
329  relation = ModelRelation(parent = self, fromShape = actionItem,
330  toShape = dataItem, param = rel['name'])
331  relation.SetControlPoints(rel['points'])
332  actionItem.AddRelation(relation)
333  dataItem.AddRelation(relation)
334 
335  if self.canvas:
336  dataItem.Update()
337 
338  # load loops
339  for loop in gxmXml.loops:
340  loopItem = ModelLoop(parent = self,
341  x = loop['pos'][0],
342  y = loop['pos'][1],
343  width = loop['size'][0],
344  height = loop['size'][1],
345  text = loop['text'],
346  id = loop['id'])
347  self.AddItem(loopItem)
348 
349  # load conditions
350  for condition in gxmXml.conditions:
351  conditionItem = ModelCondition(parent = self,
352  x = condition['pos'][0],
353  y = condition['pos'][1],
354  width = condition['size'][0],
355  height = condition['size'][1],
356  text = condition['text'],
357  id = condition['id'])
358  self.AddItem(conditionItem)
359 
360  # define loops & if/else items
361  for loop in gxmXml.loops:
362  alist = list()
363  for aId in loop['items']:
364  action = self.GetItem(aId)
365  alist.append(action)
366 
367  loopItem = self.GetItem(loop['id'])
368  loopItem.SetItems(alist)
369 
370  for action in loopItem.GetItems():
371  action.SetBlock(loopItem)
372 
373  for condition in gxmXml.conditions:
374  conditionItem = self.GetItem(condition['id'])
375  for b in condition['items'].keys():
376  alist = list()
377  for aId in condition['items'][b]:
378  action = self.GetItem(aId)
379  alist.append(action)
380  conditionItem.SetItems(alist, branch = b)
381 
382  items = conditionItem.GetItems()
383  for b in items.keys():
384  for action in items[b]:
385  action.SetBlock(conditionItem)
386 
387  def AddItem(self, newItem):
388  """!Add item to the list"""
389  iId = newItem.GetId()
390 
391  i = 0
392  for item in self.items:
393  if item.GetId() > iId:
394  self.items.insert(i, newItem)
395  return
396  i += 1
397 
398  self.items.append(newItem)
399 
400  def IsValid(self):
401  """Return True if model is valid"""
402  if self.Validate():
403  return False
404 
405  return True
406 
407  def Validate(self):
408  """!Validate model, return None if model is valid otherwise
409  error string"""
410  errList = list()
411  for action in self.GetItems(objType = ModelAction):
412  task = menuform.GUI(show = None).ParseCommand(cmd = action.GetLog(string = False))
413  errList += task.getCmdError()
414 
415  return errList
416 
417  def RunAction(self, item, params, log, onDone, statusbar = None):
418  """!Run given action
419 
420  @param item action item
421  @param params parameters dict
422  @param log logging window
423  @param onDone on-done method
424  @param statusbar wx.StatusBar instance or None
425  """
426  name = item.GetName()
427  if name in params:
428  paramsOrig = item.GetParams(dcopy = True)
429  item.MergeParams(params[name])
430 
431  if statusbar:
432  statusbar.SetStatusText(_('Running model...'), 0)
433  log.RunCmd(command = item.GetLog(string = False),
434  onDone = onDone)
435 
436  if name in params:
437  item.SetParams(paramsOrig)
438 
439  def Run(self, log, onDone, parent = None):
440  """!Run model
441 
442  @param log logging window (see goutput.GMConsole)
443  @param onDone on-done method
444  @param parent window for messages or None
445  """
446  if self.GetNumItems() < 1:
447  GMessage(parent = parent,
448  message = _('Model is empty. Nothing to run.'))
449  return
450 
451  statusbar = None
452  if isinstance(parent, wx.Frame):
453  statusbar = parent.GetStatusBar()
454 
455  # validation
456  if statusbar:
457  statusbar.SetStatusText(_('Validating model...'), 0)
458  errList = self.Validate()
459  if statusbar:
460  statusbar.SetStatusText('', 0)
461  if errList:
462  dlg = wx.MessageDialog(parent = parent,
463  message = _('Model is not valid. Do you want to '
464  'run the model anyway?\n\n%s') % '\n'.join(errList),
465  caption = _("Run model?"),
466  style = wx.YES_NO | wx.NO_DEFAULT |
467  wx.ICON_QUESTION | wx.CENTRE)
468  ret = dlg.ShowModal()
469  if ret != wx.ID_YES:
470  return
471 
472  # parametrization
473  params = self.Parameterize()
474  if params:
475  dlg = ModelParamDialog(parent = parent,
476  params = params)
477  dlg.CenterOnParent()
478 
479  ret = dlg.ShowModal()
480  if ret != wx.ID_OK:
481  dlg.Destroy()
482  return
483 
484  err = dlg.GetErrors()
485  if err:
486  GError(parent = self, message = unicode('\n'.join(err)))
487  return
488 
489  log.cmdThread.SetId(-1)
490  for item in self.GetItems():
491  if not item.IsEnabled():
492  continue
493  if isinstance(item, ModelAction):
494  if item.GetBlockId():
495  continue
496  self.RunAction(item, params, log, onDone)
497  elif isinstance(item, ModelLoop):
498  cond = item.GetText()
499  # substitute variables in condition
500  variables = self.GetVariables()
501  for variable in variables:
502  pattern = re.compile('%' + variable)
503  if pattern.search(cond):
504  value = variables[variable].get('value', '')
505  vtype = variables[variable].get('type', 'string')
506  if vtype == 'string':
507  value = '"' + value + '"'
508  cond = pattern.sub(value, cond)
509  # split condition
510  condVar, condText = re.split('\s*in\s*', cond)
511 
512  for action in item.GetItems():
513  for vars()[condVar] in eval(condText):
514  if not isinstance(action, ModelAction) or \
515  not action.IsEnabled():
516  continue
517 
518  self.RunAction(action, params, log, onDone)
519 
520  if params:
521  dlg.Destroy()
522 
523  def DeleteIntermediateData(self, log):
524  """!Detele intermediate data"""
525  rast, vect, rast3d, msg = self.GetIntermediateData()
526 
527  if rast:
528  log.RunCmd(['g.remove', 'rast=%s' %','.join(rast)])
529  if rast3d:
530  log.RunCmd(['g.remove', 'rast3d=%s' %','.join(rast3d)])
531  if vect:
532  log.RunCmd(['g.remove', 'vect=%s' %','.join(vect)])
533 
535  """!Get info about intermediate data"""
536  rast = list()
537  rast3d = list()
538  vect = list()
539  for data in self.GetData():
540  if not data.IsIntermediate():
541  continue
542  name = data.GetValue()
543  prompt = data.GetPrompt()
544  if prompt == 'raster':
545  rast.append(name)
546  elif prompt == 'vector':
547  vect.append(name)
548  elif prompt == 'rast3d':
549  rast3d.append(name)
550 
551  msg = ''
552  if rast:
553  msg += '\n\n%s: ' % _('Raster maps')
554  msg += ', '.join(rast)
555  if rast3d:
556  msg += '\n\n%s: ' % _('3D raster maps')
557  msg += ', '.join(rast3d)
558  if vect:
559  msg += '\n\n%s: ' % _('Vector maps')
560  msg += ', '.join(vect)
561 
562  return rast, vect, rast3d, msg
563 
564  def Update(self):
565  """!Update model"""
566  for item in self.items:
567  item.Update()
568 
569  def IsParameterized(self):
570  """!Return True if model is parameterized"""
571  if self.Parameterize():
572  return True
573 
574  return False
575 
576  def Parameterize(self):
577  """!Return parameterized options"""
578  result = dict()
579  idx = 0
580  if self.variables:
581  params = list()
582  result[_("Variables")] = { 'flags' : list(),
583  'params' : params,
584  'idx' : idx }
585  for name, values in self.variables.iteritems():
586  gtype = values.get('type', 'string')
587  if gtype in ('raster', 'vector'):
588  gisprompt = True
589  prompt = gtype
590  if gtype == 'raster':
591  element = 'cell'
592  else:
593  element = 'vector'
594  ptype = 'string'
595  else:
596  gisprompt = False
597  prompt = None
598  element = None
599  ptype = gtype
600  params.append({ 'gisprompt' : gisprompt,
601  'multiple' : 'no',
602  'description' : values.get('description', ''),
603  'guidependency' : '',
604  'default' : '',
605  'age' : None,
606  'required' : 'yes',
607  'value' : values.get('value', ''),
608  'label' : '',
609  'guisection' : '',
610  'key_desc' : '',
611  'values' : list(),
612  'parameterized' : False,
613  'values_desc' : list(),
614  'prompt' : prompt,
615  'element' : element,
616  'type' : ptype,
617  'name' : name })
618 
619  idx += 1
620 
621  for action in self.GetItems(objType = ModelAction):
622  if not action.IsEnabled():
623  continue
624  name = action.GetName()
625  params = action.GetParams()
626  for f in params['flags']:
627  if f.get('parameterized', False):
628  if name not in result:
629  result[name] = { 'flags' : list(),
630  'params': list(),
631  'idx' : idx }
632  result[name]['flags'].append(f)
633  for p in params['params']:
634  if p.get('parameterized', False):
635  if name not in result:
636  result[name] = { 'flags' : list(),
637  'params': list(),
638  'idx' : idx }
639  result[name]['params'].append(p)
640  idx += 1
641 
642  self.variablesParams = result # record parameters
643 
644  return result
645 
646 class ModelFrame(wx.Frame):
647  def __init__(self, parent, id = wx.ID_ANY,
648  title = _("GRASS GIS Graphical Modeler (experimental prototype)"), **kwargs):
649  """!Graphical modeler main window
650 
651  @param parent parent window
652  @param id window id
653  @param title window title
654 
655  @param kwargs wx.Frames' arguments
656  """
657  self.parent = parent
658  self.searchDialog = None # module search dialog
659  self.baseTitle = title
660  self.modelFile = None # loaded model
661  self.modelChanged = False
662 
663  self.cursors = {
664  "default" : wx.StockCursor(wx.CURSOR_ARROW),
665  "cross" : wx.StockCursor(wx.CURSOR_CROSS),
666  }
667 
668  wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
669  self.SetName("Modeler")
670  self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
671 
672  self.menubar = menu.Menu(parent = self, data = menudata.ModelerData())
673 
674  self.SetMenuBar(self.menubar)
675 
676  self.toolbar = toolbars.ModelToolbar(parent = self)
677  self.SetToolBar(self.toolbar)
678 
679  self.statusbar = self.CreateStatusBar(number = 1)
680 
681  self.notebook = menuform.GNotebook(parent = self,
682  style = FN.FNB_FANCY_TABS | FN.FNB_BOTTOM |
683  FN.FNB_NO_NAV_BUTTONS | FN.FNB_NO_X_BUTTON)
684 
685  self.canvas = ModelCanvas(self)
686  self.canvas.SetBackgroundColour(wx.WHITE)
687  self.canvas.SetCursor(self.cursors["default"])
688 
689  self.model = Model(self.canvas)
690 
691  self.variablePanel = VariablePanel(parent = self)
692 
693  self.itemPanel = ItemPanel(parent = self)
694 
695  self.goutput = goutput.GMConsole(parent = self, notebook = self.notebook)
696 
697  self.notebook.AddPage(page = self.canvas, text=_('Model'), name = 'model')
698  self.notebook.AddPage(page = self.itemPanel, text=_('Items'), name = 'items')
699  self.notebook.AddPage(page = self.variablePanel, text=_('Variables'), name = 'variables')
700  self.notebook.AddPage(page = self.goutput, text=_('Command output'), name = 'output')
701  wx.CallAfter(self.notebook.SetSelectionByName, 'model')
702  wx.CallAfter(self.ModelChanged, False)
703 
704  self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
705  self.Bind(wx.EVT_SIZE, self.OnSize)
706 
707  self._layout()
708  self.SetMinSize((475, 300))
709  self.SetSize((640, 480))
710 
711  # fix goutput's pane size
712  if self.goutput:
713  self.goutput.SetSashPosition(int(self.GetSize()[1] * .75))
714 
715  def _layout(self):
716  """!Do layout"""
717  sizer = wx.BoxSizer(wx.VERTICAL)
718 
719  sizer.Add(item = self.notebook, proportion = 1,
720  flag = wx.EXPAND)
721 
722  self.SetAutoLayout(True)
723  self.SetSizer(sizer)
724  sizer.Fit(self)
725 
726  self.Layout()
727 
728  def _addEvent(self, item):
729  """!Add event to item"""
730  evthandler = ModelEvtHandler(self.statusbar,
731  self)
732  evthandler.SetShape(item)
733  evthandler.SetPreviousHandler(item.GetEventHandler())
734  item.SetEventHandler(evthandler)
735 
736  def GetCanvas(self):
737  """!Get canvas"""
738  return self.canvas
739 
740  def GetModel(self):
741  """!Get model"""
742  return self.model
743 
744  def ModelChanged(self, changed = True):
745  """!Update window title"""
746  self.modelChanged = changed
747 
748  if self.modelFile:
749  if self.modelChanged:
750  self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile) + '*')
751  else:
752  self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
753  else:
754  self.SetTitle(self.baseTitle)
755 
756  def OnVariables(self, event):
757  """!Switch to variables page"""
758  self.notebook.SetSelectionByName('variables')
759 
760  def OnRemoveItem(self, event):
761  """!Remove shape
762  """
763  self.GetCanvas().RemoveSelected()
764 
765  def OnCanvasRefresh(self, event):
766  """!Refresh canvas"""
767  self.SetStatusText(_("Redrawing model..."), 0)
768  self.GetCanvas().Refresh()
769  self.SetStatusText("", 0)
770 
771  def OnCmdRun(self, event):
772  """!Run command"""
773  try:
774  action = self.GetModel().GetItems()[event.pid]
775  if hasattr(action, "task"):
776  action.Update(running = True)
777  except IndexError:
778  pass
779 
780  def OnCmdDone(self, event):
781  """!Command done (or aborted)"""
782  try:
783  action = self.GetModel().GetItems()[event.pid]
784  if hasattr(action, "task"):
785  action.Update(running = True)
786  except IndexError:
787  pass
788 
789  def OnCloseWindow(self, event):
790  """!Close window"""
791  if self.modelChanged and \
792  UserSettings.Get(group='manager', key='askOnQuit', subkey='enabled'):
793  if self.modelFile:
794  message = _("Do you want to save changes in the model?")
795  else:
796  message = _("Do you want to store current model settings "
797  "to model file?")
798 
799  # ask user to save current settings
800  dlg = wx.MessageDialog(self,
801  message = message,
802  caption=_("Quit Graphical Modeler"),
803  style = wx.YES_NO | wx.YES_DEFAULT |
804  wx.CANCEL | wx.ICON_QUESTION | wx.CENTRE)
805  ret = dlg.ShowModal()
806  if ret == wx.ID_YES:
807  if not self.modelFile:
808  self.OnWorkspaceSaveAs()
809  else:
810  self.WriteModelFile(self.modelFile)
811  elif ret == wx.ID_CANCEL:
812  dlg.Destroy()
813  return
814  dlg.Destroy()
815 
816  self.Destroy()
817 
818  def OnSize(self, event):
819  """Window resized, save to the model"""
820  self.ModelChanged()
821  event.Skip()
822 
823  def OnPreferences(self, event):
824  """!Open preferences dialog"""
825  dlg = PreferencesDialog(parent = self)
826  dlg.CenterOnParent()
827 
828  dlg.ShowModal()
829  self.canvas.Refresh()
830 
831  def OnHelp(self, event):
832  """!Show help"""
833  if self.parent and self.parent.GetName() == 'LayerManager':
834  log = self.parent.GetLogWindow()
835  log.RunCmd(['g.manual',
836  'entry=wxGUI.Modeler'])
837  else:
838  RunCommand('g.manual',
839  quiet = True,
840  entry = 'wxGUI.Modeler')
841 
842  def OnModelProperties(self, event):
843  """!Model properties dialog"""
844  dlg = PropertiesDialog(parent = self)
845  dlg.CentreOnParent()
846  properties = self.model.GetProperties()
847  dlg.Init(properties)
848  if dlg.ShowModal() == wx.ID_OK:
849  self.ModelChanged()
850  for key, value in dlg.GetValues().iteritems():
851  properties[key] = value
852  for action in self.model.GetItems(objType = ModelAction):
853  action.GetTask().set_flag('overwrite', properties['overwrite'])
854 
855  dlg.Destroy()
856 
857  def OnDeleteData(self, event):
858  """!Delete intermediate data"""
859  rast, vect, rast3d, msg = self.model.GetIntermediateData()
860 
861  if not rast and not vect and not rast3d:
862  GMessage(parent = self,
863  message = _('Nothing to delete.'))
864  return
865 
866  dlg = wx.MessageDialog(parent = self,
867  message= _("Do you want to permanently delete data?%s" % msg),
868  caption=_("Delete intermediate data?"),
869  style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
870 
871  ret = dlg.ShowModal()
872  if ret == wx.ID_YES:
873  dlg.Destroy()
874 
875  if rast:
876  self.goutput.RunCmd(['g.remove', 'rast=%s' %','.join(rast)])
877  if rast3d:
878  self.goutput.RunCmd(['g.remove', 'rast3d=%s' %','.join(rast3d)])
879  if vect:
880  self.goutput.RunCmd(['g.remove', 'vect=%s' %','.join(vect)])
881 
882  self.SetStatusText(_("%d maps deleted from current mapset") % \
883  int(len(rast) + len(rast3d) + len(vect)))
884  return
885 
886  dlg.Destroy()
887 
888  def OnModelNew(self, event):
889  """!Create new model"""
890  Debug.msg(4, "ModelFrame.OnModelNew():")
891 
892  # ask user to save current model
893  if self.modelFile and self.modelChanged:
894  self.OnModelSave()
895  elif self.modelFile is None and \
896  (self.model.GetNumItems() > 0 or len(self.model.GetData()) > 0):
897  dlg = wx.MessageDialog(self, message=_("Current model is not empty. "
898  "Do you want to store current settings "
899  "to model file?"),
900  caption=_("Create new model?"),
901  style=wx.YES_NO | wx.YES_DEFAULT |
902  wx.CANCEL | wx.ICON_QUESTION)
903  ret = dlg.ShowModal()
904  if ret == wx.ID_YES:
905  self.OnModelSaveAs()
906  elif ret == wx.ID_CANCEL:
907  dlg.Destroy()
908  return
909 
910  dlg.Destroy()
911 
912  # delete all items
913  self.canvas.GetDiagram().DeleteAllShapes()
914  self.model.Reset()
915  self.canvas.Refresh()
916  self.itemPanel.Update()
917  self.variablePanel.Reset()
918 
919  # no model file loaded
920  self.modelFile = None
921  self.modelChanged = False
922  self.SetTitle(self.baseTitle)
923 
924  def OnModelOpen(self, event):
925  """!Load model from file"""
926  filename = ''
927  dlg = wx.FileDialog(parent = self, message=_("Choose model file"),
928  defaultDir = os.getcwd(),
929  wildcard=_("GRASS Model File (*.gxm)|*.gxm"))
930  if dlg.ShowModal() == wx.ID_OK:
931  filename = dlg.GetPath()
932 
933  if not filename:
934  return
935 
936  Debug.msg(4, "ModelFrame.OnModelOpen(): filename=%s" % filename)
937 
938  # close current model
939  self.OnModelClose()
940 
941  self.LoadModelFile(filename)
942 
943  self.modelFile = filename
944  self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
945  self.SetStatusText(_('%(items)d items (%(actions)d actions) loaded into model') % \
946  { 'items' : self.model.GetNumItems(),
947  'actions' : self.model.GetNumItems(actionOnly = True) }, 0)
948 
949  def OnModelSave(self, event = None):
950  """!Save model to file"""
951  if self.modelFile and self.modelChanged:
952  dlg = wx.MessageDialog(self, message=_("Model file <%s> already exists. "
953  "Do you want to overwrite this file?") % \
954  self.modelFile,
955  caption=_("Save model"),
956  style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
957  if dlg.ShowModal() == wx.ID_NO:
958  dlg.Destroy()
959  else:
960  Debug.msg(4, "ModelFrame.OnModelSave(): filename=%s" % self.modelFile)
961  self.WriteModelFile(self.modelFile)
962  self.SetStatusText(_('File <%s> saved') % self.modelFile, 0)
963  self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
964  elif not self.modelFile:
965  self.OnModelSaveAs(None)
966 
967  def OnModelSaveAs(self, event):
968  """!Create model to file as"""
969  filename = ''
970  dlg = wx.FileDialog(parent = self,
971  message = _("Choose file to save current model"),
972  defaultDir = os.getcwd(),
973  wildcard=_("GRASS Model File (*.gxm)|*.gxm"),
974  style=wx.FD_SAVE)
975 
976 
977  if dlg.ShowModal() == wx.ID_OK:
978  filename = dlg.GetPath()
979 
980  if not filename:
981  return
982 
983  # check for extension
984  if filename[-4:] != ".gxm":
985  filename += ".gxm"
986 
987  if os.path.exists(filename):
988  dlg = wx.MessageDialog(parent = self,
989  message=_("Model file <%s> already exists. "
990  "Do you want to overwrite this file?") % filename,
991  caption=_("File already exists"),
992  style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
993  if dlg.ShowModal() != wx.ID_YES:
994  dlg.Destroy()
995  return
996 
997  Debug.msg(4, "GMFrame.OnModelSaveAs(): filename=%s" % filename)
998 
999  self.WriteModelFile(filename)
1000  self.modelFile = filename
1001  self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
1002  self.SetStatusText(_('File <%s> saved') % self.modelFile, 0)
1003 
1004  def OnModelClose(self, event = None):
1005  """!Close model file"""
1006  Debug.msg(4, "ModelFrame.OnModelClose(): file=%s" % self.modelFile)
1007  # ask user to save current model
1008  if self.modelFile and self.modelChanged:
1009  self.OnModelSave()
1010  elif self.modelFile is None and \
1011  (self.model.GetNumItems() > 0 or len(self.model.GetData()) > 0):
1012  dlg = wx.MessageDialog(self, message=_("Current model is not empty. "
1013  "Do you want to store current settings "
1014  "to model file?"),
1015  caption=_("Create new model?"),
1016  style=wx.YES_NO | wx.YES_DEFAULT |
1017  wx.CANCEL | wx.ICON_QUESTION)
1018  ret = dlg.ShowModal()
1019  if ret == wx.ID_YES:
1020  self.OnModelSaveAs()
1021  elif ret == wx.ID_CANCEL:
1022  dlg.Destroy()
1023  return
1024 
1025  dlg.Destroy()
1026 
1027  self.modelFile = None
1028  self.SetTitle(self.baseTitle)
1029 
1030  self.canvas.GetDiagram().DeleteAllShapes()
1031  self.model.Reset()
1032 
1033  self.canvas.Refresh()
1034 
1035  def OnRunModel(self, event):
1036  """!Run entire model"""
1037  self.model.Run(self.goutput, self.OnDone, parent = self)
1038 
1039  def OnDone(self, cmd, returncode):
1040  """!Computation finished"""
1041  self.SetStatusText('', 0)
1042 
1043  def OnValidateModel(self, event, showMsg = True):
1044  """!Validate entire model"""
1045  if self.model.GetNumItems() < 1:
1046  GMessage(parent = self,
1047  message = _('Model is empty. Nothing to validate.'))
1048  return
1049 
1050 
1051  self.SetStatusText(_('Validating model...'), 0)
1052  errList = self.model.Validate()
1053  self.SetStatusText('', 0)
1054 
1055  if errList:
1056  GWarning(parent = self,
1057  message = _('Model is not valid.\n\n%s') % '\n'.join(errList))
1058  else:
1059  GMessage(parent = self,
1060  message = _('Model is valid.'))
1061 
1062  def OnExportImage(self, event):
1063  """!Export model to image (default image)
1064  """
1065  xminImg = 0
1066  xmaxImg = 0
1067  yminImg = 0
1068  ymaxImg = 0
1069  # get current size of canvas
1070  for shape in self.canvas.GetDiagram().GetShapeList():
1071  w, h = shape.GetBoundingBoxMax()
1072  x = shape.GetX()
1073  y = shape.GetY()
1074  xmin = x - w / 2
1075  xmax = x + w / 2
1076  ymin = y - h / 2
1077  ymax = y + h / 2
1078  if xmin < xminImg:
1079  xminImg = xmin
1080  if xmax > xmaxImg:
1081  xmaxImg = xmax
1082  if ymin < yminImg:
1083  yminImg = ymin
1084  if ymax > ymaxImg:
1085  ymaxImg = ymax
1086  size = wx.Size(int(xmaxImg - xminImg) + 50,
1087  int(ymaxImg - yminImg) + 50)
1088  bitmap = wx.EmptyBitmap(width = size.width, height = size.height)
1089 
1090  filetype, ltype = GetImageHandlers(wx.ImageFromBitmap(bitmap))
1091 
1092  dlg = wx.FileDialog(parent = self,
1093  message = _("Choose a file name to save the image (no need to add extension)"),
1094  defaultDir = "",
1095  defaultFile = "",
1096  wildcard = filetype,
1097  style=wx.SAVE | wx.FD_OVERWRITE_PROMPT)
1098 
1099  if dlg.ShowModal() == wx.ID_OK:
1100  path = dlg.GetPath()
1101  if not path:
1102  dlg.Destroy()
1103  return
1104 
1105  base, ext = os.path.splitext(path)
1106  fileType = ltype[dlg.GetFilterIndex()]['type']
1107  extType = ltype[dlg.GetFilterIndex()]['ext']
1108  if ext != extType:
1109  path = base + '.' + extType
1110 
1111  dc = wx.MemoryDC(bitmap)
1112  dc.SetBackground(wx.WHITE_BRUSH)
1113  dc.SetBackgroundMode(wx.SOLID)
1114 
1115  dc.BeginDrawing()
1116  self.canvas.GetDiagram().Clear(dc)
1117  self.canvas.GetDiagram().Redraw(dc)
1118  dc.EndDrawing()
1119 
1120  bitmap.SaveFile(path, fileType)
1121  self.SetStatusText(_("Model exported to <%s>") % path)
1122 
1123  dlg.Destroy()
1124 
1125  def OnExportPython(self, event):
1126  """!Export model to Python script"""
1127  filename = ''
1128  dlg = wx.FileDialog(parent = self,
1129  message = _("Choose file to save"),
1130  defaultDir = os.getcwd(),
1131  wildcard=_("Python script (*.py)|*.py"),
1132  style=wx.FD_SAVE)
1133 
1134  if dlg.ShowModal() == wx.ID_OK:
1135  filename = dlg.GetPath()
1136 
1137  if not filename:
1138  return
1139 
1140  # check for extension
1141  if filename[-3:] != ".py":
1142  filename += ".py"
1143 
1144  if os.path.exists(filename):
1145  dlg = wx.MessageDialog(self, message=_("File <%s> already exists. "
1146  "Do you want to overwrite this file?") % filename,
1147  caption=_("Save file"),
1148  style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
1149  if dlg.ShowModal() == wx.ID_NO:
1150  dlg.Destroy()
1151  return
1152 
1153  dlg.Destroy()
1154 
1155  fd = open(filename, "w")
1156  try:
1157  WritePythonFile(fd, self.model)
1158  finally:
1159  fd.close()
1160 
1161  # executable file
1162  os.chmod(filename, stat.S_IRWXU | stat.S_IWUSR)
1163 
1164  self.SetStatusText(_("Model exported to <%s>") % filename)
1165 
1166  def OnDefineRelation(self, event):
1167  """!Define relation between data and action items"""
1168  self.canvas.SetCursor(self.cursors["cross"])
1169  self.defineRelation = { 'from' : None,
1170  'to' : None }
1171 
1172  def OnDefineLoop(self, event):
1173  """!Define new loop in the model"""
1174  self.ModelChanged()
1175 
1176  width, height = self.canvas.GetSize()
1177  loop = ModelLoop(self, x = width/2, y = height/2,
1178  id = self.model.GetNumItems() + 1)
1179  self.canvas.diagram.AddShape(loop)
1180  loop.Show(True)
1181 
1182  self._addEvent(loop)
1183  self.model.AddItem(loop)
1184 
1185  self.canvas.Refresh()
1186 
1187  def OnDefineCondition(self, event):
1188  """!Define new condition in the model"""
1189  self.ModelChanged()
1190 
1191  width, height = self.canvas.GetSize()
1192  cond = ModelCondition(self, x = width/2, y = height/2,
1193  id = self.model.GetNumItems() + 1)
1194  self.canvas.diagram.AddShape(cond)
1195  cond.Show(True)
1196 
1197  self._addEvent(cond)
1198  self.model.AddItem(cond)
1199 
1200  self.canvas.Refresh()
1201 
1202  def OnAddAction(self, event):
1203  """!Add action to model"""
1204  if self.searchDialog is None:
1205  self.searchDialog = ModelSearchDialog(self)
1206  self.searchDialog.CentreOnParent()
1207  else:
1208  self.searchDialog.Reset()
1209 
1210  if self.searchDialog.ShowModal() == wx.ID_CANCEL:
1211  self.searchDialog.Hide()
1212  return
1213 
1214  cmd = self.searchDialog.GetCmd()
1215  self.searchDialog.Hide()
1216 
1217  self.ModelChanged()
1218 
1219  # add action to canvas
1220  width, height = self.canvas.GetSize()
1221  if cmd[0] == 'r.mapcalc':
1222  GMessage(parent = self,
1223  message = _("Module r.mapcalc cannot be used in the model. "
1224  "Use r.mapcalculator instead."))
1225  return
1226 
1227  action = ModelAction(self.model, cmd = cmd, x = width/2, y = height/2,
1228  id = self.model.GetNextId())
1229  overwrite = self.model.GetProperties().get('overwrite', None)
1230  if overwrite is not None:
1231  action.GetTask().set_flag('overwrite', overwrite)
1232 
1233  self.canvas.diagram.AddShape(action)
1234  action.Show(True)
1235 
1236  self._addEvent(action)
1237  self.model.AddItem(action)
1238 
1239  self.itemPanel.Update()
1240  self.canvas.Refresh()
1241  time.sleep(.1)
1242 
1243  # show properties dialog
1244  win = action.GetPropDialog()
1245  if not win:
1246  if len(action.GetLog(string = False)) > 1:
1247  self.GetOptData(dcmd = action.GetLog(string = False), layer = action,
1248  params = action.GetParams(), propwin = None)
1249  else:
1250  menuform.GUI(parent = self, show = True).ParseCommand(action.GetLog(string = False),
1251  completed = (self.GetOptData, action, action.GetParams()))
1252  elif win and not win.IsShown():
1253  win.Show()
1254 
1255  if win:
1256  win.Raise()
1257 
1258  def OnAddData(self, event):
1259  """!Add data item to model
1260  """
1261  # add action to canvas
1262  width, height = self.canvas.GetSize()
1263  data = ModelData(self, x = width/2, y = height/2)
1264 
1265  dlg = ModelDataDialog(parent = self, shape = data)
1266  data.SetPropDialog(dlg)
1267  dlg.CentreOnParent()
1268  ret = dlg.ShowModal()
1269  dlg.Destroy()
1270  if ret != wx.ID_OK:
1271  return
1272 
1273  data.Update()
1274  self.canvas.diagram.AddShape(data)
1275  data.Show(True)
1276 
1277  self.ModelChanged()
1278 
1279  self._addEvent(data)
1280  self.model.AddItem(data)
1281 
1282  self.canvas.Refresh()
1283 
1284 
1285  def OnHelp(self, event):
1286  """!Display manual page"""
1287  grass.run_command('g.manual',
1288  entry = 'wxGUI.Modeler')
1289 
1290  def OnAbout(self, event):
1291  """!Display About window"""
1292  info = wx.AboutDialogInfo()
1293 
1294  info.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
1295  info.SetName(_('wxGUI Graphical Modeler'))
1296  info.SetWebSite('http://grass.osgeo.org')
1297  year = grass.version()['date']
1298  info.SetDescription(_('(C) 2010-%s by the GRASS Development Team\n\n') % year +
1299  '\n'.join(textwrap.wrap(_('This program is free software under the GNU General Public License'
1300  '(>=v2). Read the file COPYING that comes with GRASS for details.'), 75)))
1301 
1302  wx.AboutBox(info)
1303 
1304  def GetOptData(self, dcmd, layer, params, propwin):
1305  """!Process action data"""
1306  if params: # add data items
1307  width, height = self.canvas.GetSize()
1308  x = [width/2 + 200, width/2 - 200]
1309  for p in params['params']:
1310  if p.get('prompt', '') in ('raster', 'vector', 'raster3d') and \
1311  (p.get('value', None) or \
1312  (p.get('age', 'old') != 'old' and p.get('required', 'no') == 'yes')):
1313  data = layer.FindData(p.get('name', ''))
1314  if data:
1315  data.SetValue(p.get('value', ''))
1316  data.Update()
1317  continue
1318 
1319  data = self.model.FindData(p.get('value', ''),
1320  p.get('prompt', ''))
1321  if data:
1322  if p.get('age', 'old') == 'old':
1323  rel = ModelRelation(parent = self, fromShape = data,
1324  toShape = layer, param = p.get('name', ''))
1325  else:
1326  rel = ModelRelation(parent = self, fromShape = layer,
1327  toShape = data, param = p.get('name', ''))
1328  layer.AddRelation(rel)
1329  data.AddRelation(rel)
1330  self.AddLine(rel)
1331  data.Update()
1332  continue
1333 
1334  data = ModelData(self, value = p.get('value', ''),
1335  prompt = p.get('prompt', ''),
1336  x = x.pop(), y = height/2)
1337  self._addEvent(data)
1338  self.canvas.diagram.AddShape(data)
1339  data.Show(True)
1340 
1341  if p.get('age', 'old') == 'old':
1342  rel = ModelRelation(parent = self, fromShape = data,
1343  toShape = layer, param = p.get('name', ''))
1344  else:
1345  rel = ModelRelation(parent = self, fromShape = layer,
1346  toShape = data, param = p.get('name', ''))
1347  layer.AddRelation(rel)
1348  data.AddRelation(rel)
1349  self.AddLine(rel)
1350  data.Update()
1351 
1352  # valid ?
1353  valid = True
1354  for p in params['params']:
1355  if p.get('required', False) and \
1356  p.get('value', '') == '' and \
1357  p.get('default', '') == '':
1358  valid = False
1359  break
1360  layer.SetValid(valid)
1361 
1362  # parameterized ?
1363  parameterized = False
1364  for f in params['flags']:
1365  if f.get('parameterized', False):
1366  parameterized = True
1367  break
1368  if not parameterized:
1369  for p in params['params']:
1370  if p.get('parameterized', False):
1371  parameterized = True
1372  break
1373  layer.SetParameterized(parameterized)
1374 
1375  self.canvas.Refresh()
1376 
1377  if dcmd:
1378  layer.SetProperties(params, propwin)
1379 
1380  self.SetStatusText(layer.GetLog(), 0)
1381 
1382  def AddLine(self, rel):
1383  """!Add connection between model objects
1384 
1385  @param rel relation
1386  """
1387  fromShape = rel.GetFrom()
1388  toShape = rel.GetTo()
1389 
1390  rel.SetCanvas(self)
1391  rel.SetPen(wx.BLACK_PEN)
1392  rel.SetBrush(wx.BLACK_BRUSH)
1393  rel.AddArrow(ogl.ARROW_ARROW)
1394  points = rel.GetControlPoints()
1395  rel.MakeLineControlPoints(2)
1396  if points:
1397  for x, y in points:
1398  rel.InsertLineControlPoint(point = wx.RealPoint(x, y))
1399 
1400  self._addEvent(rel)
1401  try:
1402  fromShape.AddLine(rel, toShape)
1403  except TypeError:
1404  pass # bug when connecting ModelCondition and ModelLoop - to be fixed
1405 
1406  self.canvas.diagram.AddShape(rel)
1407  rel.Show(True)
1408 
1409  def LoadModelFile(self, filename):
1410  """!Load model definition stored in GRASS Model XML file (gxm)
1411  """
1412  try:
1413  self.model.LoadModel(filename)
1414  except GException, e:
1415  GError(parent = self,
1416  message = _("Reading model file <%s> failed.\n"
1417  "Invalid file, unable to parse XML document.") % filename)
1418 
1419  self.modelFile = filename
1420  self.SetTitle(self.baseTitle + " - " + os.path.basename(self.modelFile))
1421 
1422  self.SetStatusText(_("Please wait, loading model..."), 0)
1423 
1424  # load actions
1425  for item in self.model.GetItems(objType = ModelAction):
1426  self._addEvent(item)
1427  self.canvas.diagram.AddShape(item)
1428  item.Show(True)
1429  # relations/data
1430  for rel in item.GetRelations():
1431  if rel.GetFrom() == item:
1432  dataItem = rel.GetTo()
1433  else:
1434  dataItem = rel.GetFrom()
1435  self._addEvent(dataItem)
1436  self.canvas.diagram.AddShape(dataItem)
1437  self.AddLine(rel)
1438  dataItem.Show(True)
1439 
1440  # load loops
1441  for item in self.model.GetItems(objType = ModelLoop):
1442  self._addEvent(item)
1443  self.canvas.diagram.AddShape(item)
1444  item.Show(True)
1445 
1446  # connect items in the loop
1447  self.DefineLoop(item)
1448 
1449  # load conditions
1450  for item in self.model.GetItems(objType = ModelCondition):
1451  self._addEvent(item)
1452  self.canvas.diagram.AddShape(item)
1453  item.Show(True)
1454 
1455  # connect items in the condition
1456  self.DefineCondition(item)
1457 
1458  # load variables
1459  self.variablePanel.Update()
1460  self.itemPanel.Update()
1461  self.SetStatusText('', 0)
1462 
1463  self.canvas.Refresh(True)
1464 
1465  def WriteModelFile(self, filename):
1466  """!Save model to model file, recover original file on error.
1467 
1468  @return True on success
1469  @return False on failure
1470  """
1471  self.ModelChanged(False)
1472  tmpfile = tempfile.TemporaryFile(mode='w+b')
1473  try:
1474  WriteModelFile(fd = tmpfile, model = self.model)
1475  except StandardError:
1476  GError(parent = self,
1477  message = _("Writing current settings to model file failed."))
1478  return False
1479 
1480  try:
1481  mfile = open(filename, "w")
1482  tmpfile.seek(0)
1483  for line in tmpfile.readlines():
1484  mfile.write(line)
1485  except IOError:
1486  wx.MessageBox(parent = self,
1487  message = _("Unable to open file <%s> for writing.") % filename,
1488  caption = _("Error"),
1489  style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
1490  return False
1491 
1492  mfile.close()
1493 
1494  return True
1495 
1496  def DefineLoop(self, loop):
1497  """!Define loop with given list of items"""
1498  parent = loop
1499  items = loop.GetItems()
1500  if not items:
1501  return
1502 
1503  # remove defined relations first
1504  for rel in loop.GetRelations():
1505  self.canvas.GetDiagram().RemoveShape(rel)
1506  loop.Clear()
1507 
1508  for item in items:
1509  rel = ModelRelation(parent = self, fromShape = parent, toShape = item)
1510  dx = item.GetX() - parent.GetX()
1511  dy = item.GetY() - parent.GetY()
1512  loop.AddRelation(rel)
1513  if dx != 0:
1514  rel.SetControlPoints(((parent.GetX(), parent.GetY() + dy / 2),
1515  (parent.GetX() + dx, parent.GetY() + dy / 2)))
1516  self.AddLine(rel)
1517  parent = item
1518 
1519  # close loop
1520  item = loop.GetItems()[-1]
1521  rel = ModelRelation(parent = self, fromShape = item, toShape = loop)
1522  loop.AddRelation(rel)
1523  self.AddLine(rel)
1524  dx = (item.GetX() - loop.GetX()) + loop.GetWidth() / 2 + 50
1525  dy = item.GetHeight() / 2 + 50
1526  rel.MakeLineControlPoints(0)
1527  rel.InsertLineControlPoint(point = wx.RealPoint(loop.GetX() - loop.GetWidth() / 2 ,
1528  loop.GetY()))
1529  rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX(),
1530  item.GetY() + item.GetHeight() / 2))
1531  rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX(),
1532  item.GetY() + dy))
1533  rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - dx,
1534  item.GetY() + dy))
1535  rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - dx,
1536  loop.GetY()))
1537 
1538  self.canvas.Refresh()
1539 
1540  def DefineCondition(self, condition):
1541  """!Define if-else statement with given list of items"""
1542  parent = condition
1543  items = condition.GetItems()
1544  if not items['if'] and not items['else']:
1545  return
1546 
1547  # remove defined relations first
1548  for rel in condition.GetRelations():
1549  self.canvas.GetDiagram().RemoveShape(rel)
1550  condition.Clear()
1551  dxIf = condition.GetX() + condition.GetWidth() / 2
1552  dxElse = condition.GetX() - condition.GetWidth() / 2
1553  dy = condition.GetY()
1554  for branch in items.keys():
1555  for item in items[branch]:
1556  rel = ModelRelation(parent = self, fromShape = parent,
1557  toShape = item)
1558  condition.AddRelation(rel)
1559  self.AddLine(rel)
1560  rel.MakeLineControlPoints(0)
1561  if branch == 'if':
1562  rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - item.GetWidth() / 2, item.GetY()))
1563  rel.InsertLineControlPoint(point = wx.RealPoint(dxIf, dy))
1564  else:
1565  rel.InsertLineControlPoint(point = wx.RealPoint(dxElse, dy))
1566  rel.InsertLineControlPoint(point = wx.RealPoint(item.GetX() - item.GetWidth() / 2, item.GetY()))
1567  parent = item
1568 
1569  self.canvas.Refresh()
1570 
1571 class ModelCanvas(ogl.ShapeCanvas):
1572  """!Canvas where model is drawn"""
1573  def __init__(self, parent):
1574  self.parent = parent
1575  ogl.OGLInitialize()
1576  ogl.ShapeCanvas.__init__(self, parent)
1577 
1578  self.diagram = ogl.Diagram()
1579  self.SetDiagram(self.diagram)
1580  self.diagram.SetCanvas(self)
1581 
1582  self.SetScrollbars(20, 20, 1000/20, 1000/20)
1583 
1584  self.Bind(wx.EVT_CHAR, self.OnChar)
1585 
1586  def OnChar(self, event):
1587  """!Key pressed"""
1588  kc = event.GetKeyCode()
1589  diagram = self.GetDiagram()
1590  if kc == wx.WXK_DELETE:
1591  self.RemoveSelected()
1592 
1593  def RemoveSelected(self):
1594  """!Remove selected shapes"""
1595  self.parent.ModelChanged()
1596 
1597  diagram = self.GetDiagram()
1598  for shape in diagram.GetShapeList():
1599  if not shape.Selected():
1600  continue
1601  remList, upList = self.parent.GetModel().RemoveItem(shape)
1602  shape.Select(False)
1603  diagram.RemoveShape(shape)
1604  shape.__del__()
1605  for item in remList:
1606  diagram.RemoveShape(item)
1607  item.__del__()
1608 
1609  for item in upList:
1610  item.Update()
1611 
1612  self.Refresh()
1613 
1615  def __init__(self, id = -1):
1616  self.id = id
1617  self.rels = list() # list of ModelRelations
1618 
1619  self.isEnabled = True
1620  self.inBlock = list() # list of related loops/conditions
1621 
1622  def __del__(self):
1623  pass
1624 
1625  def GetId(self):
1626  """!Get id"""
1627  return self.id
1628 
1629  def AddRelation(self, rel):
1630  """!Record new relation
1631  """
1632  self.rels.append(rel)
1633 
1634  def GetRelations(self, fdir = None):
1635  """!Get list of relations
1636 
1637  @param fdir True for 'from'
1638  """
1639  if fdir is None:
1640  return self.rels
1641 
1642  result = list()
1643  for rel in self.rels:
1644  if fdir == 'from':
1645  if rel.GetFrom() == self:
1646  result.append(rel)
1647  else:
1648  if rel.GetTo() == self:
1649  result.append(rel)
1650 
1651  return result
1652 
1653  def IsEnabled(self):
1654  """!Get True if action is enabled, otherwise False"""
1655  return self.isEnabled
1656 
1657  def Enable(self, enabled = True):
1658  """!Enable/disable action"""
1659  self.isEnabled = enabled
1660  self.Update()
1661 
1662  def Update(self):
1663  pass
1664 
1665  def SetBlock(self, item):
1666  """!Add object to the block (loop/condition)
1667 
1668  @param item reference to ModelLoop or ModelCondition which
1669  defines loops/condition
1670  """
1671  if item not in self.inBlock:
1672  self.inBlock.append(item)
1673 
1674  def UnSetBlock(self, item):
1675  """!Remove object from the block (loop/consition)
1676 
1677  @param item reference to ModelLoop or ModelCondition which
1678  defines loops/codition
1679  """
1680  if item in self.inBlock:
1681  self.inBlock.remove(item)
1682 
1683  def GetBlock(self):
1684  """!Get list of related ModelObject(s) which defines block
1685  (loop/condition)
1686 
1687  @return list of ModelObjects
1688  """
1689  return self.inBlock
1690 
1691  def GetBlockId(self):
1692  """!Get list of related ids which defines block
1693 
1694  @return list of ids
1695  """
1696  ret = list()
1697  for mo in self.inBlock:
1698  ret.append(mo.GetId())
1699 
1700  return ret
1701 
1702 class ModelAction(ModelObject, ogl.RectangleShape):
1703  """!Action class (GRASS module)"""
1704  def __init__(self, parent, x, y, id = -1, cmd = None, task = None, width = None, height = None):
1705  ModelObject.__init__(self, id)
1706 
1707  self.parent = parent
1708  self.task = task
1709 
1710  if not width:
1711  width = UserSettings.Get(group='modeler', key='action', subkey=('size', 'width'))
1712  if not height:
1713  height = UserSettings.Get(group='modeler', key='action', subkey=('size', 'height'))
1714 
1715  if cmd:
1716  self.task = menuform.GUI(show = None).ParseCommand(cmd = cmd)
1717  else:
1718  if task:
1719  self.task = task
1720  else:
1721  self.task = None
1722 
1723  self.propWin = None
1724 
1725  self.data = list() # list of connected data items
1726 
1727  self.isValid = False
1728  self.isParameterized = False
1729 
1730  if self.parent.GetCanvas():
1731  ogl.RectangleShape.__init__(self, width, height)
1732 
1733  self.SetCanvas(self.parent)
1734  self.SetX(x)
1735  self.SetY(y)
1736  self.SetPen(wx.BLACK_PEN)
1737  self._setPen()
1738  self._setBrush()
1739  self.SetId(id)
1740 
1741  def _setBrush(self, running = False):
1742  """!Set brush"""
1743  if running:
1744  color = UserSettings.Get(group='modeler', key='action',
1745  subkey=('color', 'running'))
1746  elif not self.isEnabled:
1747  color = UserSettings.Get(group='modeler', key='disabled',
1748  subkey='color')
1749  elif self.isValid:
1750  color = UserSettings.Get(group='modeler', key='action',
1751  subkey=('color', 'valid'))
1752  else:
1753  color = UserSettings.Get(group='modeler', key='action',
1754  subkey=('color', 'invalid'))
1755 
1756  wxColor = wx.Color(color[0], color[1], color[2])
1757  self.SetBrush(wx.Brush(wxColor))
1758 
1759  def _setPen(self):
1760  """!Set pen"""
1761  if self.isParameterized:
1762  width = int(UserSettings.Get(group='modeler', key='action',
1763  subkey=('width', 'parameterized')))
1764  else:
1765  width = int(UserSettings.Get(group='modeler', key='action',
1766  subkey=('width', 'default')))
1767  pen = self.GetPen()
1768  pen.SetWidth(width)
1769  self.SetPen(pen)
1770 
1771  def SetId(self, id):
1772  """!Set id"""
1773  self.id = id
1774  cmd = self.task.getCmd(ignoreErrors = True)
1775  if cmd and len(cmd) > 0:
1776  self.ClearText()
1777  self.AddText('(%d) %s' % (self.id, cmd[0]))
1778  else:
1779  self.AddText('(%d) <<%s>>' % (self.id, _("unknown")))
1780 
1781  def SetProperties(self, params, propwin):
1782  """!Record properties dialog"""
1783  self.task.params = params['params']
1784  self.task.flags = params['flags']
1785  self.propWin = propwin
1786 
1787  def GetPropDialog(self):
1788  """!Get properties dialog"""
1789  return self.propWin
1790 
1791  def GetLog(self, string = True):
1792  """!Get logging info"""
1793  cmd = self.task.getCmd(ignoreErrors = True, ignoreRequired = True)
1794 
1795  # substitute variables
1796  variables = self.parent.GetVariables()
1797  fparams = self.parent.GetVariables(params = True)
1798  params = None
1799  for values in fparams.itervalues():
1800  params = values['params']
1801  break
1802 
1803  for variable in variables:
1804  pattern= re.compile('%' + variable)
1805  value = None
1806  if params:
1807  for p in params:
1808  if variable == p.get('name', ''):
1809  value = p.get('value', '')
1810  break
1811  if not value:
1812  value = variables[variable].get('value', '')
1813 
1814  for idx in range(len(cmd)):
1815  if pattern.search(cmd[idx]):
1816  if value:
1817  cmd[idx] = pattern.sub(value, cmd[idx])
1818  else:
1819  self.isValid = False
1820  break
1821  idx += 1
1822 
1823  if string:
1824  if cmd is None:
1825  return ''
1826  else:
1827  return ' '.join(cmd)
1828 
1829  return cmd
1830 
1831  def GetName(self):
1832  """!Get name"""
1833  cmd = self.task.getCmd(ignoreErrors = True)
1834  if cmd and len(cmd) > 0:
1835  return cmd[0]
1836 
1837  return _('unknown')
1838 
1839  def GetParams(self, dcopy = False):
1840  """!Get dictionary of parameters"""
1841  if dcopy:
1842  return copy.deepcopy(self.task.get_options())
1843 
1844  return self.task.get_options()
1845 
1846  def GetTask(self):
1847  """!Get grassTask instance"""
1848  return self.task
1849 
1850  def SetParams(self, params):
1851  """!Set dictionary of parameters"""
1852  self.task.params = params['params']
1853  self.task.flags = params['flags']
1854 
1855  def MergeParams(self, params):
1856  """!Merge dictionary of parameters"""
1857  if 'flags' in params:
1858  for f in params['flags']:
1859  self.task.set_flag(f['name'],
1860  f.get('value', False))
1861  if 'params' in params:
1862  for p in params['params']:
1863  self.task.set_param(p['name'],
1864  p.get('value', ''))
1865 
1866  def SetValid(self, isvalid):
1867  """!Set instance to be valid/invalid"""
1868  self.isValid = isvalid
1869  self._setBrush()
1870 
1871  def SetParameterized(self, isparameterized):
1872  """!Set action parameterized"""
1873  self.isParameterized = isparameterized
1874  if self.parent.GetCanvas():
1875  self._setPen()
1876 
1877  def IsParameterized(self):
1878  """!Check if action is parameterized"""
1879  return self.isParameterized
1880 
1881  def FindData(self, name):
1882  """!Find data item by name"""
1883  for rel in self.GetRelations():
1884  data = rel.GetData()
1885  if name == rel.GetName() and name in data.GetName():
1886  return data
1887 
1888  return None
1889 
1890  def Update(self, running = False):
1891  """!Update action"""
1892  if running:
1893  self._setBrush(running = True)
1894  else:
1895  self._setBrush()
1896  self._setPen()
1897 
1898  def OnDraw(self, dc):
1899  """!Draw action in canvas"""
1900  self._setBrush()
1901  self._setPen()
1902  ogl.RectangleShape.OnDraw(self, dc)
1903 
1904 class ModelData(ModelObject, ogl.EllipseShape):
1905  def __init__(self, parent, x, y, value = '', prompt = '', width = None, height = None):
1906  """Data item class
1907 
1908  @param parent window parent
1909  @param x, y position of the shape
1910  @param fname, tname list of parameter names from / to
1911  @param value value
1912  @param prompt type of GIS element
1913  @param width,height dimension of the shape
1914  """
1915  ModelObject.__init__(self)
1916 
1917  self.parent = parent
1918  self.value = value
1919  self.prompt = prompt
1920  self.intermediate = False
1921  self.propWin = None
1922  if not width:
1923  width = UserSettings.Get(group='modeler', key='data', subkey=('size', 'width'))
1924  if not height:
1925  height = UserSettings.Get(group='modeler', key='data', subkey=('size', 'height'))
1926 
1927  if self.parent.GetCanvas():
1928  ogl.EllipseShape.__init__(self, width, height)
1929 
1930  self.SetCanvas(self.parent)
1931  self.SetX(x)
1932  self.SetY(y)
1933  self.SetPen(wx.BLACK_PEN)
1934  self._setBrush()
1935 
1936  self._setText()
1937 
1938  def IsIntermediate(self):
1939  """!Checks if data item is intermediate"""
1940  return self.intermediate
1941 
1942  def SetIntermediate(self, im):
1943  """!Set intermediate flag"""
1944  self.intermediate = im
1945 
1946  def OnDraw(self, dc):
1947  pen = self.GetPen()
1948  pen.SetWidth(1)
1949  if self.intermediate:
1950  pen.SetStyle(wx.SHORT_DASH)
1951  else:
1952  pen.SetStyle(wx.SOLID)
1953  self.SetPen(pen)
1954 
1955  ogl.EllipseShape.OnDraw(self, dc)
1956 
1957  def GetLog(self, string = True):
1958  """!Get logging info"""
1959  name = list()
1960  for rel in self.GetRelations():
1961  name.append(rel.GetName())
1962  if name:
1963  return '/'.join(name) + '=' + self.value + ' (' + self.prompt + ')'
1964  else:
1965  return self.value + ' (' + self.prompt + ')'
1966 
1967  def GetName(self):
1968  """!Get list of names"""
1969  name = list()
1970  for rel in self.GetRelations():
1971  name.append(rel.GetName())
1972 
1973  return name
1974 
1975  def GetPrompt(self):
1976  """!Get prompt"""
1977  return self.prompt
1978 
1979  def SetPrompt(self, prompt):
1980  """!Set prompt
1981 
1982  @param prompt
1983  """
1984  self.prompt = prompt
1985 
1986  def GetValue(self):
1987  """!Get value"""
1988  return self.value
1989 
1990  def SetValue(self, value):
1991  """!Set value
1992 
1993  @param value
1994  """
1995  self.value = value
1996  self._setText()
1997  for direction in ('from', 'to'):
1998  for rel in self.GetRelations(direction):
1999  if direction == 'from':
2000  action = rel.GetTo()
2001  else:
2002  action = rel.GetFrom()
2003 
2004  task = menuform.GUI(show = None).ParseCommand(cmd = action.GetLog(string = False))
2005  task.set_param(rel.GetName(), self.value)
2006  action.SetParams(params = task.get_options())
2007 
2008  def GetPropDialog(self):
2009  """!Get properties dialog"""
2010  return self.propWin
2011 
2012  def SetPropDialog(self, win):
2013  """!Get properties dialog"""
2014  self.propWin = win
2015 
2016  def _setBrush(self):
2017  """!Set brush"""
2018  if self.prompt == 'raster':
2019  color = UserSettings.Get(group = 'modeler', key = 'data',
2020  subkey = ('color', 'raster'))
2021  elif self.prompt == 'raster3d':
2022  color = UserSettings.Get(group = 'modeler', key = 'data',
2023  subkey = ('color', 'raster3d'))
2024  elif self.prompt == 'vector':
2025  color = UserSettings.Get(group = 'modeler', key = 'data',
2026  subkey = ('color', 'vector'))
2027  else:
2028  color = UserSettings.Get(group = 'modeler', key = 'action',
2029  subkey = ('color', 'invalid'))
2030  wxColor = wx.Color(color[0], color[1], color[2])
2031  self.SetBrush(wx.Brush(wxColor))
2032 
2033  def _setPen(self):
2034  """!Set pen"""
2035  isParameterized = False
2036  for rel in self.GetRelations('from'):
2037  if rel.GetTo().IsParameterized():
2038  isParameterized = True
2039  break
2040  if not isParameterized:
2041  for rel in self.GetRelations('to'):
2042  if rel.GetFrom().IsParameterized():
2043  isParameterized = True
2044  break
2045 
2046  if isParameterized:
2047  width = int(UserSettings.Get(group = 'modeler', key = 'action',
2048  subkey = ('width', 'parameterized')))
2049  else:
2050  width = int(UserSettings.Get(group = 'modeler', key = 'action',
2051  subkey = ('width', 'default')))
2052  pen = self.GetPen()
2053  pen.SetWidth(width)
2054  self.SetPen(pen)
2055 
2056  def _setText(self):
2057  """!Update text"""
2058  self.ClearText()
2059  name = []
2060  for rel in self.GetRelations():
2061  name.append(rel.GetName())
2062  self.AddText('/'.join(name))
2063  if self.value:
2064  self.AddText(self.value)
2065  else:
2066  self.AddText(_('<not defined>'))
2067 
2068  def Update(self):
2069  """!Update action"""
2070  self._setBrush()
2071  self._setPen()
2072  self._setText()
2073 
2074 class ModelDataDialog(ElementDialog):
2075  """!Data item properties dialog"""
2076  def __init__(self, parent, shape, id = wx.ID_ANY, title = _("Data properties"),
2077  style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER):
2078  self.parent = parent
2079  self.shape = shape
2080 
2081  label, etype = self._getLabel()
2082  ElementDialog.__init__(self, parent, title, label = label, etype = etype)
2083 
2084  self.element = gselect.Select(parent = self.panel,
2085  type = prompt)
2086  self.element.SetValue(shape.GetValue())
2087 
2088  self.Bind(wx.EVT_BUTTON, self.OnOK, self.btnOK)
2089  self.Bind(wx.EVT_BUTTON, self.OnCancel, self.btnCancel)
2090 
2091  self.PostInit()
2092 
2093  if shape.GetValue():
2094  self.btnOK.Enable()
2095 
2096  self._layout()
2097  self.SetMinSize(self.GetSize())
2098 
2099  def _getLabel(self):
2100  etype = False
2101  prompt = self.shape.GetPrompt()
2102  if prompt == 'raster':
2103  label = _('Name of raster map:')
2104  elif prompt == 'vector':
2105  label = _('Name of vector map:')
2106  else:
2107  etype = True
2108  label = _('Name of element:')
2109 
2110  return label, etype
2111 
2112  def _layout(self):
2113  """!Do layout"""
2114  self.dataSizer.Add(self.element, proportion=0,
2115  flag=wx.EXPAND | wx.ALL, border=1)
2116 
2117  self.panel.SetSizer(self.sizer)
2118  self.sizer.Fit(self)
2119 
2120  def OnOK(self, event):
2121  """!Ok pressed"""
2122  self.shape.SetValue(self.GetElement())
2123  if self.etype:
2124  elem = self.GetType()
2125  if elem == 'rast':
2126  self.shape.SetPrompt('raster')
2127  elif elem == 'vect':
2128  self.shape.SetPrompt('raster')
2129 
2130  self.parent.canvas.Refresh()
2131  self.parent.SetStatusText('', 0)
2132  self.shape.SetPropDialog(None)
2133 
2134  if self.IsModal():
2135  event.Skip()
2136  else:
2137  self.Destroy()
2138 
2139  def OnCancel(self, event):
2140  """!Cancel pressed"""
2141  self.shape.SetPropDialog(None)
2142  if self.IsModal():
2143  event.Skip()
2144  else:
2145  self.Destroy()
2146 
2147 class ModelEvtHandler(ogl.ShapeEvtHandler):
2148  """!Model event handler class"""
2149  def __init__(self, log, frame):
2150  ogl.ShapeEvtHandler.__init__(self)
2151  self.log = log
2152  self.frame = frame
2153  self.x = self.y = None
2154 
2155  def OnLeftClick(self, x, y, keys = 0, attachment = 0):
2156  """!Left mouse button pressed -> select item & update statusbar"""
2157  shape = self.GetShape()
2158  canvas = shape.GetCanvas()
2159  dc = wx.ClientDC(canvas)
2160  canvas.PrepareDC(dc)
2161 
2162  if hasattr(self.frame, 'defineRelation'):
2163  drel = self.frame.defineRelation
2164  if drel['from'] is None:
2165  drel['from'] = shape
2166  elif drel['to'] is None:
2167  drel['to'] = shape
2168  rel = ModelRelation(parent = self.frame, fromShape = drel['from'],
2169  toShape = drel['to'])
2170  dlg = ModelRelationDialog(parent = self.frame,
2171  shape = rel)
2172  if dlg.IsValid():
2173  ret = dlg.ShowModal()
2174  if ret == wx.ID_OK:
2175  option = dlg.GetOption()
2176  rel.SetName(option)
2177  drel['from'].AddRelation(rel)
2178  drel['to'].AddRelation(rel)
2179  drel['from'].Update()
2180  params = { 'params' : [{ 'name' : option,
2181  'value' : drel['from'].GetValue()}] }
2182  drel['to'].MergeParams(params)
2183  self.frame.AddLine(rel)
2184 
2185  dlg.Destroy()
2186  del self.frame.defineRelation
2187 
2188  if shape.Selected():
2189  shape.Select(False, dc)
2190  else:
2191  redraw = False
2192  shapeList = canvas.GetDiagram().GetShapeList()
2193  toUnselect = list()
2194 
2195  for s in shapeList:
2196  if s.Selected():
2197  toUnselect.append(s)
2198 
2199  shape.Select(True, dc)
2200 
2201  for s in toUnselect:
2202  s.Select(False, dc)
2203 
2204  canvas.Refresh(False)
2205 
2206  if hasattr(shape, "GetLog"):
2207  self.log.SetStatusText(shape.GetLog(), 0)
2208  else:
2209  self.log.SetStatusText('', 0)
2210 
2211  def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0):
2212  """!Left mouse button pressed (double-click) -> show properties"""
2213  self.OnProperties()
2214 
2215  def OnProperties(self, event = None):
2216  """!Show properties dialog"""
2217  self.frame.ModelChanged()
2218  shape = self.GetShape()
2219  if isinstance(shape, ModelAction):
2220  module = menuform.GUI(parent = self.frame, show = True).ParseCommand(shape.GetLog(string = False),
2221  completed = (self.frame.GetOptData, shape, shape.GetParams()))
2222 
2223  elif isinstance(shape, ModelData):
2224  dlg = ModelDataDialog(parent = self.frame, shape = shape)
2225  shape.SetPropDialog(dlg)
2226  dlg.CentreOnParent()
2227  dlg.Show()
2228 
2229  elif isinstance(shape, ModelLoop):
2230  dlg = ModelLoopDialog(parent = self.frame, shape = shape)
2231  dlg.CentreOnParent()
2232  if dlg.ShowModal() == wx.ID_OK:
2233  shape.SetText(dlg.GetCondition())
2234  alist = list()
2235  ids = dlg.GetItems()
2236  for aId in ids['unchecked']:
2237  action = self.frame.GetModel().GetItem(aId)
2238  action.UnSetBlock(shape)
2239  for aId in ids['checked']:
2240  action = self.frame.GetModel().GetItem(aId)
2241  action.SetBlock(shape)
2242  if action:
2243  alist.append(action)
2244  shape.SetItems(alist)
2245  self.frame.DefineLoop(shape)
2246  self.frame.GetCanvas().Refresh()
2247 
2248  dlg.Destroy()
2249 
2250  elif isinstance(shape, ModelCondition):
2251  dlg = ModelConditionDialog(parent = self.frame, shape = shape)
2252  dlg.CentreOnParent()
2253  if dlg.ShowModal() == wx.ID_OK:
2254  shape.SetText(dlg.GetCondition())
2255  ids = dlg.GetItems()
2256  for b in ids.keys():
2257  alist = list()
2258  for aId in ids[b]['unchecked']:
2259  action = self.frame.GetModel().GetItem(aId)
2260  action.UnSetBlock(shape)
2261  for aId in ids[b]['checked']:
2262  action = self.frame.GetModel().GetItem(aId)
2263  action.SetBlock(shape)
2264  if action:
2265  alist.append(action)
2266  shape.SetItems(alist, branch = b)
2267  self.frame.DefineCondition(shape)
2268  self.frame.GetCanvas().Refresh()
2269 
2270  dlg.Destroy()
2271 
2272  def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
2273  """!Drag shape (begining)"""
2274  self.frame.ModelChanged()
2275  if self._previousHandler:
2276  self._previousHandler.OnBeginDragLeft(x, y, keys, attachment)
2277 
2278  def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
2279  """!Drag shape (end)"""
2280  if self._previousHandler:
2281  self._previousHandler.OnEndDragLeft(x, y, keys, attachment)
2282 
2283  shape = self.GetShape()
2284  if isinstance(shape, ModelLoop):
2285  self.frame.DefineLoop(shape)
2286  elif isinstance(shape, ModelCondition):
2287  self.frame.DefineCondition(shape)
2288 
2289  for mo in shape.GetBlock():
2290  if isinstance(mo, ModelLoop):
2291  self.frame.DefineLoop(mo)
2292  elif isinstance(mo, ModelCondition):
2293  self.frame.DefineCondition(mo)
2294 
2295  def OnEndSize(self, x, y):
2296  """!Resize shape"""
2297  self.frame.ModelChanged()
2298  if self._previousHandler:
2299  self._previousHandler.OnEndSize(x, y)
2300 
2301  def OnRightClick(self, x, y, keys = 0, attachment = 0):
2302  """!Right click -> pop-up menu"""
2303  if not hasattr (self, "popupID"):
2304  self.popupID = dict()
2305  for key in ('remove', 'enable', 'addPoint',
2306  'delPoint', 'intermediate', 'props', 'id'):
2307  self.popupID[key] = wx.NewId()
2308 
2309  # record coordinates
2310  self.x = x
2311  self.y = y
2312 
2313  shape = self.GetShape()
2314  popupMenu = wx.Menu()
2315  popupMenu.Append(self.popupID['remove'], text=_('Remove'))
2316  self.frame.Bind(wx.EVT_MENU, self.OnRemove, id = self.popupID['remove'])
2317  if isinstance(shape, ModelAction) or isinstance(shape, ModelLoop):
2318  if shape.IsEnabled():
2319  popupMenu.Append(self.popupID['enable'], text=_('Disable'))
2320  self.frame.Bind(wx.EVT_MENU, self.OnDisable, id = self.popupID['enable'])
2321  else:
2322  popupMenu.Append(self.popupID['enable'], text=_('Enable'))
2323  self.frame.Bind(wx.EVT_MENU, self.OnEnable, id = self.popupID['enable'])
2324 
2325  if isinstance(shape, ModelRelation):
2326  popupMenu.AppendSeparator()
2327  popupMenu.Append(self.popupID['addPoint'], text=_('Add control point'))
2328  self.frame.Bind(wx.EVT_MENU, self.OnAddPoint, id = self.popupID['addPoint'])
2329  popupMenu.Append(self.popupID['delPoint'], text=_('Remove control point'))
2330  self.frame.Bind(wx.EVT_MENU, self.OnRemovePoint, id = self.popupID['delPoint'])
2331  if len(shape.GetLineControlPoints()) == 2:
2332  popupMenu.Enable(self.popupID['delPoint'], False)
2333 
2334  if isinstance(shape, ModelData) and '@' not in shape.GetValue():
2335  popupMenu.AppendSeparator()
2336  popupMenu.Append(self.popupID['intermediate'], text=_('Intermediate'),
2337  kind = wx.ITEM_CHECK)
2338  if self.GetShape().IsIntermediate():
2339  popupMenu.Check(self.popupID['intermediate'], True)
2340 
2341  self.frame.Bind(wx.EVT_MENU, self.OnIntermediate, id = self.popupID['intermediate'])
2342 
2343  if isinstance(shape, ModelData) or \
2344  isinstance(shape, ModelAction) or \
2345  isinstance(shape, ModelLoop):
2346  popupMenu.AppendSeparator()
2347  popupMenu.Append(self.popupID['props'], text=_('Properties'))
2348  self.frame.Bind(wx.EVT_MENU, self.OnProperties, id = self.popupID['props'])
2349 
2350  if isinstance(shape, ModelAction):
2351  popupMenu.Append(self.popupID['id'], text=_('Change ID'))
2352  self.frame.Bind(wx.EVT_MENU, self.OnChangeId, id = self.popupID['id'])
2353 
2354  self.frame.PopupMenu(popupMenu)
2355  popupMenu.Destroy()
2356 
2357  def OnChangeId(self, event):
2358  """!Change action id"""
2359  pass
2360 
2361  def OnDisable(self, event):
2362  """!Disable action"""
2363  self._onEnable(False)
2364 
2365  def OnEnable(self, event):
2366  """!Disable action"""
2367  self._onEnable(True)
2368 
2369  def _onEnable(self, enable):
2370  shape = self.GetShape()
2371  shape.Enable(enable)
2372  self.frame.ModelChanged()
2373  self.frame.canvas.Refresh()
2374 
2375  def OnAddPoint(self, event):
2376  """!Add control point"""
2377  shape = self.GetShape()
2378  shape.InsertLineControlPoint(point = wx.RealPoint(self.x, self.y))
2379  shape.ResetShapes()
2380  shape.Select(True)
2381  self.frame.ModelChanged()
2382  self.frame.canvas.Refresh()
2383 
2384  def OnRemovePoint(self, event):
2385  """!Remove control point"""
2386  shape = self.GetShape()
2387  shape.DeleteLineControlPoint()
2388  shape.Select(False)
2389  shape.Select(True)
2390  self.frame.ModelChanged()
2391  self.frame.canvas.Refresh()
2392 
2393  def OnIntermediate(self, event):
2394  """!Mark data as intermediate"""
2395  self.frame.ModelChanged()
2396  shape = self.GetShape()
2397  shape.SetIntermediate(event.IsChecked())
2398  self.frame.canvas.Refresh()
2399 
2400  def OnRemove(self, event):
2401  """!Remove shape
2402  """
2403  self.frame.GetCanvas().RemoveSelected()
2404  self.frame.itemPanel.Update()
2405 
2406 class ModelSearchDialog(wx.Dialog):
2407  def __init__(self, parent, id = wx.ID_ANY, title = _("Add new GRASS module to the model"),
2408  style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
2409  """!Graphical modeler module search window
2410 
2411  @param parent parent window
2412  @param id window id
2413  @param title window title
2414  @param kwargs wx.Dialogs' arguments
2415  """
2416  self.parent = parent
2417 
2418  wx.Dialog.__init__(self, parent = parent, id = id, title = title, **kwargs)
2419  self.SetName("ModelerDialog")
2420  self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
2421 
2422  self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
2423 
2424  self.cmdBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
2425  label=" %s " % _("Command"))
2426 
2427  self.cmd_prompt = prompt.GPromptSTC(parent = self)
2428  self.search = SearchModuleWindow(parent = self.panel, cmdPrompt = self.cmd_prompt, showTip = True)
2429  wx.CallAfter(self.cmd_prompt.SetFocus)
2430 
2431  # get commands
2432  items = self.cmd_prompt.GetCommandItems()
2433 
2434  self.btnCancel = wx.Button(self.panel, wx.ID_CANCEL)
2435  self.btnOk = wx.Button(self.panel, wx.ID_OK)
2436  self.btnOk.SetDefault()
2437  self.btnOk.Enable(False)
2438 
2439  self.cmd_prompt.Bind(wx.EVT_KEY_UP, self.OnText)
2440  self.search.searchChoice.Bind(wx.EVT_CHOICE, self.OnText)
2441  self.Bind(wx.EVT_BUTTON, self.OnOk, self.btnOk)
2442 
2443  self._layout()
2444 
2445  self.SetSize((500, 275))
2446 
2447  def _layout(self):
2448  cmdSizer = wx.StaticBoxSizer(self.cmdBox, wx.VERTICAL)
2449  cmdSizer.Add(item = self.cmd_prompt, proportion = 1,
2450  flag = wx.EXPAND)
2451 
2452  btnSizer = wx.StdDialogButtonSizer()
2453  btnSizer.AddButton(self.btnCancel)
2454  btnSizer.AddButton(self.btnOk)
2455  btnSizer.Realize()
2456 
2457  mainSizer = wx.BoxSizer(wx.VERTICAL)
2458  mainSizer.Add(item = self.search, proportion = 0,
2459  flag = wx.EXPAND | wx.ALL, border = 3)
2460  mainSizer.Add(item = cmdSizer, proportion = 1,
2461  flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border = 3)
2462  mainSizer.Add(item = btnSizer, proportion = 0,
2463  flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
2464 
2465  self.panel.SetSizer(mainSizer)
2466  mainSizer.Fit(self.panel)
2467 
2468  self.Layout()
2469 
2470  def GetPanel(self):
2471  """!Get dialog panel"""
2472  return self.panel
2473 
2474  def GetCmd(self):
2475  """!Get command"""
2476  line = self.cmd_prompt.GetCurLine()[0].strip()
2477  if len(line) == 0:
2478  list()
2479 
2480  try:
2481  cmd = utils.split(str(line))
2482  except UnicodeError:
2483  cmd = utils.split(utils.EncodeString((line)))
2484 
2485  return cmd
2486 
2487  def OnOk(self, event):
2488  """!Button 'OK' pressed"""
2489  self.btnOk.SetFocus()
2490  cmd = self.GetCmd()
2491 
2492  if len(cmd) < 1:
2493  GError(parent = self,
2494  message = _("Command not defined.\n\n"
2495  "Unable to add new action to the model."))
2496  return
2497 
2498  if cmd[0] not in globalvar.grassCmd['all']:
2499  GError(parent = self,
2500  message = _("'%s' is not a GRASS module.\n\n"
2501  "Unable to add new action to the model.") % cmd[0])
2502  return
2503 
2504  self.EndModal(wx.ID_OK)
2505 
2506  def OnText(self, event):
2507  """!Text in prompt changed"""
2508  if self.cmd_prompt.AutoCompActive():
2509  event.Skip()
2510  return
2511 
2512  if isinstance(event, wx.KeyEvent):
2513  entry = self.cmd_prompt.GetTextLeft()
2514  elif isinstance(event, wx.stc.StyledTextEvent):
2515  entry = event.GetText()
2516  else:
2517  entry = event.GetString()
2518 
2519  if entry:
2520  self.btnOk.Enable()
2521  else:
2522  self.btnOk.Enable(False)
2523 
2524  event.Skip()
2525 
2526  def Reset(self):
2527  """!Reset dialog"""
2528  self.search.Reset()
2529  self.cmd_prompt.OnCmdErase(None)
2530  self.btnOk.Enable(False)
2531  self.cmd_prompt.SetFocus()
2532 
2533 class ModelRelation(ogl.LineShape):
2534  """!Data - action relation"""
2535  def __init__(self, parent, fromShape, toShape, param = ''):
2536  self.fromShape = fromShape
2537  self.toShape = toShape
2538  self.param = param
2539  self.parent = parent
2540 
2541  self._points = None
2542 
2543  if self.parent.GetCanvas():
2544  ogl.LineShape.__init__(self)
2545 
2546  def __del__(self):
2547  if self in self.fromShape.rels:
2548  self.fromShape.rels.remove(self)
2549  if self in self.toShape.rels:
2550  self.toShape.rels.remove(self)
2551 
2552  def GetFrom(self):
2553  """!Get id of 'from' shape"""
2554  return self.fromShape
2555 
2556  def GetTo(self):
2557  """!Get id of 'to' shape"""
2558  return self.toShape
2559 
2560  def GetData(self):
2561  """!Get related ModelData instance
2562 
2563  @return ModelData instance
2564  @return None if not found
2565  """
2566  if isinstance(self.fromShape, ModelData):
2567  return self.fromShape
2568  elif isinstance(self.toShape, ModelData):
2569  return self.toShape
2570 
2571  return None
2572 
2573  def GetName(self):
2574  """!Get parameter name"""
2575  return self.param
2576 
2577  def ResetShapes(self):
2578  """!Reset related objects"""
2579  self.fromShape.ResetControlPoints()
2580  self.toShape.ResetControlPoints()
2581  self.ResetControlPoints()
2582 
2583  def SetControlPoints(self, points):
2584  """!Set control points"""
2585  self._points = points
2586 
2587  def GetControlPoints(self):
2588  """!Get list of control points"""
2589  return self._points
2590 
2591  def _setPen(self):
2592  """!Set pen"""
2593  pen = self.GetPen()
2594  pen.SetWidth(1)
2595  pen.SetStyle(wx.SOLID)
2596  self.SetPen(pen)
2597 
2598  def OnDraw(self, dc):
2599  """!Draw relation"""
2600  self._setPen()
2601  ogl.LineShape.OnDraw(self, dc)
2602 
2603  def SetName(self, param):
2604  self.param = param
2605 
2606 class ModelRelationDialog(wx.Dialog):
2607  """!Relation properties dialog"""
2608  def __init__(self, parent, shape, id = wx.ID_ANY, title = _("Relation properties"),
2609  style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
2610  self.parent = parent
2611  self.shape = shape
2612 
2613  options = self._getOptions()
2614  if not options:
2615  self.valid = False
2616  return
2617 
2618  self.valid = True
2619  wx.Dialog.__init__(self, parent, id, title, style = style, **kwargs)
2620  self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
2621 
2622  self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
2623 
2624  self.fromBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
2625  label = " %s " % _("From"))
2626  self.toBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
2627  label = " %s " % _("To"))
2628 
2629  self.option = wx.ComboBox(parent = self.panel, id = wx.ID_ANY,
2630  style = wx.CB_READONLY,
2631  choices = options)
2632  self.option.Bind(wx.EVT_COMBOBOX, self.OnOption)
2633 
2634  self.btnCancel = wx.Button(self.panel, wx.ID_CANCEL)
2635  self.btnOk = wx.Button(self.panel, wx.ID_OK)
2636  self.btnOk.Enable(False)
2637 
2638  self._layout()
2639 
2640  def _layout(self):
2641  mainSizer = wx.BoxSizer(wx.VERTICAL)
2642 
2643  fromSizer = wx.StaticBoxSizer(self.fromBox, wx.VERTICAL)
2644  self._layoutShape(shape = self.shape.GetFrom(), sizer = fromSizer)
2645  toSizer = wx.StaticBoxSizer(self.toBox, wx.VERTICAL)
2646  self._layoutShape(shape = self.shape.GetTo(), sizer = toSizer)
2647 
2648  btnSizer = wx.StdDialogButtonSizer()
2649  btnSizer.AddButton(self.btnCancel)
2650  btnSizer.AddButton(self.btnOk)
2651  btnSizer.Realize()
2652 
2653  mainSizer.Add(item = fromSizer, proportion = 0,
2654  flag = wx.EXPAND | wx.ALL, border = 5)
2655  mainSizer.Add(item = toSizer, proportion = 0,
2656  flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
2657  mainSizer.Add(item = btnSizer, proportion = 0,
2658  flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
2659 
2660  self.panel.SetSizer(mainSizer)
2661  mainSizer.Fit(self.panel)
2662 
2663  self.Layout()
2664  self.SetSize(self.GetBestSize())
2665 
2666  def _layoutShape(self, shape, sizer):
2667  if isinstance(shape, ModelData):
2668  sizer.Add(item = wx.StaticText(parent = self.panel, id = wx.ID_ANY,
2669  label = _("Data: %s") % shape.GetLog()),
2670  proportion = 1, flag = wx.EXPAND | wx.ALL,
2671  border = 5)
2672  elif isinstance(shape, ModelAction):
2673  gridSizer = wx.GridBagSizer (hgap = 5, vgap = 5)
2674  gridSizer.Add(item = wx.StaticText(parent = self.panel, id = wx.ID_ANY,
2675  label = _("Command:")),
2676  pos = (0, 0))
2677  gridSizer.Add(item = wx.StaticText(parent = self.panel, id = wx.ID_ANY,
2678  label = shape.GetName()),
2679  pos = (0, 1))
2680  gridSizer.Add(item = wx.StaticText(parent = self.panel, id = wx.ID_ANY,
2681  label = _("Option:")),
2682  flag = wx.ALIGN_CENTER_VERTICAL,
2683  pos = (1, 0))
2684  gridSizer.Add(item = self.option,
2685  pos = (1, 1))
2686  sizer.Add(item = gridSizer,
2687  proportion = 1, flag = wx.EXPAND | wx.ALL,
2688  border = 5)
2689 
2690  def _getOptions(self):
2691  """!Get relevant options"""
2692  items = []
2693  fromShape = self.shape.GetFrom()
2694  if not isinstance(fromShape, ModelData):
2695  GError(parent = self.parent,
2696  message = _("Relation doesn't start with data item.\n"
2697  "Unable to add relation."))
2698  return items
2699 
2700  toShape = self.shape.GetTo()
2701  if not isinstance(toShape, ModelAction):
2702  GError(parent = self.parent,
2703  message = _("Relation doesn't point to GRASS command.\n"
2704  "Unable to add relation."))
2705  return items
2706 
2707  prompt = fromShape.GetPrompt()
2708  task = toShape.GetTask()
2709  for p in task.get_options()['params']:
2710  if p.get('prompt', '') == prompt and \
2711  'name' in p:
2712  items.append(p['name'])
2713 
2714  if not items:
2715  GError(parent = self.parent,
2716  message = _("No relevant option found.\n"
2717  "Unable to add relation."))
2718  return items
2719 
2720  def GetOption(self):
2721  """!Get selected option"""
2722  return self.option.GetStringSelection()
2723 
2724  def IsValid(self):
2725  """!Check if relation is valid"""
2726  return self.valid
2727 
2728  def OnOption(self, event):
2729  """!Set option"""
2730  if event.GetString():
2731  self.btnOk.Enable()
2732  else:
2733  self.btnOk.Enable(False)
2734 
2736  """!Process GRASS model file (gxm)"""
2737  def __init__(self, tree):
2738  """!A ElementTree handler for the GXM XML file, as defined in
2739  grass-gxm.dtd.
2740  """
2741  self.tree = tree
2742  self.root = self.tree.getroot()
2743 
2744  # list of actions, data
2745  self.properties = dict()
2746  self.variables = dict()
2747  self.actions = list()
2748  self.data = list()
2749  self.loops = list()
2750  self.conditions = list()
2751 
2752  self._processWindow()
2753  self._processProperties()
2754  self._processVariables()
2755  self._processItems()
2756  self._processData()
2757 
2758  def _filterValue(self, value):
2759  """!Filter value
2760 
2761  @param value
2762  """
2763  value = value.replace('&lt;', '<')
2764  value = value.replace('&gt;', '>')
2765 
2766  return value
2767 
2768  def _getNodeText(self, node, tag, default = ''):
2769  """!Get node text"""
2770  p = node.find(tag)
2771  if p is not None:
2772  if p.text:
2773  return utils.normalize_whitespace(p.text)
2774  else:
2775  return ''
2776 
2777  return default
2778 
2779  def _processWindow(self):
2780  """!Process window properties"""
2781  node = self.root.find('window')
2782  if node is None:
2783  self.pos = self.size = None
2784  return
2785 
2786  self.pos, self.size = self._getDim(node)
2787 
2788  def _processProperties(self):
2789  """!Process model properties"""
2790  node = self.root.find('properties')
2791  if node is None:
2792  return
2793  for key in ('name', 'description', 'author'):
2794  self._processProperty(node, key)
2795 
2796  for f in node.findall('flag'):
2797  name = f.get('name', '')
2798  if name == 'overwrite':
2799  self.properties['overwrite'] = True
2800 
2801  def _processProperty(self, pnode, name):
2802  """!Process given property"""
2803  node = pnode.find(name)
2804  if node is not None:
2805  self.properties[name] = node.text
2806  else:
2807  self.properties[name] = ''
2808 
2809  def _processVariables(self):
2810  """!Process model variables"""
2811  vnode = self.root.find('variables')
2812  if vnode is None:
2813  return
2814  for node in vnode.findall('variable'):
2815  name = node.get('name', '')
2816  if not name:
2817  continue # should not happen
2818  self.variables[name] = { 'type' : node.get('type', 'string') }
2819  for key in ('description', 'value'):
2820  self._processVariable(node, name, key)
2821 
2822  def _processVariable(self, pnode, name, key):
2823  """!Process given variable"""
2824  node = pnode.find(key)
2825  if node is not None:
2826  if node.text:
2827  self.variables[name][key] = node.text
2828 
2829  def _processItems(self):
2830  """!Process model items (actions, loops, conditions)"""
2831  self._processActions()
2832  self._processLoops()
2833  self._processConditions()
2834 
2835  def _processActions(self):
2836  """!Process model file"""
2837  for action in self.root.findall('action'):
2838  pos, size = self._getDim(action)
2839  disabled = False
2840 
2841  task = action.find('task')
2842  if task is not None:
2843  if task.find('disabled') is not None:
2844  disabled = True
2845  task = self._processTask(task)
2846  else:
2847  task = None
2848 
2849  aId = int(action.get('id', -1))
2850 
2851  self.actions.append({ 'pos' : pos,
2852  'size' : size,
2853  'task' : task,
2854  'id' : aId,
2855  'disabled' : disabled })
2856 
2857  def _getDim(self, node):
2858  """!Get position and size of shape"""
2859  pos = size = None
2860  posAttr = node.get('pos', None)
2861  if posAttr:
2862  posVal = map(int, posAttr.split(','))
2863  try:
2864  pos = (posVal[0], posVal[1])
2865  except:
2866  pos = None
2867 
2868  sizeAttr = node.get('size', None)
2869  if sizeAttr:
2870  sizeVal = map(int, sizeAttr.split(','))
2871  try:
2872  size = (sizeVal[0], sizeVal[1])
2873  except:
2874  size = None
2875 
2876  return pos, size
2877 
2878  def _processData(self):
2879  """!Process model file"""
2880  for data in self.root.findall('data'):
2881  pos, size = self._getDim(data)
2882  param = data.find('data-parameter')
2883  prompt = value = None
2884  if param is not None:
2885  prompt = param.get('prompt', None)
2886  value = self._filterValue(self._getNodeText(param, 'value'))
2887 
2888  if data.find('intermediate') is None:
2889  intermediate = False
2890  else:
2891  intermediate = True
2892 
2893  rels = list()
2894  for rel in data.findall('relation'):
2895  defrel = { 'id' : int(rel.get('id', -1)),
2896  'dir' : rel.get('dir', 'to'),
2897  'name' : rel.get('name', '') }
2898  points = list()
2899  for point in rel.findall('point'):
2900  x = self._filterValue(self._getNodeText(point, 'x'))
2901  y = self._filterValue(self._getNodeText(point, 'y'))
2902  points.append((float(x), float(y)))
2903  defrel['points'] = points
2904  rels.append(defrel)
2905 
2906  self.data.append({ 'pos' : pos,
2907  'size': size,
2908  'prompt' : prompt,
2909  'value' : value,
2910  'intermediate' : intermediate,
2911  'rels' : rels })
2912 
2913  def _processTask(self, node):
2914  """!Process task
2915 
2916  @return grassTask instance
2917  @return None on error
2918  """
2919  cmd = list()
2920  parameterized = list()
2921 
2922  name = node.get('name', None)
2923  if not name:
2924  return None
2925 
2926  cmd.append(name)
2927 
2928  # flags
2929  for f in node.findall('flag'):
2930  flag = f.get('name', '')
2931  if f.get('parameterized', '0') == '1':
2932  parameterized.append(('flag', flag))
2933  if f.get('value', '1') == '0':
2934  continue
2935  if len(flag) > 1:
2936  cmd.append('--' + flag)
2937  else:
2938  cmd.append('-' + flag)
2939  # parameters
2940  for p in node.findall('parameter'):
2941  name = p.get('name', '')
2942  if p.find('parameterized') is not None:
2943  parameterized.append(('param', name))
2944  cmd.append('%s=%s' % (name,
2945  self._filterValue(self._getNodeText(p, 'value'))))
2946 
2947  task, err = menuform.GUI(show = None, checkError = True).ParseCommand(cmd = cmd)
2948  if err:
2949  GWarning(os.linesep.join(err))
2950 
2951  for opt, name in parameterized:
2952  if opt == 'flag':
2953  task.set_flag(name, True, element = 'parameterized')
2954  else:
2955  task.set_param(name, True, element = 'parameterized')
2956 
2957  return task
2958 
2959  def _processLoops(self):
2960  """!Process model loops"""
2961  for node in self.root.findall('loop'):
2962  pos, size = self._getDim(node)
2963  text = self._filterValue(self._getNodeText(node, 'condition')).strip()
2964  aid = list()
2965  for anode in node.findall('item'):
2966  try:
2967  aid.append(int(anode.text))
2968  except ValueError:
2969  pass
2970 
2971  self.loops.append({ 'pos' : pos,
2972  'size' : size,
2973  'text' : text,
2974  'id' : int(node.get('id', -1)),
2975  'items' : aid })
2976 
2977  def _processConditions(self):
2978  """!Process model conditions"""
2979  for node in self.root.findall('if-else'):
2980  pos, size = self._getDim(node)
2981  text = self._filterValue(self._getNodeText(node, 'condition')).strip()
2982  aid = { 'if' : list(),
2983  'else' : list() }
2984  for b in aid.keys():
2985  bnode = node.find(b)
2986  if bnode is None:
2987  continue
2988  for anode in bnode.findall('item'):
2989  try:
2990  aid[b].append(int(anode.text))
2991  except ValueError:
2992  pass
2993 
2994  self.conditions.append({ 'pos' : pos,
2995  'size' : size,
2996  'text' : text,
2997  'id' : int(node.get('id', -1)),
2998  'items' : aid })
2999 
3001  """!Generic class for writing model file"""
3002  def __init__(self, fd, model):
3003  self.fd = fd
3004  self.model = model
3005  self.properties = model.GetProperties()
3006  self.variables = model.GetVariables()
3007  self.items = model.GetItems()
3008 
3009  self.indent = 0
3010 
3011  self._header()
3012 
3013  self._window()
3014  self._properties()
3015  self._variables()
3016  self._items()
3017 
3018  dataList = list()
3019  for action in model.GetItems(objType = ModelAction):
3020  for rel in action.GetRelations():
3021  dataItem = rel.GetData()
3022  if dataItem not in dataList:
3023  dataList.append(dataItem)
3024  self._data(dataList)
3025 
3026  self._footer()
3027 
3028  def _filterValue(self, value):
3029  """!Make value XML-valid"""
3030  value = value.replace('<', '&lt;')
3031  value = value.replace('>', '&gt;')
3032 
3033  return value
3034 
3035  def _header(self):
3036  """!Write header"""
3037  self.fd.write('<?xml version="1.0" encoding="UTF-8"?>\n')
3038  self.fd.write('<!DOCTYPE gxm SYSTEM "grass-gxm.dtd">\n')
3039  self.fd.write('%s<gxm>\n' % (' ' * self.indent))
3040  self.indent += 4
3041 
3042  def _footer(self):
3043  """!Write footer"""
3044  self.indent -= 4
3045  self.fd.write('%s</gxm>\n' % (' ' * self.indent))
3046 
3047  def _window(self):
3048  """!Write window properties"""
3049  canvas = self.model.GetCanvas()
3050  if canvas is None:
3051  return
3052  win = canvas.parent
3053  pos = win.GetPosition()
3054  size = win.GetSize()
3055  self.fd.write('%s<window pos="%d,%d" size="%d,%d" />\n' % \
3056  (' ' * self.indent, pos[0], pos[1], size[0], size[1]))
3057 
3058  def _properties(self):
3059  """!Write model properties"""
3060  self.fd.write('%s<properties>\n' % (' ' * self.indent))
3061  self.indent += 4
3062  if self.properties['name']:
3063  self.fd.write('%s<name>%s</name>\n' % (' ' * self.indent, self.properties['name']))
3064  if self.properties['description']:
3065  self.fd.write('%s<description>%s</description>\n' % (' ' * self.indent,
3066  utils.EncodeString(self.properties['description'])))
3067  if self.properties['author']:
3068  self.fd.write('%s<author>%s</author>\n' % (' ' * self.indent,
3069  utils.EncodeString(self.properties['author'])))
3070 
3071  if 'overwrite' in self.properties and \
3072  self.properties['overwrite']:
3073  self.fd.write('%s<flag name="overwrite" />\n' % (' ' * self.indent))
3074  self.indent -= 4
3075  self.fd.write('%s</properties>\n' % (' ' * self.indent))
3076 
3077  def _variables(self):
3078  """!Write model variables"""
3079  if not self.variables:
3080  return
3081  self.fd.write('%s<variables>\n' % (' ' * self.indent))
3082  self.indent += 4
3083  for name, values in self.variables.iteritems():
3084  self.fd.write('%s<variable name="%s" type="%s">\n' % \
3085  (' ' * self.indent, name, values['type']))
3086  self.indent += 4
3087  if 'value' in values:
3088  self.fd.write('%s<value>%s</value>\n' % \
3089  (' ' * self.indent, values['value']))
3090  if 'description' in values:
3091  self.fd.write('%s<description>%s</description>\n' % \
3092  (' ' * self.indent, values['description']))
3093  self.indent -= 4
3094  self.fd.write('%s</variable>\n' % (' ' * self.indent))
3095  self.indent -= 4
3096  self.fd.write('%s</variables>\n' % (' ' * self.indent))
3097 
3098  def _items(self):
3099  """!Write actions/loops/conditions"""
3100  for item in self.items:
3101  if isinstance(item, ModelAction):
3102  self._action(item)
3103  elif isinstance(item, ModelLoop):
3104  self._loop(item)
3105  elif isinstance(item, ModelCondition):
3106  self._condition(item)
3107 
3108  def _action(self, action):
3109  """!Write actions"""
3110  self.fd.write('%s<action id="%d" name="%s" pos="%d,%d" size="%d,%d">\n' % \
3111  (' ' * self.indent, action.GetId(), action.GetName(), action.GetX(), action.GetY(),
3112  action.GetWidth(), action.GetHeight()))
3113  self.indent += 4
3114  self.fd.write('%s<task name="%s">\n' % (' ' * self.indent, action.GetLog(string = False)[0]))
3115  self.indent += 4
3116  if not action.IsEnabled():
3117  self.fd.write('%s<disabled />\n' % (' ' * self.indent))
3118  for key, val in action.GetParams().iteritems():
3119  if key == 'flags':
3120  for f in val:
3121  if f.get('value', False) or f.get('parameterized', False):
3122  if f.get('parameterized', False):
3123  if f.get('value', False) == False:
3124  self.fd.write('%s<flag name="%s" value="0" parameterized="1" />\n' %
3125  (' ' * self.indent, f.get('name', '')))
3126  else:
3127  self.fd.write('%s<flag name="%s" parameterized="1" />\n' %
3128  (' ' * self.indent, f.get('name', '')))
3129  else:
3130  self.fd.write('%s<flag name="%s" />\n' %
3131  (' ' * self.indent, f.get('name', '')))
3132  else: # parameter
3133  for p in val:
3134  if not p.get('value', ''):
3135  continue
3136  self.fd.write('%s<parameter name="%s">\n' %
3137  (' ' * self.indent, p.get('name', '')))
3138  self.indent += 4
3139  if p.get('parameterized', False):
3140  self.fd.write('%s<parameterized />\n' % (' ' * self.indent))
3141  self.fd.write('%s<value>%s</value>\n' %
3142  (' ' * self.indent, self._filterValue(p.get('value', ''))))
3143  self.indent -= 4
3144  self.fd.write('%s</parameter>\n' % (' ' * self.indent))
3145  self.indent -= 4
3146  self.fd.write('%s</task>\n' % (' ' * self.indent))
3147  self.indent -= 4
3148  self.fd.write('%s</action>\n' % (' ' * self.indent))
3149 
3150  def _data(self, dataList):
3151  """!Write data"""
3152  for data in dataList:
3153  self.fd.write('%s<data pos="%d,%d" size="%d,%d">\n' % \
3154  (' ' * self.indent, data.GetX(), data.GetY(),
3155  data.GetWidth(), data.GetHeight()))
3156  self.indent += 4
3157  self.fd.write('%s<data-parameter prompt="%s">\n' % \
3158  (' ' * self.indent, data.GetPrompt()))
3159  self.indent += 4
3160  self.fd.write('%s<value>%s</value>\n' %
3161  (' ' * self.indent, self._filterValue(data.GetValue())))
3162  self.indent -= 4
3163  self.fd.write('%s</data-parameter>\n' % (' ' * self.indent))
3164 
3165  if data.IsIntermediate():
3166  self.fd.write('%s<intermediate />\n' % (' ' * self.indent))
3167 
3168  # relations
3169  for ft in ('from', 'to'):
3170  for rel in data.GetRelations(ft):
3171  if ft == 'from':
3172  aid = rel.GetTo().GetId()
3173  else:
3174  aid = rel.GetFrom().GetId()
3175  self.fd.write('%s<relation dir="%s" id="%d" name="%s">\n' % \
3176  (' ' * self.indent, ft, aid, rel.GetName()))
3177  self.indent += 4
3178  for point in rel.GetLineControlPoints()[1:-1]:
3179  self.fd.write('%s<point>\n' % (' ' * self.indent))
3180  self.indent += 4
3181  x, y = point.Get()
3182  self.fd.write('%s<x>%d</x>\n' % (' ' * self.indent, int(x)))
3183  self.fd.write('%s<y>%d</y>\n' % (' ' * self.indent, int(y)))
3184  self.indent -= 4
3185  self.fd.write('%s</point>\n' % (' ' * self.indent))
3186  self.indent -= 4
3187  self.fd.write('%s</relation>\n' % (' ' * self.indent))
3188 
3189  self.indent -= 4
3190  self.fd.write('%s</data>\n' % (' ' * self.indent))
3191 
3192  def _loop(self, loop):
3193  """!Write loops"""
3194  self.fd.write('%s<loop id="%d" pos="%d,%d" size="%d,%d">\n' % \
3195  (' ' * self.indent, loop.GetId(), loop.GetX(), loop.GetY(),
3196  loop.GetWidth(), loop.GetHeight()))
3197  text = loop.GetText()
3198  self.indent += 4
3199  if text:
3200  self.fd.write('%s<condition>%s</condition>\n' %
3201  (' ' * self.indent, self._filterValue(text)))
3202  for item in loop.GetItems():
3203  self.fd.write('%s<item>%d</item>\n' %
3204  (' ' * self.indent, item.GetId()))
3205  self.indent -= 4
3206  self.fd.write('%s</loop>\n' % (' ' * self.indent))
3207 
3208  def _condition(self, condition):
3209  """!Write conditions"""
3210  bbox = condition.GetBoundingBoxMin()
3211  self.fd.write('%s<if-else id="%d" pos="%d,%d" size="%d,%d">\n' % \
3212  (' ' * self.indent, condition.GetId(), condition.GetX(), condition.GetY(),
3213  bbox[0], bbox[1]))
3214  text = condition.GetText()
3215  self.indent += 4
3216  if text:
3217  self.fd.write('%s<condition>%s</condition>\n' %
3218  (' ' * self.indent, self._filterValue(text)))
3219  items = condition.GetItems()
3220  for b in items.keys():
3221  if len(items[b]) < 1:
3222  continue
3223  self.fd.write('%s<%s>\n' % (' ' * self.indent, b))
3224  self.indent += 4
3225  for item in items[b]:
3226  self.fd.write('%s<item>%d</item>\n' %
3227  (' ' * self.indent, item.GetId()))
3228  self.indent -= 4
3229  self.fd.write('%s</%s>\n' % (' ' * self.indent, b))
3230 
3231  self.indent -= 4
3232  self.fd.write('%s</if-else>\n' % (' ' * self.indent))
3233 
3234 class PreferencesDialog(PreferencesBaseDialog):
3235  """!User preferences dialog"""
3236  def __init__(self, parent, settings = UserSettings,
3237  title = _("Modeler settings")):
3238 
3239  PreferencesBaseDialog.__init__(self, parent = parent, title = title,
3240  settings = settings)
3241 
3242  # create notebook pages
3243  self._createGeneralPage(self.notebook)
3244  self._createActionPage(self.notebook)
3245  self._createDataPage(self.notebook)
3246  self._createLoopPage(self.notebook)
3247 
3248  self.SetMinSize(self.GetBestSize())
3249  self.SetSize(self.size)
3250 
3251  def _createGeneralPage(self, notebook):
3252  """!Create notebook page for action settings"""
3253  panel = wx.Panel(parent = notebook, id = wx.ID_ANY)
3254  notebook.AddPage(page = panel, text = _("General"))
3255 
3256  # colors
3257  border = wx.BoxSizer(wx.VERTICAL)
3258  box = wx.StaticBox (parent = panel, id = wx.ID_ANY,
3259  label = " %s " % _("Item properties"))
3260  sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
3261 
3262  gridSizer = wx.GridBagSizer (hgap = 3, vgap = 3)
3263  gridSizer.AddGrowableCol(0)
3264 
3265  row = 0
3266  gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
3267  label = _("Disabled:")),
3268  flag = wx.ALIGN_LEFT |
3269  wx.ALIGN_CENTER_VERTICAL,
3270  pos = (row, 0))
3271  rColor = csel.ColourSelect(parent = panel, id = wx.ID_ANY,
3272  colour = self.settings.Get(group='modeler', key='disabled', subkey='color'),
3273  size = globalvar.DIALOG_COLOR_SIZE)
3274  rColor.SetName('GetColour')
3275  self.winId['modeler:disabled:color'] = rColor.GetId()
3276 
3277  gridSizer.Add(item = rColor,
3278  flag = wx.ALIGN_RIGHT |
3279  wx.ALIGN_CENTER_VERTICAL,
3280  pos = (row, 1))
3281 
3282  sizer.Add(item = gridSizer, proportion = 1, flag = wx.ALL | wx.EXPAND, border = 5)
3283  border.Add(item = sizer, proportion = 0, flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
3284 
3285  panel.SetSizer(border)
3286 
3287  return panel
3288 
3289  def _createActionPage(self, notebook):
3290  """!Create notebook page for action settings"""
3291  panel = wx.Panel(parent = notebook, id = wx.ID_ANY)
3292  notebook.AddPage(page = panel, text = _("Action"))
3293 
3294  # colors
3295  border = wx.BoxSizer(wx.VERTICAL)
3296  box = wx.StaticBox (parent = panel, id = wx.ID_ANY,
3297  label = " %s " % _("Color"))
3298  sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
3299 
3300  gridSizer = wx.GridBagSizer (hgap = 3, vgap = 3)
3301  gridSizer.AddGrowableCol(0)
3302 
3303  row = 0
3304  gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
3305  label = _("Valid:")),
3306  flag = wx.ALIGN_LEFT |
3307  wx.ALIGN_CENTER_VERTICAL,
3308  pos = (row, 0))
3309  vColor = csel.ColourSelect(parent = panel, id = wx.ID_ANY,
3310  colour = self.settings.Get(group='modeler', key='action', subkey=('color', 'valid')),
3311  size = globalvar.DIALOG_COLOR_SIZE)
3312  vColor.SetName('GetColour')
3313  self.winId['modeler:action:color:valid'] = vColor.GetId()
3314 
3315  gridSizer.Add(item = vColor,
3316  flag = wx.ALIGN_RIGHT |
3317  wx.ALIGN_CENTER_VERTICAL,
3318  pos = (row, 1))
3319 
3320  row += 1
3321  gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
3322  label = _("Invalid:")),
3323  flag = wx.ALIGN_LEFT |
3324  wx.ALIGN_CENTER_VERTICAL,
3325  pos = (row, 0))
3326  iColor = csel.ColourSelect(parent = panel, id = wx.ID_ANY,
3327  colour = self.settings.Get(group='modeler', key='action', subkey=('color', 'invalid')),
3328  size = globalvar.DIALOG_COLOR_SIZE)
3329  iColor.SetName('GetColour')
3330  self.winId['modeler:action:color:invalid'] = iColor.GetId()
3331 
3332  gridSizer.Add(item = iColor,
3333  flag = wx.ALIGN_RIGHT |
3334  wx.ALIGN_CENTER_VERTICAL,
3335  pos = (row, 1))
3336 
3337  row += 1
3338  gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
3339  label = _("Running:")),
3340  flag = wx.ALIGN_LEFT |
3341  wx.ALIGN_CENTER_VERTICAL,
3342  pos = (row, 0))
3343  rColor = csel.ColourSelect(parent = panel, id = wx.ID_ANY,
3344  colour = self.settings.Get(group='modeler', key='action', subkey=('color', 'running')),
3345  size = globalvar.DIALOG_COLOR_SIZE)
3346  rColor.SetName('GetColour')
3347  self.winId['modeler:action:color:running'] = rColor.GetId()
3348 
3349  gridSizer.Add(item = rColor,
3350  flag = wx.ALIGN_RIGHT |
3351  wx.ALIGN_CENTER_VERTICAL,
3352  pos = (row, 1))
3353 
3354  sizer.Add(item = gridSizer, proportion = 1, flag = wx.ALL | wx.EXPAND, border = 5)
3355  border.Add(item = sizer, proportion = 0, flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
3356 
3357  # size
3358  box = wx.StaticBox (parent = panel, id = wx.ID_ANY,
3359  label = " %s " % _("Shape size"))
3360  sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
3361 
3362  gridSizer = wx.GridBagSizer (hgap=3, vgap=3)
3363  gridSizer.AddGrowableCol(0)
3364 
3365  row = 0
3366  gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
3367  label = _("Width:")),
3368  flag = wx.ALIGN_LEFT |
3369  wx.ALIGN_CENTER_VERTICAL,
3370  pos = (row, 0))
3371 
3372  width = wx.SpinCtrl(parent = panel, id = wx.ID_ANY,
3373  min = 0, max = 500,
3374  initial = self.settings.Get(group='modeler', key='action', subkey=('size', 'width')))
3375  width.SetName('GetValue')
3376  self.winId['modeler:action:size:width'] = width.GetId()
3377 
3378  gridSizer.Add(item = width,
3379  flag = wx.ALIGN_RIGHT |
3380  wx.ALIGN_CENTER_VERTICAL,
3381  pos = (row, 1))
3382 
3383  row += 1
3384  gridSizer.Add(item = wx.StaticText(parent=panel, id=wx.ID_ANY,
3385  label=_("Height:")),
3386  flag = wx.ALIGN_LEFT |
3387  wx.ALIGN_CENTER_VERTICAL,
3388  pos=(row, 0))
3389 
3390  height = wx.SpinCtrl(parent = panel, id = wx.ID_ANY,
3391  min = 0, max = 500,
3392  initial = self.settings.Get(group='modeler', key='action', subkey=('size', 'height')))
3393  height.SetName('GetValue')
3394  self.winId['modeler:action:size:height'] = height.GetId()
3395 
3396  gridSizer.Add(item = height,
3397  flag = wx.ALIGN_RIGHT |
3398  wx.ALIGN_CENTER_VERTICAL,
3399  pos = (row, 1))
3400 
3401  sizer.Add(item=gridSizer, proportion=1, flag=wx.ALL | wx.EXPAND, border=5)
3402  border.Add(item=sizer, proportion=0, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=3)
3403 
3404  panel.SetSizer(border)
3405 
3406  return panel
3407 
3408  def _createDataPage(self, notebook):
3409  """!Create notebook page for data settings"""
3410  panel = wx.Panel(parent = notebook, id = wx.ID_ANY)
3411  notebook.AddPage(page = panel, text = _("Data"))
3412 
3413  # colors
3414  border = wx.BoxSizer(wx.VERTICAL)
3415  box = wx.StaticBox (parent = panel, id = wx.ID_ANY,
3416  label = " %s " % _("Type"))
3417  sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
3418 
3419  gridSizer = wx.GridBagSizer (hgap = 3, vgap = 3)
3420  gridSizer.AddGrowableCol(0)
3421 
3422  row = 0
3423  gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
3424  label = _("Raster:")),
3425  flag = wx.ALIGN_LEFT |
3426  wx.ALIGN_CENTER_VERTICAL,
3427  pos = (row, 0))
3428  rColor = csel.ColourSelect(parent = panel, id = wx.ID_ANY,
3429  colour = self.settings.Get(group='modeler', key='data', subkey=('color', 'raster')),
3430  size = globalvar.DIALOG_COLOR_SIZE)
3431  rColor.SetName('GetColour')
3432  self.winId['modeler:data:color:raster'] = rColor.GetId()
3433 
3434  gridSizer.Add(item = rColor,
3435  flag = wx.ALIGN_RIGHT |
3436  wx.ALIGN_CENTER_VERTICAL,
3437  pos = (row, 1))
3438 
3439  row += 1
3440  gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
3441  label = _("3D raster:")),
3442  flag = wx.ALIGN_LEFT |
3443  wx.ALIGN_CENTER_VERTICAL,
3444  pos = (row, 0))
3445  r3Color = csel.ColourSelect(parent = panel, id = wx.ID_ANY,
3446  colour = self.settings.Get(group='modeler', key='data', subkey=('color', 'raster3d')),
3447  size = globalvar.DIALOG_COLOR_SIZE)
3448  r3Color.SetName('GetColour')
3449  self.winId['modeler:data:color:raster3d'] = r3Color.GetId()
3450 
3451  gridSizer.Add(item = r3Color,
3452  flag = wx.ALIGN_RIGHT |
3453  wx.ALIGN_CENTER_VERTICAL,
3454  pos = (row, 1))
3455 
3456  row += 1
3457  gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
3458  label = _("Vector:")),
3459  flag = wx.ALIGN_LEFT |
3460  wx.ALIGN_CENTER_VERTICAL,
3461  pos = (row, 0))
3462  vColor = csel.ColourSelect(parent = panel, id = wx.ID_ANY,
3463  colour = self.settings.Get(group='modeler', key='data', subkey=('color', 'vector')),
3464  size = globalvar.DIALOG_COLOR_SIZE)
3465  vColor.SetName('GetColour')
3466  self.winId['modeler:data:color:vector'] = vColor.GetId()
3467 
3468  gridSizer.Add(item = vColor,
3469  flag = wx.ALIGN_RIGHT |
3470  wx.ALIGN_CENTER_VERTICAL,
3471  pos = (row, 1))
3472 
3473  sizer.Add(item = gridSizer, proportion = 1, flag = wx.ALL | wx.EXPAND, border = 5)
3474  border.Add(item = sizer, proportion = 0, flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
3475 
3476  # size
3477  box = wx.StaticBox (parent = panel, id = wx.ID_ANY,
3478  label = " %s " % _("Shape size"))
3479  sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
3480 
3481  gridSizer = wx.GridBagSizer (hgap=3, vgap=3)
3482  gridSizer.AddGrowableCol(0)
3483 
3484  row = 0
3485  gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
3486  label = _("Width:")),
3487  flag = wx.ALIGN_LEFT |
3488  wx.ALIGN_CENTER_VERTICAL,
3489  pos = (row, 0))
3490 
3491  width = wx.SpinCtrl(parent = panel, id = wx.ID_ANY,
3492  min = 0, max = 500,
3493  initial = self.settings.Get(group='modeler', key='data', subkey=('size', 'width')))
3494  width.SetName('GetValue')
3495  self.winId['modeler:data:size:width'] = width.GetId()
3496 
3497  gridSizer.Add(item = width,
3498  flag = wx.ALIGN_RIGHT |
3499  wx.ALIGN_CENTER_VERTICAL,
3500  pos = (row, 1))
3501 
3502  row += 1
3503  gridSizer.Add(item = wx.StaticText(parent=panel, id=wx.ID_ANY,
3504  label=_("Height:")),
3505  flag = wx.ALIGN_LEFT |
3506  wx.ALIGN_CENTER_VERTICAL,
3507  pos=(row, 0))
3508 
3509  height = wx.SpinCtrl(parent = panel, id = wx.ID_ANY,
3510  min = 0, max = 500,
3511  initial = self.settings.Get(group='modeler', key='data', subkey=('size', 'height')))
3512  height.SetName('GetValue')
3513  self.winId['modeler:data:size:height'] = height.GetId()
3514 
3515  gridSizer.Add(item = height,
3516  flag = wx.ALIGN_RIGHT |
3517  wx.ALIGN_CENTER_VERTICAL,
3518  pos = (row, 1))
3519 
3520  sizer.Add(item=gridSizer, proportion=1, flag=wx.ALL | wx.EXPAND, border=5)
3521  border.Add(item=sizer, proportion=0, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=3)
3522 
3523  panel.SetSizer(border)
3524 
3525  return panel
3526 
3527  def _createLoopPage(self, notebook):
3528  """!Create notebook page for loop settings"""
3529  panel = wx.Panel(parent = notebook, id = wx.ID_ANY)
3530  notebook.AddPage(page = panel, text = _("Loop"))
3531 
3532  # colors
3533  border = wx.BoxSizer(wx.VERTICAL)
3534  box = wx.StaticBox (parent = panel, id = wx.ID_ANY,
3535  label = " %s " % _("Color"))
3536  sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
3537 
3538  gridSizer = wx.GridBagSizer (hgap = 3, vgap = 3)
3539  gridSizer.AddGrowableCol(0)
3540 
3541  row = 0
3542  gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
3543  label = _("Valid:")),
3544  flag = wx.ALIGN_LEFT |
3545  wx.ALIGN_CENTER_VERTICAL,
3546  pos = (row, 0))
3547  vColor = csel.ColourSelect(parent = panel, id = wx.ID_ANY,
3548  colour = self.settings.Get(group='modeler', key='loop', subkey=('color', 'valid')),
3549  size = globalvar.DIALOG_COLOR_SIZE)
3550  vColor.SetName('GetColour')
3551  self.winId['modeler:loop:color:valid'] = vColor.GetId()
3552 
3553  gridSizer.Add(item = vColor,
3554  flag = wx.ALIGN_RIGHT |
3555  wx.ALIGN_CENTER_VERTICAL,
3556  pos = (row, 1))
3557 
3558  sizer.Add(item = gridSizer, proportion = 1, flag = wx.ALL | wx.EXPAND, border = 5)
3559  border.Add(item = sizer, proportion = 0, flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
3560 
3561  # size
3562  box = wx.StaticBox (parent = panel, id = wx.ID_ANY,
3563  label = " %s " % _("Shape size"))
3564  sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
3565 
3566  gridSizer = wx.GridBagSizer (hgap=3, vgap=3)
3567  gridSizer.AddGrowableCol(0)
3568 
3569  row = 0
3570  gridSizer.Add(item = wx.StaticText(parent = panel, id = wx.ID_ANY,
3571  label = _("Width:")),
3572  flag = wx.ALIGN_LEFT |
3573  wx.ALIGN_CENTER_VERTICAL,
3574  pos = (row, 0))
3575 
3576  width = wx.SpinCtrl(parent = panel, id = wx.ID_ANY,
3577  min = 0, max = 500,
3578  initial = self.settings.Get(group='modeler', key='loop', subkey=('size', 'width')))
3579  width.SetName('GetValue')
3580  self.winId['modeler:loop:size:width'] = width.GetId()
3581 
3582  gridSizer.Add(item = width,
3583  flag = wx.ALIGN_RIGHT |
3584  wx.ALIGN_CENTER_VERTICAL,
3585  pos = (row, 1))
3586 
3587  row += 1
3588  gridSizer.Add(item = wx.StaticText(parent=panel, id=wx.ID_ANY,
3589  label=_("Height:")),
3590  flag = wx.ALIGN_LEFT |
3591  wx.ALIGN_CENTER_VERTICAL,
3592  pos=(row, 0))
3593 
3594  height = wx.SpinCtrl(parent = panel, id = wx.ID_ANY,
3595  min = 0, max = 500,
3596  initial = self.settings.Get(group='modeler', key='loop', subkey=('size', 'height')))
3597  height.SetName('GetValue')
3598  self.winId['modeler:loop:size:height'] = height.GetId()
3599 
3600  gridSizer.Add(item = height,
3601  flag = wx.ALIGN_RIGHT |
3602  wx.ALIGN_CENTER_VERTICAL,
3603  pos = (row, 1))
3604 
3605  sizer.Add(item=gridSizer, proportion=1, flag=wx.ALL | wx.EXPAND, border=5)
3606  border.Add(item=sizer, proportion=0, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=3)
3607 
3608  panel.SetSizer(border)
3609 
3610  return panel
3611 
3612  def OnApply(self, event):
3613  """!Button 'Apply' pressed"""
3614  PreferencesBaseDialog.OnApply(self, event)
3615 
3616  self.parent.GetModel().Update()
3617  self.parent.GetCanvas().Refresh()
3618 
3619  def OnSave(self, event):
3620  """!Button 'Save' pressed"""
3621  PreferencesBaseDialog.OnSave(self, event)
3622 
3623  self.parent.GetModel().Update()
3624  self.parent.GetCanvas().Refresh()
3625 
3626 class PropertiesDialog(wx.Dialog):
3627  """!Model properties dialog
3628  """
3629  def __init__(self, parent, id = wx.ID_ANY,
3630  title = _('Model properties'),
3631  size = (350, 400),
3632  style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER):
3633  wx.Dialog.__init__(self, parent, id, title, size = size,
3634  style = style)
3635 
3636  self.metaBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
3637  label=" %s " % _("Metadata"))
3638  self.cmdBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
3639  label=" %s " % _("Commands"))
3640 
3641  self.name = wx.TextCtrl(parent = self, id = wx.ID_ANY,
3642  size = (300, 25))
3643  self.desc = wx.TextCtrl(parent = self, id = wx.ID_ANY,
3644  style = wx.TE_MULTILINE,
3645  size = (300, 50))
3646  self.author = wx.TextCtrl(parent = self, id = wx.ID_ANY,
3647  size = (300, 25))
3648 
3649  # commands
3650  self.overwrite = wx.CheckBox(parent = self, id=wx.ID_ANY,
3651  label=_("Allow output files to overwrite existing files"))
3652  self.overwrite.SetValue(UserSettings.Get(group='cmd', key='overwrite', subkey='enabled'))
3653 
3654  # buttons
3655  self.btnOk = wx.Button(self, wx.ID_OK)
3656  self.btnCancel = wx.Button(self, wx.ID_CANCEL)
3657  self.btnOk.SetDefault()
3658 
3659  self.btnOk.SetToolTipString(_("Apply properties"))
3660  self.btnOk.SetDefault()
3661  self.btnCancel.SetToolTipString(_("Close dialog and ignore changes"))
3662 
3663  self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
3664 
3665  self._layout()
3666 
3667  def _layout(self):
3668  metaSizer = wx.StaticBoxSizer(self.metaBox, wx.VERTICAL)
3669  gridSizer = wx.GridBagSizer(hgap = 3, vgap = 3)
3670  gridSizer.AddGrowableCol(0)
3671  gridSizer.AddGrowableRow(1)
3672  gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
3673  label = _("Name:")),
3674  flag = wx.ALIGN_LEFT |
3675  wx.ALIGN_CENTER_VERTICAL,
3676  pos = (0, 0))
3677  gridSizer.Add(item = self.name,
3678  flag = wx.ALIGN_LEFT |
3679  wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
3680  pos = (0, 1))
3681  gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
3682  label = _("Description:")),
3683  flag = wx.ALIGN_LEFT |
3684  wx.ALIGN_CENTER_VERTICAL,
3685  pos = (1, 0))
3686  gridSizer.Add(item = self.desc,
3687  flag = wx.ALIGN_LEFT |
3688  wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
3689  pos = (1, 1))
3690  gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
3691  label = _("Author(s):")),
3692  flag = wx.ALIGN_LEFT |
3693  wx.ALIGN_CENTER_VERTICAL,
3694  pos = (2, 0))
3695  gridSizer.Add(item = self.author,
3696  flag = wx.ALIGN_LEFT |
3697  wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
3698  pos = (2, 1))
3699  metaSizer.Add(item = gridSizer)
3700 
3701  cmdSizer = wx.StaticBoxSizer(self.cmdBox, wx.VERTICAL)
3702  cmdSizer.Add(item = self.overwrite,
3703  flag = wx.EXPAND | wx.ALL, border = 3)
3704 
3705  btnStdSizer = wx.StdDialogButtonSizer()
3706  btnStdSizer.AddButton(self.btnCancel)
3707  btnStdSizer.AddButton(self.btnOk)
3708  btnStdSizer.Realize()
3709 
3710  mainSizer = wx.BoxSizer(wx.VERTICAL)
3711  mainSizer.Add(item=metaSizer, proportion=1,
3712  flag=wx.EXPAND | wx.ALL, border=5)
3713  mainSizer.Add(item=cmdSizer, proportion=0,
3714  flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5)
3715  mainSizer.Add(item=btnStdSizer, proportion=0,
3716  flag=wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT, border=5)
3717 
3718  self.SetSizer(mainSizer)
3719  mainSizer.Fit(self)
3720 
3721  def OnCloseWindow(self, event):
3722  self.Hide()
3723 
3724  def GetValues(self):
3725  """!Get values"""
3726  return { 'name' : self.name.GetValue(),
3727  'description' : self.desc.GetValue(),
3728  'author' : self.author.GetValue(),
3729  'overwrite' : self.overwrite.IsChecked() }
3730 
3731  def Init(self, prop):
3732  """!Initialize dialog"""
3733  self.name.SetValue(prop['name'])
3734  self.desc.SetValue(prop['description'])
3735  self.author.SetValue(prop['author'])
3736  if 'overwrite' in prop:
3737  self.overwrite.SetValue(prop['overwrite'])
3738 
3739 class ModelParamDialog(wx.Dialog):
3740  def __init__(self, parent, params, id = wx.ID_ANY, title = _("Model parameters"),
3741  style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
3742  """!Model parameters dialog
3743  """
3744  self.parent = parent
3745  self.params = params
3746  self.tasks = list() # list of tasks/pages
3747 
3748  wx.Dialog.__init__(self, parent = parent, id = id, title = title, style = style, **kwargs)
3749 
3750  if globalvar.hasAgw:
3751  self.notebook = FN.FlatNotebook(self, id = wx.ID_ANY,
3752  agwStyle = FN.FNB_FANCY_TABS |
3753  FN.FNB_BOTTOM |
3754  FN.FNB_NO_NAV_BUTTONS |
3755  FN.FNB_NO_X_BUTTON)
3756  else:
3757  self.notebook = FN.FlatNotebook(self, id = wx.ID_ANY,
3758  style = FN.FNB_FANCY_TABS |
3759  FN.FNB_BOTTOM |
3760  FN.FNB_NO_NAV_BUTTONS |
3761  FN.FNB_NO_X_BUTTON)
3762 
3763  panel = self._createPages()
3764  wx.CallAfter(self.notebook.SetSelection, 0)
3765 
3766  self.btnCancel = wx.Button(parent = self, id = wx.ID_CANCEL)
3767  self.btnRun = wx.Button(parent = self, id = wx.ID_OK,
3768  label = _("&Run"))
3769  self.btnRun.SetDefault()
3770 
3771  self._layout()
3772 
3773  size = self.GetBestSize()
3774  self.SetMinSize(size)
3775  self.SetSize((size.width, size.height +
3776  panel.constrained_size[1] -
3777  panel.panelMinHeight))
3778 
3779  def _layout(self):
3780  btnSizer = wx.StdDialogButtonSizer()
3781  btnSizer.AddButton(self.btnCancel)
3782  btnSizer.AddButton(self.btnRun)
3783  btnSizer.Realize()
3784 
3785  mainSizer = wx.BoxSizer(wx.VERTICAL)
3786  mainSizer.Add(item = self.notebook, proportion = 1,
3787  flag = wx.EXPAND)
3788  mainSizer.Add(item=btnSizer, proportion=0,
3789  flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)
3790 
3791  self.SetSizer(mainSizer)
3792  mainSizer.Fit(self)
3793 
3794  def _createPages(self):
3795  """!Create for each parameterized module its own page"""
3796  nameOrdered = [''] * len(self.params.keys())
3797  for name, params in self.params.iteritems():
3798  nameOrdered[params['idx']] = name
3799  for name in nameOrdered:
3800  params = self.params[name]
3801  panel = self._createPage(name, params)
3802  self.notebook.AddPage(panel, text = name)
3803 
3804  return panel
3805 
3806  def _createPage(self, name, params):
3807  """!Define notebook page"""
3808  if name in globalvar.grassCmd['all']:
3809  task = gtask.grassTask(name)
3810  else:
3811  task = gtask.grassTask()
3812  task.flags = params['flags']
3813  task.params = params['params']
3814 
3815  panel = menuform.cmdPanel(parent = self, id = wx.ID_ANY, task = task)
3816  self.tasks.append(task)
3817 
3818  return panel
3819 
3820  def GetErrors(self):
3821  """!Check for errors, get list of messages"""
3822  errList = list()
3823  for task in self.tasks:
3824  errList += task.getCmdError()
3825 
3826  return errList
3827 
3828 class ModelListCtrl(wx.ListCtrl,
3829  listmix.ListCtrlAutoWidthMixin,
3830  listmix.TextEditMixin,
3831  listmix.ColumnSorterMixin):
3832  def __init__(self, parent, columns, id = wx.ID_ANY,
3833  style = wx.LC_REPORT | wx.BORDER_NONE |
3834  wx.LC_SORT_ASCENDING |wx.LC_HRULES |
3835  wx.LC_VRULES, **kwargs):
3836  """!List of model variables"""
3837  self.parent = parent
3838  self.columns = columns
3839  self.shape = None
3840  try:
3841  self.frame = parent.parent
3842  except AttributeError:
3843  self.frame = None
3844 
3845  wx.ListCtrl.__init__(self, parent, id = id, style = style, **kwargs)
3846  listmix.ListCtrlAutoWidthMixin.__init__(self)
3847  listmix.TextEditMixin.__init__(self)
3848  listmix.ColumnSorterMixin.__init__(self, 4)
3849 
3850  i = 0
3851  for col in columns:
3852  self.InsertColumn(i, col)
3853  self.SetColumnWidth(i, wx.LIST_AUTOSIZE_USEHEADER)
3854  i += 1
3855 
3856  self.itemDataMap = {} # requested by sorter
3857  self.itemCount = 0
3858 
3859  self.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.OnBeginEdit)
3860  self.Bind(wx.EVT_LIST_END_LABEL_EDIT, self.OnEndEdit)
3861  self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick)
3862  self.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnRightUp) #wxMSW
3863  self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp) #wxGTK
3864 
3865  def OnBeginEdit(self, event):
3866  """!Editing of item started"""
3867  event.Allow()
3868 
3869  def OnEndEdit(self, event):
3870  """!Finish editing of item"""
3871  pass
3872 
3873  def OnColClick(self, event):
3874  """!Click on column header (order by)"""
3875  event.Skip()
3876 
3877 class VariablePanel(wx.Panel):
3878  def __init__(self, parent, id = wx.ID_ANY,
3879  **kwargs):
3880  """!Manage model variables panel
3881  """
3882  self.parent = parent
3883 
3884  wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
3885 
3886  self.listBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
3887  label=" %s " % _("List of variables - right-click to delete"))
3888 
3889  self.list = VariableListCtrl(parent = self,
3890  columns = [_("Name"), _("Data type"),
3891  _("Default value"), _("Description")])
3892 
3893  # add new category
3894  self.addBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
3895  label = " %s " % _("Add new variable"))
3896  self.name = wx.TextCtrl(parent = self, id = wx.ID_ANY)
3897  wx.CallAfter(self.name.SetFocus)
3898  self.type = wx.Choice(parent = self, id = wx.ID_ANY,
3899  choices = [_("integer"),
3900  _("float"),
3901  _("string"),
3902  _("raster"),
3903  _("vector")])
3904  self.value = wx.TextCtrl(parent = self, id = wx.ID_ANY)
3905  self.desc = wx.TextCtrl(parent = self, id = wx.ID_ANY)
3906 
3907  # buttons
3908  self.btnAdd = wx.Button(parent = self, id = wx.ID_ADD)
3909  self.btnAdd.SetToolTipString(_("Add new variable to the model"))
3910  self.btnAdd.Enable(False)
3911 
3912  # bindings
3913  self.name.Bind(wx.EVT_TEXT, self.OnText)
3914  self.value.Bind(wx.EVT_TEXT, self.OnText)
3915  self.desc.Bind(wx.EVT_TEXT, self.OnText)
3916  self.btnAdd.Bind(wx.EVT_BUTTON, self.OnAdd)
3917 
3918  self._layout()
3919 
3920  def _layout(self):
3921  """!Layout dialog"""
3922  listSizer = wx.StaticBoxSizer(self.listBox, wx.VERTICAL)
3923  listSizer.Add(item = self.list, proportion = 1,
3924  flag = wx.EXPAND)
3925 
3926  addSizer = wx.StaticBoxSizer(self.addBox, wx.VERTICAL)
3927  gridSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
3928  gridSizer.AddGrowableCol(1)
3929  gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
3930  label = "%s:" % _("Name")),
3931  flag = wx.ALIGN_CENTER_VERTICAL,
3932  pos = (0, 0))
3933  gridSizer.Add(item = self.name,
3934  pos = (0, 1),
3935  flag = wx.EXPAND)
3936  gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
3937  label = "%s:" % _("Data type")),
3938  flag = wx.ALIGN_CENTER_VERTICAL,
3939  pos = (0, 2))
3940  gridSizer.Add(item = self.type,
3941  pos = (0, 3))
3942  gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
3943  label = "%s:" % _("Default value")),
3944  flag = wx.ALIGN_CENTER_VERTICAL,
3945  pos = (1, 0))
3946  gridSizer.Add(item = self.value,
3947  pos = (1, 1), span = (1, 3),
3948  flag = wx.EXPAND)
3949  gridSizer.Add(item = wx.StaticText(parent = self, id = wx.ID_ANY,
3950  label = "%s:" % _("Description")),
3951  flag = wx.ALIGN_CENTER_VERTICAL,
3952  pos = (2, 0))
3953  gridSizer.Add(item = self.desc,
3954  pos = (2, 1), span = (1, 3),
3955  flag = wx.EXPAND)
3956  addSizer.Add(item = gridSizer,
3957  flag = wx.EXPAND)
3958  addSizer.Add(item = self.btnAdd, proportion = 0,
3959  flag = wx.TOP | wx.ALIGN_RIGHT, border = 5)
3960 
3961  mainSizer = wx.BoxSizer(wx.VERTICAL)
3962  mainSizer.Add(item = listSizer, proportion = 1,
3963  flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
3964  mainSizer.Add(item = addSizer, proportion = 0,
3965  flag = wx.EXPAND | wx.ALIGN_CENTER |
3966  wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
3967 
3968  self.SetSizer(mainSizer)
3969  mainSizer.Fit(self)
3970 
3971  def OnText(self, event):
3972  """!Text entered"""
3973  if self.name.GetValue():
3974  self.btnAdd.Enable()
3975  else:
3976  self.btnAdd.Enable(False)
3977 
3978  def OnAdd(self, event):
3979  """!Add new variable to the list"""
3980  msg = self.list.Append(self.name.GetValue(),
3981  self.type.GetStringSelection(),
3982  self.value.GetValue(),
3983  self.desc.GetValue())
3984  self.name.SetValue('')
3985  self.name.SetFocus()
3986 
3987  if msg:
3988  GError(parent = self,
3989  message = msg)
3990  else:
3991  self.type.SetSelection(0)
3992  self.value.SetValue('')
3993  self.desc.SetValue('')
3994  self.UpdateModelVariables()
3995 
3997  """!Update model variables"""
3998  variables = dict()
3999  for values in self.list.GetData().itervalues():
4000  name = values[0]
4001  variables[name] = { 'type' : str(values[1]) }
4002  if values[2]:
4003  variables[name]['value'] = values[2]
4004  if values[3]:
4005  variables[name]['description'] = values[3]
4006 
4007  self.parent.GetModel().SetVariables(variables)
4008  self.parent.ModelChanged()
4009 
4010  def Update(self):
4011  """!Reload list of variables"""
4012  self.list.OnReload(None)
4013 
4014  def Reset(self):
4015  """!Remove all variables"""
4016  self.list.DeleteAllItems()
4017  self.parent.GetModel().SetVariables([])
4018 
4020  def __init__(self, parent, columns, **kwargs):
4021  """!List of model variables"""
4022  ModelListCtrl.__init__(self, parent, columns, **kwargs)
4023 
4024  def GetListCtrl(self):
4025  """!Used by ColumnSorterMixin"""
4026  return self
4027 
4028  def GetData(self):
4029  """!Get list data"""
4030  return self.itemDataMap
4031 
4032  def Populate(self, data):
4033  """!Populate the list"""
4034  self.itemDataMap = dict()
4035  i = 0
4036  for name, values in data.iteritems():
4037  self.itemDataMap[i] = [name, values['type'],
4038  values.get('value', ''),
4039  values.get('description', '')]
4040  i += 1
4041 
4042  self.itemCount = len(self.itemDataMap.keys())
4043  self.DeleteAllItems()
4044  i = 0
4045  for name, vtype, value, desc in self.itemDataMap.itervalues():
4046  index = self.InsertStringItem(sys.maxint, name)
4047  self.SetStringItem(index, 0, name)
4048  self.SetStringItem(index, 1, vtype)
4049  self.SetStringItem(index, 2, value)
4050  self.SetStringItem(index, 3, desc)
4051  self.SetItemData(index, i)
4052  i += 1
4053 
4054  def Append(self, name, vtype, value, desc):
4055  """!Append new item to the list
4056 
4057  @return None on success
4058  @return error string
4059  """
4060  for iname, ivtype, ivalue, idesc in self.itemDataMap.itervalues():
4061  if iname == name:
4062  return _("Variable <%s> already exists in the model. "
4063  "Adding variable failed.") % name
4064 
4065  index = self.InsertStringItem(sys.maxint, name)
4066  self.SetStringItem(index, 0, name)
4067  self.SetStringItem(index, 1, vtype)
4068  self.SetStringItem(index, 2, value)
4069  self.SetStringItem(index, 3, desc)
4070  self.SetItemData(index, self.itemCount)
4071 
4072  self.itemDataMap[self.itemCount] = [name, vtype, value, desc]
4073  self.itemCount += 1
4074 
4075  return None
4076 
4077  def OnRemove(self, event):
4078  """!Remove selected variable(s) from the model"""
4079  item = self.GetFirstSelected()
4080  while item != -1:
4081  self.DeleteItem(item)
4082  del self.itemDataMap[item]
4083  item = self.GetFirstSelected()
4084  self.parent.UpdateModelVariables()
4085 
4086  event.Skip()
4087 
4088  def OnRemoveAll(self, event):
4089  """!Remove all variable(s) from the model"""
4090  dlg = wx.MessageBox(parent=self,
4091  message=_("Do you want to delete all variables from "
4092  "the model?"),
4093  caption=_("Delete variables"),
4094  style=wx.YES_NO | wx.CENTRE)
4095  if dlg != wx.YES:
4096  return
4097 
4098  self.DeleteAllItems()
4099  self.itemDataMap = dict()
4100 
4101  self.parent.UpdateModelVariables()
4102 
4103  def OnEndEdit(self, event):
4104  """!Finish editing of item"""
4105  itemIndex = event.GetIndex()
4106  columnIndex = event.GetColumn()
4107  nameOld = self.GetItem(itemIndex, 0).GetText()
4108 
4109  if columnIndex == 0: # TODO
4110  event.Veto()
4111 
4112  self.itemDataMap[itemIndex][columnIndex] = event.GetText()
4113 
4114  self.parent.UpdateModelVariables()
4115 
4116  def OnReload(self, event):
4117  """!Reload list of variables"""
4118  self.Populate(self.parent.parent.GetModel().GetVariables())
4119 
4120  def OnRightUp(self, event):
4121  """!Mouse right button up"""
4122  if not hasattr(self, "popupID1"):
4123  self.popupID1 = wx.NewId()
4124  self.popupID2 = wx.NewId()
4125  self.popupID3 = wx.NewId()
4126  self.Bind(wx.EVT_MENU, self.OnRemove, id = self.popupID1)
4127  self.Bind(wx.EVT_MENU, self.OnRemoveAll, id = self.popupID2)
4128  self.Bind(wx.EVT_MENU, self.OnReload, id = self.popupID3)
4129 
4130  # generate popup-menu
4131  menu = wx.Menu()
4132  menu.Append(self.popupID1, _("Delete selected"))
4133  menu.Append(self.popupID2, _("Delete all"))
4134  if self.GetFirstSelected() == -1:
4135  menu.Enable(self.popupID1, False)
4136  menu.Enable(self.popupID2, False)
4137 
4138  menu.AppendSeparator()
4139  menu.Append(self.popupID3, _("Reload"))
4140 
4141  self.PopupMenu(menu)
4142  menu.Destroy()
4143 
4145  def __init__(self, parent, x, y, id = -1, width = None, height = None, text = '', items = []):
4146  """!Abstract class for loops and conditions"""
4147  ModelObject.__init__(self, id)
4148  self.parent = parent
4149  self.text = text
4150  self.items = items # list of items in the loop
4151 
4152  def GetText(self):
4153  """!Get loop text"""
4154  return self.text
4155 
4156  def GetItems(self):
4157  """!Get items (id)"""
4158  return self.items
4159 
4160  def SetId(self, id):
4161  """!Set loop id"""
4162  self.id = id
4163 
4164  def SetText(self, cond):
4165  """!Set loop text (condition)"""
4166  self.text = cond
4167  self.ClearText()
4168  self.AddText('(' + str(self.id) + ') ' + self.text)
4169 
4170  def GetLog(self):
4171  """!Get log info"""
4172  if self.text:
4173  return _("Condition: ") + self.text
4174  else:
4175  return _("Condition: not defined")
4176 
4177  def AddRelation(self, rel):
4178  """!Record relation"""
4179  self.rels.append(rel)
4180 
4181  def Clear(self):
4182  """!Clear object, remove rels"""
4183  self.rels = list()
4184 
4185 class ModelItemDialog(wx.Dialog):
4186  """!Abstract item properties dialog"""
4187  def __init__(self, parent, shape, title, id = wx.ID_ANY,
4188  style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
4189  self.parent = parent
4190  self.shape = shape
4191 
4192  wx.Dialog.__init__(self, parent, id, title = title, style = style, **kwargs)
4193 
4194  self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
4195 
4196  self.condBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
4197  label=" %s " % _("Condition"))
4198  self.condText = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY,
4199  value = shape.GetText())
4200 
4201  self.itemList = ItemCheckListCtrl(parent = self.panel,
4202  window = self,
4203  columns = [_("ID"), _("Name"),
4204  _("Command")],
4205  shape = shape)
4206  self.itemList.Populate(self.parent.GetModel().GetItems())
4207 
4208  self.btnCancel = wx.Button(parent = self.panel, id = wx.ID_CANCEL)
4209  self.btnOk = wx.Button(parent = self.panel, id = wx.ID_OK)
4210  self.btnOk.SetDefault()
4211 
4212  def _layout(self):
4213  """!Do layout (virtual method)"""
4214  pass
4215 
4216  def GetCondition(self):
4217  """!Get loop condition"""
4218  return self.condText.GetValue()
4219 
4220 class ModelLoop(ModelItem, ogl.RectangleShape):
4221  def __init__(self, parent, x, y, id = -1, width = None, height = None, text = '', items = []):
4222  """!Defines a loop"""
4223  ModelItem.__init__(self, parent, x, y, id, width, height, text, items)
4224 
4225  if not width:
4226  width = UserSettings.Get(group='modeler', key='loop', subkey=('size', 'width'))
4227  if not height:
4228  height = UserSettings.Get(group='modeler', key='loop', subkey=('size', 'height'))
4229 
4230  if self.parent.GetCanvas():
4231  ogl.RectangleShape.__init__(self, width, height)
4232 
4233  self.SetCanvas(self.parent)
4234  self.SetX(x)
4235  self.SetY(y)
4236  self.SetPen(wx.BLACK_PEN)
4237  self.SetCornerRadius(100)
4238  if text:
4239  self.AddText('(' + str(self.id) + ') ' + text)
4240  else:
4241  self.AddText('(' + str(self.id) + ')')
4242 
4243  self._setBrush()
4244 
4245  def _setBrush(self):
4246  """!Set brush"""
4247  if not self.isEnabled:
4248  color = UserSettings.Get(group='modeler', key='disabled',
4249  subkey='color')
4250  else:
4251  color = UserSettings.Get(group='modeler', key='loop',
4252  subkey=('color', 'valid'))
4253 
4254  wxColor = wx.Color(color[0], color[1], color[2])
4255  self.SetBrush(wx.Brush(wxColor))
4256 
4257  def Enable(self, enabled = True):
4258  """!Enable/disable action"""
4259  for item in self.items:
4260  if not isinstance(item, ModelAction):
4261  continue
4262  item.Enable(enabled)
4263 
4264  ModelObject.Enable(self, enabled)
4265 
4266  def Update(self):
4267  self._setBrush()
4268 
4269  def GetName(self):
4270  """!Get name"""
4271  return _("loop")
4272 
4273  def SetItems(self, items):
4274  """!Set items (id)"""
4275  self.items = items
4276 
4278  """!Loop properties dialog"""
4279  def __init__(self, parent, shape, id = wx.ID_ANY, title = _("Loop properties"),
4280  style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
4281  ModelItemDialog.__init__(self, parent, shape, title,
4282  style = style, **kwargs)
4283 
4284  self.listBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
4285  label=" %s " % _("List of items in loop"))
4286 
4287  self._layout()
4288  self.SetMinSize(self.GetSize())
4289  self.SetSize((500, 400))
4290 
4291  def _layout(self):
4292  """!Do layout"""
4293  sizer = wx.BoxSizer(wx.VERTICAL)
4294 
4295  condSizer = wx.StaticBoxSizer(self.condBox, wx.VERTICAL)
4296  condSizer.Add(item = self.condText, proportion = 1,
4297  flag = wx.EXPAND)
4298 
4299  listSizer = wx.StaticBoxSizer(self.listBox, wx.VERTICAL)
4300  listSizer.Add(item = self.itemList, proportion = 1,
4301  flag = wx.EXPAND)
4302 
4303  btnSizer = wx.StdDialogButtonSizer()
4304  btnSizer.AddButton(self.btnCancel)
4305  btnSizer.AddButton(self.btnOk)
4306  btnSizer.Realize()
4307 
4308  sizer.Add(item = condSizer, proportion = 0,
4309  flag = wx.EXPAND | wx.ALL, border = 3)
4310  sizer.Add(item = listSizer, proportion = 1,
4311  flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 3)
4312  sizer.Add(item = btnSizer, proportion=0,
4313  flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)
4314 
4315  self.panel.SetSizer(sizer)
4316  sizer.Fit(self.panel)
4317 
4318  self.Layout()
4319 
4320  def GetItems(self):
4321  """!Get list of selected actions"""
4322  return self.itemList.GetItems()
4323 
4324 class ItemPanel(wx.Panel):
4325  def __init__(self, parent, id = wx.ID_ANY,
4326  **kwargs):
4327  """!Manage model items
4328  """
4329  self.parent = parent
4330 
4331  wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
4332 
4333  self.listBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
4334  label=" %s " % _("List of items - right-click to delete"))
4335 
4336  self.list = ItemListCtrl(parent = self,
4337  columns = [_("ID"), _("Name"), _("In block"),
4338  _("Command / Condition")])
4339 
4340  self._layout()
4341 
4342  def _layout(self):
4343  """!Layout dialog"""
4344  listSizer = wx.StaticBoxSizer(self.listBox, wx.VERTICAL)
4345  listSizer.Add(item = self.list, proportion = 1,
4346  flag = wx.EXPAND)
4347 
4348  mainSizer = wx.BoxSizer(wx.VERTICAL)
4349  mainSizer.Add(item = listSizer, proportion = 1,
4350  flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
4351 
4352  self.SetSizer(mainSizer)
4353  mainSizer.Fit(self)
4354 
4355  def Update(self):
4356  """!Reload list of variables"""
4357  self.list.OnReload(None)
4358 
4360  def __init__(self, parent, columns, disablePopup = False, **kwargs):
4361  """!List of model actions"""
4362  self.disablePopup = disablePopup
4363 
4364  ModelListCtrl.__init__(self, parent, columns, **kwargs)
4365  self.SetColumnWidth(1, 100)
4366  self.SetColumnWidth(2, 65)
4367 
4368  def GetListCtrl(self):
4369  """!Used by ColumnSorterMixin"""
4370  return self
4371 
4372  def GetData(self):
4373  """!Get list data"""
4374  return self.itemDataMap
4375 
4376  def Populate(self, data):
4377  """!Populate the list"""
4378  self.itemDataMap = dict()
4379 
4380  if self.shape:
4381  if isinstance(self.shape, ModelCondition):
4382  if self.GetName() == 'ElseBlockList':
4383  shapeItems = map(lambda x: x.GetId(), self.shape.GetItems()['else'])
4384  else:
4385  shapeItems = map(lambda x: x.GetId(), self.shape.GetItems()['if'])
4386  else:
4387  shapeItems = map(lambda x: x.GetId(), self.shape.GetItems())
4388  else:
4389  shapeItems = list()
4390  i = 0
4391  if len(self.columns) == 3: # ItemCheckList
4392  checked = list()
4393  for action in data:
4394  if isinstance(action, ModelData):
4395  continue
4396 
4397  if len(self.columns) == 3:
4398  self.itemDataMap[i] = [str(action.GetId()),
4399  action.GetName(),
4400  action.GetLog()]
4401  aId = action.GetBlockId()
4402  if action.GetId() in shapeItems:
4403  checked.append(aId)
4404  else:
4405  checked.append(None)
4406  else:
4407  bId = action.GetBlockId()
4408  if not bId:
4409  bId = ''
4410  self.itemDataMap[i] = [str(action.GetId()),
4411  action.GetName(),
4412  ','.join(map(str, bId)),
4413  action.GetLog()]
4414 
4415  i += 1
4416 
4417  self.itemCount = len(self.itemDataMap.keys())
4418  self.DeleteAllItems()
4419  i = 0
4420  if len(self.columns) == 3:
4421  for aid, name, desc in self.itemDataMap.itervalues():
4422  index = self.InsertStringItem(sys.maxint, aid)
4423  self.SetStringItem(index, 0, aid)
4424  self.SetStringItem(index, 1, name)
4425  self.SetStringItem(index, 2, desc)
4426  self.SetItemData(index, i)
4427  if checked[i]:
4428  self.CheckItem(index, True)
4429  i += 1
4430  else:
4431  for aid, name, inloop, desc in self.itemDataMap.itervalues():
4432  index = self.InsertStringItem(sys.maxint, aid)
4433  self.SetStringItem(index, 0, aid)
4434  self.SetStringItem(index, 1, name)
4435  self.SetStringItem(index, 2, inloop)
4436  self.SetStringItem(index, 3, desc)
4437  self.SetItemData(index, i)
4438  i += 1
4439 
4440  def OnRemove(self, event):
4441  """!Remove selected action(s) from the model"""
4442  model = self.frame.GetModel()
4443  canvas = self.frame.GetCanvas()
4444 
4445  item = self.GetFirstSelected()
4446  while item != -1:
4447  self.DeleteItem(item)
4448  del self.itemDataMap[item]
4449 
4450  aId = self.GetItem(item, 0).GetText()
4451  action = model.GetItem(int(aId))
4452  if not action:
4453  item = self.GetFirstSelected()
4454  continue
4455 
4456  model.RemoveItem(action)
4457  canvas.GetDiagram().RemoveShape(action)
4458  self.frame.ModelChanged()
4459 
4460  item = self.GetFirstSelected()
4461 
4462  canvas.Refresh()
4463 
4464  event.Skip()
4465 
4466  def OnRemoveAll(self, event):
4467  """!Remove all variable(s) from the model"""
4468  deleteDialog = wx.MessageBox(parent=self,
4469  message=_("Selected data records (%d) will permanently deleted "
4470  "from table. Do you want to delete them?") % \
4471  (len(self.listOfSQLStatements)),
4472  caption=_("Delete records"),
4473  style=wx.YES_NO | wx.CENTRE)
4474  if deleteDialog != wx.YES:
4475  return False
4476 
4477  self.DeleteAllItems()
4478  self.itemDataMap = dict()
4479 
4480  self.parent.UpdateModelVariables()
4481 
4482  def OnEndEdit(self, event):
4483  """!Finish editing of item"""
4484  itemIndex = event.GetIndex()
4485  columnIndex = event.GetColumn()
4486 
4487  self.itemDataMap[itemIndex][columnIndex] = event.GetText()
4488 
4489  aId = int(self.GetItem(itemIndex, 0).GetText())
4490  action = self.frame.GetModel().GetItem(aId)
4491  if not action:
4492  event.Veto()
4493  if columnIndex == 0:
4494  action.SetId(int(event.GetText()))
4495 
4496  self.frame.ModelChanged()
4497 
4498  def OnReload(self, event = None):
4499  """!Reload list of actions"""
4500  self.Populate(self.frame.GetModel().GetItems())
4501 
4502  def OnRightUp(self, event):
4503  """!Mouse right button up"""
4504  if self.disablePopup:
4505  return
4506 
4507  if not hasattr(self, "popupID1"):
4508  self.popupID1 = wx.NewId()
4509  self.popupID2 = wx.NewId()
4510  self.popupID3 = wx.NewId()
4511  self.popupID4 = wx.NewId()
4512  self.Bind(wx.EVT_MENU, self.OnRemove, id = self.popupID1)
4513  self.Bind(wx.EVT_MENU, self.OnRemoveAll, id = self.popupID2)
4514  self.Bind(wx.EVT_MENU, self.OnReload, id = self.popupID3)
4515  self.Bind(wx.EVT_MENU, self.OnNormalize, id = self.popupID4)
4516 
4517  # generate popup-menu
4518  menu = wx.Menu()
4519  menu.Append(self.popupID1, _("Delete selected"))
4520  menu.Append(self.popupID2, _("Delete all"))
4521  if self.GetFirstSelected() == -1:
4522  menu.Enable(self.popupID1, False)
4523  menu.Enable(self.popupID2, False)
4524 
4525  menu.AppendSeparator()
4526  menu.Append(self.popupID4, _("Normalize"))
4527  menu.Append(self.popupID3, _("Reload"))
4528 
4529  self.PopupMenu(menu)
4530  menu.Destroy()
4531 
4532  def OnNormalize(self, event):
4533  """!Update id of actions"""
4534  model = self.frame.GetModel()
4535 
4536  aId = 1
4537  for item in model.GetItems():
4538  item.SetId(aId)
4539  aId += 1
4540 
4541  self.OnReload(None)
4542  self.frame.GetCanvas().Refresh()
4543  self.frame.ModelChanged()
4544 
4545 class ItemCheckListCtrl(ItemListCtrl, listmix.CheckListCtrlMixin):
4546  def __init__(self, parent, shape, columns, window = None, **kwargs):
4547  self.parent = parent
4548  self.window = window
4549 
4550  ItemListCtrl.__init__(self, parent, columns, disablePopup = True, **kwargs)
4551  listmix.CheckListCtrlMixin.__init__(self)
4552  self.SetColumnWidth(0, 50)
4553 
4554  self.shape = shape
4555 
4556  def OnBeginEdit(self, event):
4557  """!Disable editing"""
4558  event.Veto()
4559 
4560  def OnCheckItem(self, index, flag):
4561  """!Item checked/unchecked"""
4562  name = self.GetName()
4563  if name == 'IfBlockList' and self.window:
4564  self.window.OnCheckItemIf(index, flag)
4565  elif name == 'ElseBlockList' and self.window:
4566  self.window.OnCheckItemElse(index, flag)
4567 
4568  def GetItems(self):
4569  """!Get list of selected actions"""
4570  ids = { 'checked' : list(),
4571  'unchecked' : list() }
4572  for i in range(self.GetItemCount()):
4573  iId = int(self.GetItem(i, 0).GetText())
4574  if self.IsChecked(i):
4575  ids['checked'].append(iId)
4576  else:
4577  ids['unchecked'].append(iId)
4578 
4579  return ids
4580 
4581  def CheckItemById(self, aId, flag):
4582  """!Check/uncheck given item by id"""
4583  for i in range(self.GetItemCount()):
4584  iId = int(self.GetItem(i, 0).GetText())
4585  if iId == aId:
4586  self.CheckItem(i, flag)
4587  break
4588 
4589 class ModelCondition(ModelItem, ogl.PolygonShape):
4590  def __init__(self, parent, x, y, id = -1, width = None, height = None, text = '',
4591  items = { 'if' : [], 'else' : [] }):
4592  """!Defines a if-else condition"""
4593  ModelItem.__init__(self, parent, x, y, id, width, height, text, items)
4594 
4595  if not width:
4596  self.width = UserSettings.Get(group='modeler', key='if-else', subkey=('size', 'width'))
4597  else:
4598  self.width = width
4599  if not height:
4600  self.height = UserSettings.Get(group='modeler', key='if-else', subkey=('size', 'height'))
4601  else:
4602  self.height = height
4603 
4604  if self.parent.GetCanvas():
4605  ogl.PolygonShape.__init__(self)
4606 
4607  points = [(0, - self.height / 2),
4608  (self.width / 2, 0),
4609  (0, self.height / 2),
4610  (- self.width / 2, 0)]
4611  self.Create(points)
4612 
4613  self.SetCanvas(self.parent)
4614  self.SetX(x)
4615  self.SetY(y)
4616  self.SetPen(wx.BLACK_PEN)
4617  if text:
4618  self.AddText('(' + str(self.id) + ') ' + text)
4619  else:
4620  self.AddText('(' + str(self.id) + ')')
4621 
4622  def GetName(self):
4623  """!Get name"""
4624  return _("if-else")
4625 
4626  def GetWidth(self):
4627  """!Get object width"""
4628  return self.width
4629 
4630  def GetHeight(self):
4631  """!Get object height"""
4632  return self.height
4633 
4634  def SetItems(self, items, branch = 'if'):
4635  """!Set items (id)
4636 
4637  @param items list of items
4638  @param branch 'if' / 'else'
4639  """
4640  if branch in ['if', 'else']:
4641  self.items[branch] = items
4642 
4644  """!Condition properties dialog"""
4645  def __init__(self, parent, shape, id = wx.ID_ANY, title = _("If-else properties"),
4646  style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
4647  ModelItemDialog.__init__(self, parent, shape, title,
4648  style = style, **kwargs)
4649 
4650  self.listBoxIf = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
4651  label=" %s " % _("List of items in 'if' block"))
4652  self.itemListIf = self.itemList
4653  self.itemListIf.SetName('IfBlockList')
4654 
4655  self.listBoxElse = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
4656  label=" %s " % _("List of items in 'else' block"))
4657  self.itemListElse = ItemCheckListCtrl(parent = self.panel,
4658  window = self,
4659  columns = [_("ID"), _("Name"),
4660  _("Command")],
4661  shape = shape)
4662  self.itemListElse.SetName('ElseBlockList')
4663  self.itemListElse.Populate(self.parent.GetModel().GetItems())
4664 
4665  self._layout()
4666  self.SetMinSize(self.GetSize())
4667  self.SetSize((500, 400))
4668 
4669  def _layout(self):
4670  """!Do layout"""
4671  sizer = wx.BoxSizer(wx.VERTICAL)
4672 
4673  condSizer = wx.StaticBoxSizer(self.condBox, wx.VERTICAL)
4674  condSizer.Add(item = self.condText, proportion = 1,
4675  flag = wx.EXPAND)
4676 
4677  listIfSizer = wx.StaticBoxSizer(self.listBoxIf, wx.VERTICAL)
4678  listIfSizer.Add(item = self.itemListIf, proportion = 1,
4679  flag = wx.EXPAND)
4680  listElseSizer = wx.StaticBoxSizer(self.listBoxElse, wx.VERTICAL)
4681  listElseSizer.Add(item = self.itemListElse, proportion = 1,
4682  flag = wx.EXPAND)
4683 
4684  btnSizer = wx.StdDialogButtonSizer()
4685  btnSizer.AddButton(self.btnCancel)
4686  btnSizer.AddButton(self.btnOk)
4687  btnSizer.Realize()
4688 
4689  sizer.Add(item = condSizer, proportion = 0,
4690  flag = wx.EXPAND | wx.ALL, border = 3)
4691  sizer.Add(item = listIfSizer, proportion = 1,
4692  flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 3)
4693  sizer.Add(item = listElseSizer, proportion = 1,
4694  flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 3)
4695  sizer.Add(item = btnSizer, proportion=0,
4696  flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)
4697 
4698  self.panel.SetSizer(sizer)
4699  sizer.Fit(self.panel)
4700 
4701  self.Layout()
4702 
4703  def OnCheckItemIf(self, index, flag):
4704  """!Item in if-block checked/unchecked"""
4705  if flag is False:
4706  return
4707 
4708  aId = int(self.itemListIf.GetItem(index, 0).GetText())
4709  if aId in self.itemListElse.GetItems()['checked']:
4710  self.itemListElse.CheckItemById(aId, False)
4711 
4712  def OnCheckItemElse(self, index, flag):
4713  """!Item in else-block checked/unchecked"""
4714  if flag is False:
4715  return
4716 
4717  aId = int(self.itemListElse.GetItem(index, 0).GetText())
4718  if aId in self.itemListIf.GetItems()['checked']:
4719  self.itemListIf.CheckItemById(aId, False)
4720 
4721  def GetItems(self):
4722  """!Get items"""
4723  return { 'if' : self.itemListIf.GetItems(),
4724  'else' : self.itemListElse.GetItems() }
4725 
4727  def __init__(self, fd, model):
4728  """!Class for exporting model to Python script
4729 
4730  @param fd file desciptor
4731  """
4732  self.fd = fd
4733  self.model = model
4734  self.indent = 4
4735 
4736  self._writePython()
4737 
4738  def _writePython(self):
4739  """!Write model to file"""
4740  properties = self.model.GetProperties()
4741 
4742  self.fd.write(
4743 r"""#!/usr/bin/env python
4744 #
4745 ############################################################################
4746 #
4747 # MODULE: %s
4748 #
4749 # AUTHOR(S): %s
4750 #
4751 # PURPOSE: %s
4752 #
4753 # DATE: %s
4754 #
4755 #############################################################################
4756 """ % (properties['name'],
4757  properties['author'],
4758  properties['description'],
4759  time.asctime()))
4760 
4761  self.fd.write(
4762 r"""
4763 import sys
4764 import os
4765 import atexit
4766 
4767 import grass.script as grass
4768 """)
4769 
4770  # cleanup()
4771  rast, vect, rast3d, msg = self.model.GetIntermediateData()
4772  self.fd.write(
4773 r"""
4774 def cleanup():
4775 """)
4776  if rast:
4777  self.fd.write(
4778 r""" grass.run_command('g.remove',
4779  rast=%s)
4780 """ % ','.join(map(lambda x: "'" + x + "'", rast)))
4781  if vect:
4782  self.fd.write(
4783 r""" grass.run_command('g.remove',
4784  vect = %s)
4785 """ % ','.join(map(lambda x: "'" + x + "'", vect)))
4786  if rast3d:
4787  self.fd.write(
4788 r""" grass.run_command('g.remove',
4789  rast3d = %s)
4790 """ % ','.join(map(lambda x: "'" + x + "'", rast3d)))
4791  if not rast and not vect and not rast3d:
4792  self.fd.write(' pass\n')
4793 
4794  self.fd.write("\ndef main():\n")
4795  for item in self.model.GetItems():
4796  self._writePythonItem(item)
4797 
4798  self.fd.write("\n return 0\n")
4799 
4800  self.fd.write(
4801 r"""
4802 if __name__ == "__main__":
4803  options, flags = grass.parser()
4804  atexit.register(cleanup)
4805  sys.exit(main())
4806 """)
4807 
4808  def _writePythonItem(self, item, ignoreBlock = True):
4809  """!Write model object to Python file"""
4810  if isinstance(item, ModelAction):
4811  if ignoreBlock and item.GetBlockId(): # ignore items in loops of conditions
4812  return
4813  self._writePythonAction(item)
4814  elif isinstance(item, ModelLoop) or isinstance(item, ModelCondition):
4815  # substitute condition
4816  variables = self.model.GetVariables()
4817  cond = item.GetText()
4818  for variable in variables:
4819  pattern= re.compile('%' + variable)
4820  if pattern.search(cond):
4821  value = variables[variable].get('value', '')
4822  if variables[variable].get('type', 'string') == 'string':
4823  value = '"' + value + '"'
4824  cond = pattern.sub(value, cond)
4825  if isinstance(item, ModelLoop):
4826  self.fd.write('%sfor %s:\n' % (' ' * self.indent, cond))
4827  self.indent += 4
4828  for action in item.GetItems():
4829  self._writePythonItem(action, ignoreBlock = False)
4830  self.indent -= 4
4831  else: # ModelCondition
4832  self.fd.write('%sif %s:\n' % (' ' * self.indent, cond))
4833  self.indent += 4
4834  condItems = item.GetItems()
4835  for action in condItems['if']:
4836  self._writePythonItem(action, ignoreBlock = False)
4837  if condItems['else']:
4838  self.indent -= 4
4839  self.fd.write('%selse:\n' % (' ' * self.indent))
4840  self.indent += 4
4841  for action in condItems['else']:
4842  self._writePythonItem(action, ignoreBlock = False)
4843  self.indent += 4
4844 
4845  def _writePythonAction(self, item):
4846  """!Write model action to Python file"""
4847  task = menuform.GUI(show = None).ParseCommand(cmd = item.GetLog(string = False))
4848  opts = task.get_options()
4849  flags = ''
4850  params = list()
4851  strcmd = "%sgrass.run_command(" % (' ' * self.indent)
4852  cmdIndent = len(strcmd)
4853  for f in opts['flags']:
4854  if f.get('value', False) == True:
4855  name = f.get('name', '')
4856  if len(name) > 1:
4857  params.append('%s = True' % name)
4858  else:
4859  flags += name
4860 
4861  for p in opts['params']:
4862  name = p.get('name', None)
4863  value = p.get('value', None)
4864  if name and value:
4865  ptype = p.get('type', 'string')
4866  if ptype == 'string':
4867  params.append('%s = "%s"' % (name, value))
4868  else:
4869  params.append("%s = %s" % (name, value))
4870 
4871  self.fd.write(strcmd + '"%s"' % task.get_name())
4872  if flags:
4873  self.fd.write(",\n%sflags = '%s'" % (' ' * cmdIndent, flags))
4874  if len(params) > 0:
4875  self.fd.write(",\n")
4876  for opt in params[:-1]:
4877  self.fd.write("%s%s,\n" % (' ' * cmdIndent, opt))
4878  self.fd.write("%s%s)\n" % (' ' * cmdIndent, params[-1]))
4879  else:
4880  self.fd.write(")\n")
4881 
4882 
4883 def main():
4884  app = wx.PySimpleApp()
4885  wx.InitAllImageHandlers()
4886  frame = ModelFrame(parent = None)
4887  if len(sys.argv) > 1:
4888  frame.LoadModelFile(sys.argv[1])
4889  frame.Show()
4890 
4891  app.MainLoop()
4892 
4893 if __name__ == "__main__":
4894  main()