GRASS Programmer's Manual  6.5.svn(2014)-r66266
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
forms.py
Go to the documentation of this file.
1 """
2 @package gui_core.forms
3 
4 @brief Construct simple wxPython GUI from a GRASS command interface
5 description.
6 
7 Classes:
8  - forms::UpdateThread
9  - forms::UpdateQThread
10  - forms::TaskFrame
11  - forms::CmdPanel
12  - forms::GUI
13  - forms::GrassGUIApp
14 
15 This program is just a coarse approach to automatically build a GUI
16 from a xml-based GRASS user interface description.
17 
18 You need to have Python 2.4, wxPython 2.8 and python-xml.
19 
20 The XML stream is read from executing the command given in the
21 command line, thus you may call it for instance this way:
22 
23 python <this file.py> r.basins.fill
24 
25 Or you set an alias or wrap the call up in a nice shell script, GUI
26 environment ... please contribute your idea.
27 
28 Updated to wxPython 2.8 syntax and contrib widgets. Methods added to
29 make it callable by gui. Method added to automatically re-run with
30 pythonw on a Mac.
31 
32 @todo
33  - verify option value types
34 
35 Copyright(C) 2000-2012 by the GRASS Development Team
36 
37 This program is free software under the GPL(>=v2) Read the file
38 COPYING coming with GRASS for details.
39 
40 @author Jan-Oliver Wagner <jan@intevation.de>
41 @author Bernhard Reiter <bernhard@intevation.de>
42 @author Michael Barton, Arizona State University
43 @author Daniel Calvelo <dca.gis@gmail.com>
44 @author Martin Landa <landa.martin@gmail.com>
45 @author Luca Delucchi <lucadeluge@gmail.com>
46 """
47 
48 import sys
49 import string
50 import textwrap
51 import os
52 import time
53 import copy
54 import locale
55 from threading import Thread
56 import Queue
57 import re
58 
59 gisbase = os.getenv("GISBASE")
60 if gisbase is None:
61  print >>sys.stderr, "We don't seem to be properly installed, or we are being run outside GRASS. Expect glitches."
62  gisbase = os.path.join(os.path.dirname(sys.argv[0]), os.path.pardir)
63  wxbase = gisbase
64 else:
65  wxbase = os.path.join(gisbase, 'etc', 'wxpython')
66 
67 sys.path.append(wxbase)
68 
69 from core import globalvar
70 import wx
71 try:
72  import wx.lib.agw.flatnotebook as FN
73 except ImportError:
74  import wx.lib.flatnotebook as FN
75 import wx.lib.colourselect as csel
76 import wx.lib.filebrowsebutton as filebrowse
77 from wx.lib.newevent import NewEvent
78 
79 try:
80  import xml.etree.ElementTree as etree
81 except ImportError:
82  import elementtree.ElementTree as etree # Python <= 2.4
83 
84 from grass.script import core as grass
85 from grass.script import task as gtask
86 
87 from gui_core.widgets import StaticWrapText, ScrolledPanel
88 from gui_core.ghelp import HelpPanel
89 from gui_core import gselect
90 from core import gcmd
91 from core import utils
92 from core.settings import UserSettings
93 from gui_core.widgets import FloatValidator, GNotebook
94 
95 wxUpdateDialog, EVT_DIALOG_UPDATE = NewEvent()
96 
97 # From lib/gis/col_str.c, except purple which is mentioned
98 # there but not given RGB values
99 str2rgb = {'aqua': (100, 128, 255),
100  'black': (0, 0, 0),
101  'blue': (0, 0, 255),
102  'brown': (180, 77, 25),
103  'cyan': (0, 255, 255),
104  'gray': (128, 128, 128),
105  'green': (0, 255, 0),
106  'grey': (128, 128, 128),
107  'indigo': (0, 128, 255),
108  'magenta': (255, 0, 255),
109  'orange': (255, 128, 0),
110  'purple': (128, 0, 128),
111  'red': (255, 0, 0),
112  'violet': (128, 0, 255),
113  'white': (255, 255, 255),
114  'yellow': (255, 255, 0)}
115 rgb2str = {}
116 for (s,r) in str2rgb.items():
117  rgb2str[ r ] = s
118 
119 """!Hide some options in the GUI"""
120 _blackList = { 'enabled' : False,
121  'items' : { 'd.legend' : { 'flags' : ['m'] } }
122  }
123 
124 def color_resolve(color):
125  if len(color) > 0 and color[0] in "0123456789":
126  rgb = tuple(map(int, color.split(':')))
127  label = color
128  else:
129  # Convert color names to RGB
130  try:
131  rgb = str2rgb[ color ]
132  label = color
133  except KeyError:
134  rgb = (200,200,200)
135  label = _('Select Color')
136  return (rgb, label)
137 
138 def text_beautify(someString , width = 70):
139  """
140  Make really long texts shorter, clean up whitespace and
141  remove trailing punctuation.
142  """
143  if width > 0:
144  return escape_ampersand(string.strip(
145  os.linesep.join(textwrap.wrap(utils.normalize_whitespace(someString), width)),
146  ".,;:"))
147  else:
148  return escape_ampersand(string.strip(utils.normalize_whitespace(someString), ".,;:"))
149 
151  """!Escapes ampersands with additional ampersand for GUI"""
152  return string.replace(text, "&", "&&")
153 
154 class UpdateThread(Thread):
155  """!Update dialog widgets in the thread"""
156  def __init__(self, parent, event, eventId, task):
157  Thread.__init__(self)
158 
159  self.parent = parent
160  self.event = event
161  self.eventId = eventId
162  self.task = task
163  self.setDaemon(True)
164 
165  # list of functions which updates the dialog
166  self.data = {}
167 
168  def run(self):
169  # get widget id
170  if not self.eventId:
171  for p in self.task.params:
172  if p.get('gisprompt', False) == False:
173  continue
174  prompt = p.get('element', '')
175  if prompt == 'vector':
176  name = p.get('name', '')
177  if name in ('map', 'input'):
178  self.eventId = p['wxId'][0]
179  if self.eventId is None:
180  return
181 
182  p = self.task.get_param(self.eventId, element = 'wxId', raiseError = False)
183  if not p or 'wxId-bind' not in p:
184  return
185 
186  # get widget prompt
187  pType = p.get('prompt', '')
188  if not pType:
189  return
190 
191  # check for map/input parameter
192  pMap = self.task.get_param('map', raiseError = False)
193 
194  if not pMap:
195  pMap = self.task.get_param('input', raiseError = False)
196 
197  if pMap:
198  map = pMap.get('value', '')
199  else:
200  map = None
201 
202  # avoid running db.describe several times
203  cparams = dict()
204  cparams[map] = { 'dbInfo' : None,
205  'layers' : None, }
206 
207  # update reference widgets
208  for uid in p['wxId-bind']:
209  win = self.parent.FindWindowById(uid)
210  if not win:
211  continue
212 
213  name = win.GetName()
214  pBind = self.task.get_param(uid, element = 'wxId', raiseError = False)
215  if pBind:
216  pBind['value'] = ''
217 
218  if name == 'LayerSelect':
219  if map in cparams and not cparams[map]['layers']:
220  win.InsertLayers(vector = map)
221  win.Reset()
222  cparams[map]['layers'] = win.GetItems()
223 
224  elif name == 'TableSelect':
225  pDriver = self.task.get_param('dbdriver', element='prompt', raiseError=False)
226  driver = db = None
227  if pDriver:
228  driver = pDriver['value']
229  pDb = self.task.get_param('dbname', element='prompt', raiseError=False)
230  if pDb:
231  db = pDb['value']
232 
233  self.data[win.InsertTables] = { 'driver' : driver,
234  'database' : db }
235 
236  elif name == 'ColumnSelect':
237  pLayer = self.task.get_param('layer', element='element', raiseError=False)
238  if pLayer:
239  if pLayer.get('value', '') != '':
240  layer = pLayer.get('value', '')
241  else:
242  layer = pLayer.get('default', '')
243  else:
244  layer = 1
245 
246  if map:
247  if map in cparams:
248  if not cparams[map]['dbInfo']:
249  cparams[map]['dbInfo'] = gselect.VectorDBInfo(map)
250  self.data[win.InsertColumns] = { 'vector' : map, 'layer' : layer,
251  'dbInfo' : cparams[map]['dbInfo'] }
252  else: # table
253  driver = db = None
254  pDriver = self.task.get_param('dbdriver', element='prompt', raiseError=False)
255  if pDriver:
256  driver = pDriver.get('value', None)
257  pDb = self.task.get_param('dbname', element='prompt', raiseError=False)
258  if pDb:
259  db = pDb.get('value', None)
260  pTable = self.task.get_param('dbtable', element='element', raiseError=False)
261  if pTable and \
262  pTable.get('value', '') != '':
263  if driver and db:
264  self.data[win.InsertTableColumns] = { 'table' : pTable.get('value'),
265  'driver' : driver,
266  'database' : db }
267  else:
268  self.data[win.InsertTableColumns] = { 'table' : pTable.get('value') }
269 
270  elif name == 'SubGroupSelect':
271  self.data[win.Insert] = { 'group' : p.get('value', '')}
272 
273  elif name == 'LocationSelect':
274  pDbase = self.task.get_param('dbase', element = 'element', raiseError = False)
275  if pDbase:
276  self.data[win.UpdateItems] = { 'dbase' : pDbase.get('value', '')}
277 
278  elif name == 'MapsetSelect':
279  pDbase = self.task.get_param('dbase', element = 'element', raiseError = False)
280  pLocation = self.task.get_param('location', element = 'element', raiseError = False)
281  if pDbase and pLocation:
282  self.data[win.UpdateItems] = { 'dbase' : pDbase.get('value', ''),
283  'location' : pLocation.get('value', '')}
284 
285  elif name == 'ProjSelect':
286  pDbase = self.task.get_param('dbase', element = 'element', raiseError = False)
287  pLocation = self.task.get_param('location', element = 'element', raiseError = False)
288  pMapset = self.task.get_param('mapset', element = 'element', raiseError = False)
289  if pDbase and pLocation and pMapset:
290  self.data[win.UpdateItems] = { 'dbase' : pDbase.get('value', ''),
291  'location' : pLocation.get('value', ''),
292  'mapset' : pMapset.get('value', '')}
293 
294 def UpdateDialog(parent, event, eventId, task):
295  return UpdateThread(parent, event, eventId, task)
296 
297 class UpdateQThread(Thread):
298  """!Update dialog widgets in the thread"""
299  requestId = 0
300  def __init__(self, parent, requestQ, resultQ, **kwds):
301  Thread.__init__(self, **kwds)
302 
303  self.parent = parent # CmdPanel
304  self.setDaemon(True)
305 
306  self.requestQ = requestQ
307  self.resultQ = resultQ
308 
309  self.start()
310 
311  def Update(self, callable, *args, **kwds):
312  UpdateQThread.requestId += 1
313 
314  self.request = None
315  self.requestQ.put((UpdateQThread.requestId, callable, args, kwds))
316 
317  return UpdateQThread.requestId
318 
319  def run(self):
320  while True:
321  requestId, callable, args, kwds = self.requestQ.get()
322 
323  requestTime = time.time()
324 
325  self.request = callable(*args, **kwds)
326 
327  self.resultQ.put((requestId, self.request.run()))
328 
329  if self.request:
330  event = wxUpdateDialog(data = self.request.data)
331  wx.PostEvent(self.parent, event)
332 
333 class TaskFrame(wx.Frame):
334  """!This is the Frame containing the dialog for options input.
335 
336  The dialog is organized in a notebook according to the guisections
337  defined by each GRASS command.
338 
339  If run with a parent, it may Apply, Ok or Cancel; the latter two
340  close the dialog. The former two trigger a callback.
341 
342  If run standalone, it will allow execution of the command.
343 
344  The command is checked and sent to the clipboard when clicking
345  'Copy'.
346  """
347  def __init__(self, parent, ID, task_description,
348  get_dcmd = None, layer = None):
349  self.get_dcmd = get_dcmd
350  self.layer = layer
351  self.task = task_description
352  self.parent = parent # LayerTree | Modeler | None | ...
353  if parent and parent.GetName() == 'Modeler':
354  self.modeler = self.parent
355  else:
356  self.modeler = None
357 
358  # module name + keywords
359  if self.task.name.split('.')[-1] in ('py', 'sh'):
360  title = str(self.task.name.rsplit('.',1)[0])
361  else:
362  title = self.task.name
363  try:
364  if self.task.keywords != ['']:
365  title += " [" + ', '.join(self.task.keywords) + "]"
366  except ValueError:
367  pass
368 
369  wx.Frame.__init__(self, parent = parent, id = ID, title = title,
370  pos = wx.DefaultPosition, style = wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL,
371  name = "MainFrame")
372 
373  self.locale = wx.Locale(language = wx.LANGUAGE_DEFAULT)
374 
375  self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
376 
377  # statusbar
378  self.CreateStatusBar()
379 
380  # icon
381  self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass_dialog.ico'), wx.BITMAP_TYPE_ICO))
382 
383  guisizer = wx.BoxSizer(wx.VERTICAL)
384 
385  # set apropriate output window
386  if self.parent:
387  self.standalone = False
388  else:
389  self.standalone = True
390 
391  # logo + description
392  topsizer = wx.BoxSizer(wx.HORIZONTAL)
393 
394  # GRASS logo
395  self.logo = wx.StaticBitmap(parent = self.panel,
396  bitmap = wx.Bitmap(name = os.path.join(globalvar.ETCIMGDIR,
397  'grass_form.png'),
398  type = wx.BITMAP_TYPE_PNG))
399  topsizer.Add(item = self.logo, proportion = 0, border = 3,
400  flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL)
401 
402  # add module description
403  if self.task.label:
404  module_desc = self.task.label + ' ' + self.task.description
405  else:
406  module_desc = self.task.description
407  self.description = StaticWrapText(parent = self.panel,
408  label = module_desc)
409  topsizer.Add(item = self.description, proportion = 1, border = 5,
410  flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
411 
412  guisizer.Add(item = topsizer, proportion = 0, flag = wx.EXPAND)
413 
414  self.panel.SetSizerAndFit(guisizer)
415  self.Layout()
416 
417  # notebooks
418  self.notebookpanel = CmdPanel(parent = self.panel, task = self.task,
419  frame = self)
420  self.goutput = self.notebookpanel.goutput
421  self.notebookpanel.OnUpdateValues = self.updateValuesHook
422  guisizer.Add(item = self.notebookpanel, proportion = 1, flag = wx.EXPAND)
423 
424  # status bar
425  status_text = _("Enter parameters for '") + self.task.name + "'"
426  try:
427  self.task.get_cmd()
428  self.updateValuesHook()
429  except ValueError:
430  self.SetStatusText(status_text)
431 
432  # buttons
433  btnsizer = wx.BoxSizer(orient = wx.HORIZONTAL)
434  # cancel
435  self.btn_cancel = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
436  self.btn_cancel.SetToolTipString(_("Close this window without executing the command (Ctrl+Q)"))
437  btnsizer.Add(item = self.btn_cancel, proportion = 0, flag = wx.ALL | wx.ALIGN_CENTER, border = 10)
438  self.btn_cancel.Bind(wx.EVT_BUTTON, self.OnCancel)
439 
440  if self.get_dcmd is not None: # A callback has been set up
441  btn_apply = wx.Button(parent = self.panel, id = wx.ID_APPLY)
442  btn_ok = wx.Button(parent = self.panel, id = wx.ID_OK)
443  btn_ok.SetDefault()
444 
445  btnsizer.Add(item = btn_apply, proportion = 0,
446  flag = wx.ALL | wx.ALIGN_CENTER,
447  border = 10)
448  btnsizer.Add(item = btn_ok, proportion = 0,
449  flag = wx.ALL | wx.ALIGN_CENTER,
450  border = 10)
451 
452  btn_apply.Bind(wx.EVT_BUTTON, self.OnApply)
453  btn_ok.Bind(wx.EVT_BUTTON, self.OnOK)
454  else: # We're standalone
455  # run
456  self.btn_run = wx.Button(parent = self.panel, id = wx.ID_OK, label = _("&Run"))
457  self.btn_run.SetToolTipString(_("Run the command (Ctrl+R)"))
458  self.btn_run.SetDefault()
459  self.btn_run.SetForegroundColour(wx.Colour(35, 142, 35))
460 
461  # copy
462  self.btn_clipboard = wx.Button(parent = self.panel, id = wx.ID_COPY)
463  self.btn_clipboard.SetToolTipString(_("Copy the current command string to the clipboard (Ctrl+C)"))
464 
465  btnsizer.Add(item = self.btn_run, proportion = 0,
466  flag = wx.ALL | wx.ALIGN_CENTER,
467  border = 10)
468 
469  btnsizer.Add(item = self.btn_clipboard, proportion = 0,
470  flag = wx.ALL | wx.ALIGN_CENTER,
471  border = 10)
472 
473  self.btn_run.Bind(wx.EVT_BUTTON, self.OnRun)
474  self.btn_clipboard.Bind(wx.EVT_BUTTON, self.OnCopy)
475  # help
476  self.btn_help = wx.Button(parent = self.panel, id = wx.ID_HELP)
477  self.btn_help.SetToolTipString(_("Show manual page of the command (Ctrl+H)"))
478  self.btn_help.Bind(wx.EVT_BUTTON, self.OnHelp)
479  if self.notebookpanel.notebook.GetPageIndexByName('manual') < 0:
480  self.btn_help.Hide()
481 
482  # add help button
483  btnsizer.Add(item = self.btn_help, proportion = 0, flag = wx.ALL | wx.ALIGN_CENTER, border = 10)
484 
485  guisizer.Add(item = btnsizer, proportion = 0, flag = wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT,
486  border = 30)
487 
488  if self.parent and not self.modeler:
489  addLayer = False
490  for p in self.task.params:
491  if p.get('age', 'old') == 'new' and \
492  p.get('prompt', '') in ('raster', 'vector', '3d-raster'):
493  addLayer = True
494 
495  if addLayer:
496  # add newly created map into layer tree
497  self.addbox = wx.CheckBox(parent = self.panel,
498  label = _('Add created map(s) into layer tree'), style = wx.NO_BORDER)
499  self.addbox.SetValue(UserSettings.Get(group = 'cmd', key = 'addNewLayer', subkey = 'enabled'))
500  guisizer.Add(item = self.addbox, proportion = 0,
501  flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM,
502  border = 5)
503 
504  hasNew = False
505  for p in self.task.params:
506  if p.get('age', 'old') == 'new':
507  hasNew = True
508  break
509 
510  if self.get_dcmd is None and hasNew:
511  # close dialog when command is terminated
512  self.closebox = wx.CheckBox(parent = self.panel,
513  label = _('Close dialog on finish'), style = wx.NO_BORDER)
514  self.closebox.SetValue(UserSettings.Get(group = 'cmd', key = 'closeDlg', subkey = 'enabled'))
515  self.closebox.SetToolTipString(_("Close dialog when command is successfully finished. "
516  "Change this settings in Preferences dialog ('Command' tab)."))
517  guisizer.Add(item = self.closebox, proportion = 0,
518  flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM,
519  border = 5)
520 
521  self.Bind(wx.EVT_CLOSE, self.OnCancel)
522  self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
523 
524  # do layout
525  # called automatically by SetSizer()
526  self.panel.SetAutoLayout(True)
527  self.panel.SetSizerAndFit(guisizer)
528 
529  sizeFrame = self.GetBestSize()
530  self.SetMinSize(sizeFrame)
531  self.SetSize(wx.Size(sizeFrame[0], sizeFrame[1] + 0.33 * max(self.notebookpanel.panelMinHeight,
532  self.notebookpanel.constrained_size[1])))
533 
534  # thread to update dialog
535  # create queues
536  self.requestQ = Queue.Queue()
537  self.resultQ = Queue.Queue()
539 
540  self.Layout()
541 
542  # keep initial window size limited for small screens
543  width, height = self.GetSizeTuple()
544  self.SetSize(wx.Size(min(width, 650),
545  min(height, 500)))
546 
547  # fix goutput's pane size (required for Mac OSX)
548  if self.goutput:
549  self.goutput.SetSashPosition(int(self.GetSize()[1] * .75))
550 
551  def updateValuesHook(self, event = None):
552  """!Update status bar data"""
553  self.SetStatusText(' '.join(self.notebookpanel.createCmd(ignoreErrors = True)))
554  if event:
555  event.Skip()
556 
557  def OnKeyUp(self, event):
558  """!Key released (check hot-keys)"""
559  try:
560  kc = chr(event.GetKeyCode())
561  except ValueError:
562  event.Skip()
563  return
564 
565  if not event.ControlDown():
566  event.Skip()
567  return
568 
569  if kc == 'Q':
570  self.OnCancel(None)
571  elif kc == 'S':
572  self.OnAbort(None)
573  elif kc == 'H':
574  self.OnHelp(None)
575  elif kc == 'R':
576  self.OnRun(None)
577  elif kc == 'C':
578  self.OnCopy(None)
579 
580  event.Skip()
581 
582  def OnDone(self, cmd, returncode):
583  """!This function is launched from OnRun() when command is
584  finished
585 
586  @param returncode command's return code (0 for success)
587  """
588  if not self.parent or returncode != 0:
589  return
590  if self.parent.GetName() not in ('LayerTree', 'LayerManager'):
591  return
592 
593  if self.parent.GetName() == 'LayerTree':
594  display = self.parent.GetMapDisplay()
595  else: # Layer Manager
596  display = None
597  tree = self.parent.GetLayerTree()
598  if tree:
599  display = tree.GetMapDisplay()
600 
601  if not display or not display.IsAutoRendered():
602  return
603 
604  mapLayers = map(lambda x: x.GetName(),
605  display.GetMap().GetListOfLayers(l_type = 'raster') +
606  display.GetMap().GetListOfLayers(l_type = 'vector'))
607 
608  task = GUI(show = None).ParseCommand(cmd)
609  for p in task.get_options()['params']:
610  if p.get('prompt', '') not in ('raster', 'vector'):
611  continue
612  mapName = p.get('value', '')
613  if '@' not in mapName:
614  mapName = mapName + '@' + grass.gisenv()['MAPSET']
615  if mapName in mapLayers:
616  display.GetWindow().UpdateMap(render = True)
617  return
618 
619  def OnOK(self, event):
620  """!OK button pressed"""
621  cmd = self.OnApply(event)
622  if cmd is not None and self.get_dcmd is not None:
623  self.OnCancel(event)
624 
625  def OnApply(self, event):
626  """!Apply the command"""
627  if self.modeler:
628  cmd = self.createCmd(ignoreErrors = True, ignoreRequired = True)
629  else:
630  cmd = self.createCmd()
631 
632  if cmd is not None and self.get_dcmd is not None:
633  # return d.* command to layer tree for rendering
634  self.get_dcmd(cmd, self.layer, {"params": self.task.params,
635  "flags" : self.task.flags},
636  self)
637  # echo d.* command to output console
638  # self.parent.writeDCommand(cmd)
639 
640  return cmd
641 
642  def OnRun(self, event):
643  """!Run the command"""
644  cmd = self.createCmd()
645 
646  if not cmd or len(cmd) < 1:
647  return
648 
649  if self.standalone or cmd[0][0:2] != "d.":
650  # Send any non-display command to parent window (probably wxgui.py)
651  # put to parents switch to 'Command output'
652  self.notebookpanel.notebook.SetSelectionByName('output')
653 
654  try:
655 
656  self.goutput.RunCmd(cmd, onDone = self.OnDone)
657  except AttributeError, e:
658  print >> sys.stderr, "%s: Probably not running in wxgui.py session?" % (e)
659  print >> sys.stderr, "parent window is: %s" % (str(self.parent))
660  else:
661  gcmd.Command(cmd)
662 
663  # update buttons status
664  for btn in (self.btn_run,
665  self.btn_cancel,
666  self.btn_clipboard,
667  self.btn_help):
668  btn.Enable(False)
669 
670  def OnAbort(self, event):
671  """!Abort running command"""
672  from gui_core.goutput import wxCmdAbort
673  event = wxCmdAbort(aborted = True)
674  wx.PostEvent(self.goutput, event)
675 
676  def OnCopy(self, event):
677  """!Copy the command"""
678  cmddata = wx.TextDataObject()
679  # list -> string
680  cmdstring = ' '.join(self.createCmd(ignoreErrors = True))
681  cmddata.SetText(cmdstring)
682  if wx.TheClipboard.Open():
683 # wx.TheClipboard.UsePrimarySelection(True)
684  wx.TheClipboard.SetData(cmddata)
685  wx.TheClipboard.Close()
686  self.SetStatusText(_("'%s' copied to clipboard") % \
687  (cmdstring))
688 
689  def OnCancel(self, event):
690  """!Cancel button pressed"""
691  self.MakeModal(False)
692 
693  if self.get_dcmd and \
694  self.parent and \
695  self.parent.GetName() in ('LayerTree',
696  'MapWindow'):
697  # display decorations and
698  # pressing OK or cancel after setting layer properties
699  if self.task.name in ['d.barscale','d.legend','d.histogram'] \
700  or len(self.parent.GetPyData(self.layer)[0]['cmd']) >= 1:
701  self.Hide()
702  # canceled layer with nothing set
703  elif len(self.parent.GetPyData(self.layer)[0]['cmd']) < 1:
704  self.parent.Delete(self.layer)
705  self.Destroy()
706  else:
707  # cancel for non-display commands
708  self.Destroy()
709 
710  def OnHelp(self, event):
711  """!Show manual page (switch to the 'Manual' notebook page)"""
712  if self.notebookpanel.notebook.GetPageIndexByName('manual') > -1:
713  self.notebookpanel.notebook.SetSelectionByName('manual')
714  self.notebookpanel.OnPageChange(None)
715 
716  if event:
717  event.Skip()
718 
719  def createCmd(self, ignoreErrors = False, ignoreRequired = False):
720  """!Create command string (python list)"""
721  return self.notebookpanel.createCmd(ignoreErrors = ignoreErrors,
722  ignoreRequired = ignoreRequired)
723 
724 class CmdPanel(wx.Panel):
725  """!A panel containing a notebook dividing in tabs the different
726  guisections of the GRASS cmd.
727  """
728  def __init__(self, parent, task, id = wx.ID_ANY, frame = None, *args, **kwargs):
729  if frame:
730  self.parent = frame
731  else:
732  self.parent = parent
733  self.task = task
734 
735  wx.Panel.__init__(self, parent, id = id, *args, **kwargs)
736 
737  # Determine tab layout
738  sections = []
739  is_section = {}
740  not_hidden = [ p for p in self.task.params + self.task.flags if not p.get('hidden', False) == True ]
741 
742  self.label_id = [] # wrap titles on resize
743 
744  self.Bind(wx.EVT_SIZE, self.OnSize)
745 
746  for task in not_hidden:
747  if task.get('required', False):
748  # All required go into Main, even if they had defined another guisection
749  task['guisection'] = _('Required')
750  if task.get('guisection','') == '':
751  # Undefined guisections end up into Options
752  task['guisection'] = _('Optional')
753  if task['guisection'] not in is_section:
754  # We do it like this to keep the original order, except for Main which goes first
755  is_section[task['guisection']] = 1
756  sections.append(task['guisection'])
757  else:
758  is_section[ task['guisection'] ] += 1
759  del is_section
760 
761  # 'Required' tab goes first, 'Optional' as the last one
762  for (newidx,content) in [ (0,_('Required')), (len(sections)-1,_('Optional')) ]:
763  if content in sections:
764  idx = sections.index(content)
765  sections[idx:idx+1] = []
766  sections[newidx:newidx] = [content]
767 
768  panelsizer = wx.BoxSizer(orient = wx.VERTICAL)
769 
770  # Build notebook
771  self.notebook = GNotebook(self, style = globalvar.FNPageStyle | FN.FNB_NO_X_BUTTON )
772  self.notebook.SetTabAreaColour(globalvar.FNPageColor)
773  self.notebook.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnPageChange)
774 
775  tab = {}
776  tabsizer = {}
777  for section in sections:
778  tab[section] = ScrolledPanel(parent = self.notebook)
779  tab[section].SetScrollRate(10, 10)
780  tabsizer[section] = wx.BoxSizer(orient = wx.VERTICAL)
781  self.notebook.AddPage(page = tab[section], text = section)
782 
783  # are we running from command line?
784  ### add 'command output' tab regardless standalone dialog
785  if self.parent.GetName() == "MainFrame" and self.parent.get_dcmd is None:
786  from gui_core.goutput import GMConsole
787  self.goutput = GMConsole(parent = self, margin = False)
788  self.outpage = self.notebook.AddPage(page = self.goutput, text = _("Command output"), name = 'output')
789  else:
790  self.goutput = None
791 
792  self.manualTab = HelpPanel(parent = self, command = self.task.name)
793  if not self.manualTab.GetFile():
794  self.manualTab.Hide()
795  else:
796  self.notebook.AddPage(page = self.manualTab, text = _("Manual"), name = 'manual')
797 
798  self.notebook.SetSelection(0)
799 
800  panelsizer.Add(item = self.notebook, proportion = 1, flag = wx.EXPAND)
801 
802  #
803  # flags
804  #
805  text_style = wx.FONTWEIGHT_NORMAL
806  visible_flags = [ f for f in self.task.flags if not f.get('hidden', False) == True ]
807  for f in visible_flags:
808  which_sizer = tabsizer[ f['guisection'] ]
809  which_panel = tab[ f['guisection'] ]
810  # if label is given: description -> tooltip
811  if f.get('label','') != '':
812  title = text_beautify(f['label'])
813  tooltip = text_beautify(f['description'], width = -1)
814  else:
815  title = text_beautify(f['description'])
816  tooltip = None
817  title_sizer = wx.BoxSizer(wx.HORIZONTAL)
818  rtitle_txt = wx.StaticText(parent = which_panel,
819  label = '(' + f['name'] + ')')
820  chk = wx.CheckBox(parent = which_panel, label = title, style = wx.NO_BORDER)
821  self.label_id.append(chk.GetId())
822  if tooltip:
823  chk.SetToolTipString(tooltip)
824  chk.SetValue(f.get('value', False))
825  title_sizer.Add(item = chk, proportion = 1,
826  flag = wx.EXPAND)
827  title_sizer.Add(item = rtitle_txt, proportion = 0,
828  flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
829  which_sizer.Add(item = title_sizer, proportion = 0,
830  flag = wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border = 5)
831  f['wxId'] = [ chk.GetId(), ]
832  chk.Bind(wx.EVT_CHECKBOX, self.OnSetValue)
833 
834  if self.parent.GetName() == 'MainFrame' and self.parent.modeler:
835  parChk = wx.CheckBox(parent = which_panel, id = wx.ID_ANY,
836  label = _("Parameterized in model"))
837  parChk.SetName('ModelParam')
838  parChk.SetValue(f.get('parameterized', False))
839  if 'wxId' in f:
840  f['wxId'].append(parChk.GetId())
841  else:
842  f['wxId'] = [ parChk.GetId() ]
843  parChk.Bind(wx.EVT_CHECKBOX, self.OnSetValue)
844  which_sizer.Add(item = parChk, proportion = 0,
845  flag = wx.LEFT, border = 20)
846 
847  if f['name'] in ('verbose', 'quiet'):
848  chk.Bind(wx.EVT_CHECKBOX, self.OnVerbosity)
849  vq = UserSettings.Get(group = 'cmd', key = 'verbosity', subkey = 'selection')
850  if f['name'] == vq:
851  chk.SetValue(True)
852  f['value'] = True
853  elif f['name'] == 'overwrite' and 'value' not in f:
854  chk.SetValue(UserSettings.Get(group = 'cmd', key = 'overwrite', subkey = 'enabled'))
855  f['value'] = UserSettings.Get(group = 'cmd', key = 'overwrite', subkey = 'enabled')
856 
857  #
858  # parameters
859  #
860  visible_params = [ p for p in self.task.params if not p.get('hidden', False) == True ]
861 
862  try:
863  first_param = visible_params[0]
864  except IndexError:
865  first_param = None
866 
867  for p in visible_params:
868  which_sizer = tabsizer[ p['guisection'] ]
869  which_panel = tab[ p['guisection'] ]
870  # if label is given -> label and description -> tooltip
871  # otherwise description -> lavel
872  if p.get('label','') != '':
873  title = text_beautify(p['label'])
874  tooltip = text_beautify(p['description'], width = -1)
875  else:
876  title = text_beautify(p['description'])
877  tooltip = None
878  txt = None
879 
880  # text style (required -> bold)
881  if not p.get('required', False):
882  text_style = wx.FONTWEIGHT_NORMAL
883  else:
884  text_style = wx.FONTWEIGHT_BOLD
885 
886  # title sizer (description, name, type)
887  if (len(p.get('values', [])) > 0) and \
888  p.get('multiple', False) and \
889  p.get('gisprompt',False) == False and \
890  p.get('type', '') == 'string':
891  title_txt = wx.StaticBox(parent = which_panel, id = wx.ID_ANY)
892  else:
893  title_sizer = wx.BoxSizer(wx.HORIZONTAL)
894  title_txt = wx.StaticText(parent = which_panel)
895  if p['key_desc']:
896  ltype = ','.join(p['key_desc'])
897  else:
898  ltype = p['type']
899  rtitle_txt = wx.StaticText(parent = which_panel,
900  label = '(' + p['name'] + '=' + ltype + ')')
901  title_sizer.Add(item = title_txt, proportion = 1,
902  flag = wx.LEFT | wx.TOP | wx.EXPAND, border = 5)
903  title_sizer.Add(item = rtitle_txt, proportion = 0,
904  flag = wx.ALIGN_RIGHT | wx.RIGHT | wx.TOP, border = 5)
905  which_sizer.Add(item = title_sizer, proportion = 0,
906  flag = wx.EXPAND)
907  self.label_id.append(title_txt.GetId())
908 
909  # title expansion
910  if p.get('multiple', False) and len(p.get('values','')) == 0:
911  title = _("[multiple]") + " " + title
912  if p.get('value','') == '' :
913  p['value'] = p.get('default','')
914 
915  if (len(p.get('values', [])) > 0):
916  valuelist = map(str, p.get('values',[]))
917  valuelist_desc = map(unicode, p.get('values_desc',[]))
918 
919  if p.get('multiple', False) and \
920  p.get('gisprompt',False) == False and \
921  p.get('type', '') == 'string':
922  title_txt.SetLabel(" %s: (%s, %s) " % (title, p['name'], p['type']))
923  if valuelist_desc:
924  hSizer = wx.StaticBoxSizer(box = title_txt, orient = wx.VERTICAL)
925  else:
926  hSizer = wx.StaticBoxSizer(box = title_txt, orient = wx.HORIZONTAL)
927  isEnabled = {}
928  # copy default values
929  if p['value'] == '':
930  p['value'] = p.get('default', '')
931 
932  for defval in p.get('value', '').split(','):
933  isEnabled[ defval ] = 'yes'
934  # for multi checkboxes, this is an array of all wx IDs
935  # for each individual checkbox
936  p['wxId'] = list()
937  idx = 0
938  for val in valuelist:
939  try:
940  label = valuelist_desc[idx]
941  except IndexError:
942  label = val
943 
944  chkbox = wx.CheckBox(parent = which_panel,
945  label = text_beautify(label))
946  p[ 'wxId' ].append(chkbox.GetId())
947  if val in isEnabled:
948  chkbox.SetValue(True)
949  hSizer.Add(item = chkbox, proportion = 0,
950  flag = wx.ADJUST_MINSIZE | wx.ALL, border = 1)
951  chkbox.Bind(wx.EVT_CHECKBOX, self.OnCheckBoxMulti)
952  idx += 1
953 
954  which_sizer.Add(item = hSizer, proportion = 0,
955  flag = wx.EXPAND | wx.TOP | wx.RIGHT | wx.LEFT, border = 5)
956  elif p.get('gisprompt', False) == False:
957  if len(valuelist) == 1: # -> textctrl
958  title_txt.SetLabel("%s (%s %s):" % (title, _('valid range'),
959  str(valuelist[0])))
960 
961  if p.get('type', '') == 'integer' and \
962  not p.get('multiple', False):
963 
964  # for multiple integers use textctrl instead of spinsctrl
965  try:
966  minValue, maxValue = map(int, valuelist[0].split('-'))
967  except ValueError:
968  minValue = -1e6
969  maxValue = 1e6
970  txt2 = wx.SpinCtrl(parent = which_panel, id = wx.ID_ANY, size = globalvar.DIALOG_SPIN_SIZE,
971  min = minValue, max = maxValue)
972  txt2.SetName("SpinCtrl")
973  style = wx.BOTTOM | wx.LEFT
974  else:
975  txt2 = wx.TextCtrl(parent = which_panel, value = p.get('default',''))
976  txt2.SetName("TextCtrl")
977  style = wx.EXPAND | wx.BOTTOM | wx.LEFT
978 
979  value = self._getValue(p)
980  # parameter previously set
981  if value:
982  if txt2.GetName() == "SpinCtrl":
983  txt2.SetValue(int(value))
984  else:
985  txt2.SetValue(value)
986 
987  which_sizer.Add(item = txt2, proportion = 0,
988  flag = style, border = 5)
989 
990  p['wxId'] = [ txt2.GetId(), ]
991  txt2.Bind(wx.EVT_TEXT, self.OnSetValue)
992  else:
993  title_txt.SetLabel(title + ':')
994  value = self._getValue(p)
995 
996  if p['name'] == 'icon': # symbols
997  bitmap = wx.Bitmap(os.path.join(globalvar.ETCSYMBOLDIR, value) + '.png')
998  bb = wx.BitmapButton(parent = which_panel, id = wx.ID_ANY,
999  bitmap = bitmap)
1000  iconLabel = wx.StaticText(parent = which_panel, id = wx.ID_ANY)
1001  iconLabel.SetLabel(value)
1002  p['value'] = value
1003  p['wxId'] = [bb.GetId(), iconLabel.GetId()]
1004  bb.Bind(wx.EVT_BUTTON, self.OnSetSymbol)
1005  this_sizer = wx.BoxSizer(wx.HORIZONTAL)
1006  this_sizer.Add(item = bb, proportion = 0,
1007  flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT, border = 5)
1008  this_sizer.Add(item = iconLabel, proportion = 0,
1009  flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT | wx.ALIGN_CENTER_VERTICAL, border = 5)
1010  which_sizer.Add(item = this_sizer, proportion = 0,
1011  flag = wx.ADJUST_MINSIZE, border = 0)
1012  else:
1013  # list of values (combo)
1014  cb = wx.ComboBox(parent = which_panel, id = wx.ID_ANY, value = p.get('default',''),
1015  size = globalvar.DIALOG_COMBOBOX_SIZE,
1016  choices = valuelist, style = wx.CB_DROPDOWN)
1017  if value:
1018  cb.SetValue(value) # parameter previously set
1019  which_sizer.Add(item = cb, proportion = 0,
1020  flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT, border = 5)
1021  p['wxId'] = [cb.GetId(),]
1022  cb.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
1023  cb.Bind(wx.EVT_TEXT, self.OnSetValue)
1024 
1025  # text entry
1026  if (p.get('type','string') in ('string','integer','float')
1027  and len(p.get('values',[])) == 0
1028  and p.get('gisprompt',False) == False
1029  and p.get('prompt','') != 'color'):
1030 
1031  title_txt.SetLabel(title + ':')
1032  if p.get('multiple', False) or \
1033  p.get('type', 'string') == 'string' or \
1034  len(p.get('key_desc', [])) > 1:
1035  txt3 = wx.TextCtrl(parent = which_panel, value = p.get('default',''))
1036 
1037  value = self._getValue(p)
1038  if value:
1039  # parameter previously set
1040  txt3.SetValue(str(value))
1041 
1042  txt3.Bind(wx.EVT_TEXT, self.OnSetValue)
1043  style = wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT
1044  else:
1045  minValue = -1e9
1046  maxValue = 1e9
1047  if p.get('type', '') == 'integer':
1048  txt3 = wx.SpinCtrl(parent = which_panel, value = p.get('default',''),
1049  size = globalvar.DIALOG_SPIN_SIZE,
1050  min = minValue, max = maxValue)
1051  style = wx.BOTTOM | wx.LEFT | wx.RIGHT
1052 
1053  value = self._getValue(p)
1054  if value:
1055  txt3.SetValue(int(value)) # parameter previously set
1056 
1057  txt3.Bind(wx.EVT_SPINCTRL, self.OnSetValue)
1058  else:
1059  txt3 = wx.TextCtrl(parent = which_panel, value = p.get('default',''),
1060  validator = FloatValidator())
1061  style = wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT
1062 
1063  value = self._getValue(p)
1064  if value:
1065  txt3.SetValue(str(value)) # parameter previously set
1066 
1067  txt3.Bind(wx.EVT_TEXT, self.OnSetValue)
1068 
1069  which_sizer.Add(item = txt3, proportion = 0,
1070  flag = style, border = 5)
1071  p['wxId'] = [ txt3.GetId(), ]
1072 
1073  #
1074  # element selection tree combobox (maps, icons, regions, etc.)
1075  #
1076  if p.get('gisprompt', False) == True:
1077  title_txt.SetLabel(title + ':')
1078  # GIS element entry
1079  if p.get('prompt','') not in ('color',
1080  'color_none',
1081  'subgroup',
1082  'dbdriver',
1083  'dbname',
1084  'dbtable',
1085  'dbcolumn',
1086  'layer',
1087  'layer_all',
1088  'layer_zero',
1089  'location',
1090  'mapset',
1091  'dbase') and \
1092  p.get('element', '') != 'file':
1093  multiple = p.get('multiple', False)
1094  if p.get('age', '') == 'new':
1095  mapsets = [grass.gisenv()['MAPSET'],]
1096  else:
1097  mapsets = None
1098  if self.task.name in ('r.proj', 'v.proj') \
1099  and p.get('name', '') == 'input':
1100  if self.task.name == 'r.proj':
1101  isRaster = True
1102  else:
1103  isRaster = False
1104  selection = gselect.ProjSelect(parent = which_panel,
1105  isRaster = isRaster)
1106  p['wxId'] = [ selection.GetId(), ]
1107  selection.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
1108  selection.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
1109  else:
1110  selection = gselect.Select(parent = which_panel, id = wx.ID_ANY,
1111  size = globalvar.DIALOG_GSELECT_SIZE,
1112  type = p.get('element', ''),
1113  multiple = multiple, nmaps = len(p.get('key_desc', [])),
1114  mapsets = mapsets, fullyQualified = p.get('age', 'old') == 'old')
1115 
1116 
1117  # A select.Select is a combobox with two children: a textctl and a popupwindow;
1118  # we target the textctl here
1119  textWin = selection.GetTextCtrl()
1120  p['wxId'] = [ textWin.GetId(), ]
1121  textWin.Bind(wx.EVT_TEXT, self.OnSetValue)
1122 
1123  value = self._getValue(p)
1124  if value:
1125  selection.SetValue(value) # parameter previously set
1126 
1127  which_sizer.Add(item=selection, proportion=0,
1128  flag=wx.ADJUST_MINSIZE| wx.BOTTOM | wx.LEFT | wx.RIGHT, border=5)
1129 
1130  if p.get('prompt', '') in ('vector', 'group'):
1131  selection.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
1132  # subgroup
1133  elif p.get('prompt', '') == 'subgroup':
1134  selection = gselect.SubGroupSelect(parent = which_panel)
1135  p['wxId'] = [ selection.GetId() ]
1136  selection.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
1137  selection.Bind(wx.EVT_TEXT, self.OnSetValue)
1138  which_sizer.Add(item = selection, proportion = 0,
1139  flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT | wx.RIGHT | wx.TOP | wx.ALIGN_CENTER_VERTICAL,
1140  border = 5)
1141 
1142  # layer, dbdriver, dbname, dbcolumn, dbtable entry
1143  elif p.get('prompt', '') in ('dbdriver',
1144  'dbname',
1145  'dbtable',
1146  'dbcolumn',
1147  'layer',
1148  'layer_all',
1149  'layer_zero',
1150  'location',
1151  'mapset',
1152  'dbase'):
1153  if p.get('multiple', 'no') == 'yes':
1154  win = wx.TextCtrl(parent = which_panel, value = p.get('default',''),
1155  size = globalvar.DIALOG_TEXTCTRL_SIZE)
1156  win.Bind(wx.EVT_TEXT, self.OnSetValue)
1157  else:
1158  value = self._getValue(p)
1159 
1160  if p.get('prompt', '') in ('layer',
1161  'layer_all',
1162  'layer_zero'):
1163 
1164  if p.get('age', 'old_layer') == 'old_layer':
1165  initial = list()
1166  if p.get('prompt', '') == 'layer_all':
1167  initial.insert(0, '-1')
1168  elif p.get('prompt', '') == 'layer_zero':
1169  initial.insert(0, '0')
1170  lyrvalue = p.get('default')
1171  if lyrvalue != '':
1172  if lyrvalue not in initial:
1173  initial.append(str(lyrvalue))
1174  lyrvalue = p.get('value')
1175  if lyrvalue != '':
1176  if lyrvalue not in initial:
1177  initial.append(str(lyrvalue))
1178 
1179  win = gselect.LayerSelect(parent = which_panel,
1180  initial = initial,
1181  default = p['default'])
1182  p['wxGetValue'] = win.GetStringSelection
1183  win.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
1184  win.Bind(wx.EVT_TEXT, self.OnSetValue)
1185  win.SetValue(str(value)) # default or previously set value
1186  else:
1187  win = wx.SpinCtrl(parent = which_panel, id = wx.ID_ANY,
1188  min = 1, max = 100, initial = int(p['default']))
1189  win.Bind(wx.EVT_SPINCTRL, self.OnSetValue)
1190  win.SetValue(int(value)) # default or previously set value
1191 
1192  elif p.get('prompt', '') == 'dbdriver':
1193  win = gselect.DriverSelect(parent = which_panel,
1194  choices = p.get('values', []),
1195  value = value)
1196  p['wxGetValue'] = win.GetStringSelection
1197  win.Bind(wx.EVT_COMBOBOX, self.OnUpdateSelection)
1198  win.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
1199  elif p.get('prompt', '') == 'dbname':
1200  win = gselect.DatabaseSelect(parent = which_panel,
1201  value = value)
1202  win.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
1203  win.Bind(wx.EVT_TEXT, self.OnSetValue)
1204 
1205  elif p.get('prompt', '') == 'dbtable':
1206  if p.get('age', 'old_dbtable') == 'old_dbtable':
1207  win = gselect.TableSelect(parent=which_panel)
1208 
1209  p['wxGetValue'] = win.GetStringSelection
1210  win.Bind(wx.EVT_COMBOBOX, self.OnUpdateSelection)
1211  win.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
1212  else:
1213  win = wx.TextCtrl(parent = which_panel, value = p.get('default',''),
1214  size = globalvar.DIALOG_TEXTCTRL_SIZE)
1215  win.Bind(wx.EVT_TEXT, self.OnSetValue)
1216  elif p.get('prompt', '') == 'dbcolumn':
1217  win = gselect.ColumnSelect(parent = which_panel,
1218  value = value,
1219  param = p)
1220  win.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
1221  win.Bind(wx.EVT_TEXT, self.OnSetValue)
1222 
1223  elif p.get('prompt', '') == 'location':
1224  win = gselect.LocationSelect(parent = which_panel,
1225  value = value)
1226  win.Bind(wx.EVT_COMBOBOX, self.OnUpdateSelection)
1227  win.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
1228 
1229  elif p.get('prompt', '') == 'mapset':
1230  win = gselect.MapsetSelect(parent = which_panel,
1231  value = value)
1232  win.Bind(wx.EVT_COMBOBOX, self.OnUpdateSelection)
1233  win.Bind(wx.EVT_COMBOBOX, self.OnSetValue)
1234 
1235  elif p.get('prompt', '') == 'dbase':
1236  win = gselect.DbaseSelect(parent = which_panel,
1237  changeCallback = self.OnSetValue)
1238  win.Bind(wx.EVT_TEXT, self.OnUpdateSelection)
1239  p['wxId'] = [ win.GetChildren()[1].GetId() ]
1240 
1241  if 'wxId' not in p:
1242  try:
1243  p['wxId'] = [ win.GetId(), ]
1244  except AttributeError:
1245  pass
1246 
1247  which_sizer.Add(item = win, proportion = 0,
1248  flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT, border = 5)
1249  # color entry
1250  elif p.get('prompt', '') in ('color',
1251  'color_none'):
1252  default_color = (200,200,200)
1253  label_color = _("Select Color")
1254  if p.get('default','') != '':
1255  default_color, label_color = color_resolve(p['default'])
1256  if p.get('value','') != '' and p.get('value','') != 'none': # parameter previously set
1257  default_color, label_color = color_resolve(p['value'])
1258  if p.get('prompt', '') == 'color_none' or p.get('multiple', False):
1259  this_sizer = wx.BoxSizer(orient = wx.HORIZONTAL)
1260  else:
1261  this_sizer = which_sizer
1262  colorSize = 150
1263  # For color selectors, this is a three-member array, holding the IDs of
1264  # the color picker, the text control for multiple colors (or None),
1265  # and either a "transparent" checkbox or None
1266  p['wxId'] = [None] * 3
1267  if p.get('multiple', False):
1268  txt = wx.TextCtrl(parent = which_panel, id = wx.ID_ANY)
1269  this_sizer.Add(item = txt, proportion = 1,
1270  flag = wx.ADJUST_MINSIZE | wx.LEFT | wx.TOP, border = 5)
1271  txt.Bind(wx.EVT_TEXT, self.OnSetValue)
1272  colorSize = 40
1273  label_color = ''
1274  p['wxId'][1] = txt.GetId()
1275  which_sizer.Add(this_sizer, flag = wx.EXPAND | wx.RIGHT, border = 5)
1276 
1277  btn_colour = csel.ColourSelect(parent = which_panel, id = wx.ID_ANY,
1278  label = label_color, colour = default_color,
1279  pos = wx.DefaultPosition, size = (colorSize,-1))
1280  this_sizer.Add(item = btn_colour, proportion = 0,
1281  flag = wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT, border = 5)
1282  btn_colour.Bind(csel.EVT_COLOURSELECT, self.OnColorChange)
1283  p['wxId'][0] = btn_colour.GetId()
1284 
1285  if p.get('prompt', '') == 'color_none':
1286  none_check = wx.CheckBox(which_panel, wx.ID_ANY, _("Transparent"))
1287  if p.get('value','') != '' and p.get('value',[''])[0] == "none":
1288  none_check.SetValue(True)
1289  else:
1290  none_check.SetValue(False)
1291  this_sizer.Add(item = none_check, proportion = 0,
1292  flag = wx.ADJUST_MINSIZE | wx.LEFT | wx.RIGHT | wx.TOP, border = 5)
1293  which_sizer.Add(this_sizer)
1294  none_check.Bind(wx.EVT_CHECKBOX, self.OnColorChange)
1295  p['wxId'][2] = none_check.GetId()
1296 
1297  # file selector
1298  elif p.get('prompt','') != 'color' and p.get('element', '') == 'file':
1299  if p.get('age', 'new_file') == 'new_file':
1300  fmode = wx.SAVE
1301  else:
1302  fmode = wx.OPEN
1303  fbb = filebrowse.FileBrowseButton(parent = which_panel, id = wx.ID_ANY, fileMask = '*',
1304  size = globalvar.DIALOG_GSELECT_SIZE, labelText = '',
1305  dialogTitle = _('Choose %s') % \
1306  p.get('description',_('File')),
1307  buttonText = _('Browse'),
1308  startDirectory = os.getcwd(), fileMode = fmode,
1309  changeCallback = self.OnSetValue)
1310  value = self._getValue(p)
1311  if value:
1312  fbb.SetValue(value) # parameter previously set
1313  which_sizer.Add(item = fbb, proportion = 0,
1314  flag = wx.EXPAND | wx.RIGHT, border = 5)
1315 
1316  # A file browse button is a combobox with two children:
1317  # a textctl and a button;
1318  # we have to target the button here
1319  p['wxId'] = [ fbb.GetChildren()[1].GetId() ]
1320  if p.get('age', 'new_file') == 'old_file' and \
1321  UserSettings.Get(group='cmd', key='interactiveInput', subkey='enabled'):
1322  # widget for interactive input
1323  ifbb = wx.TextCtrl(parent = which_panel, id = wx.ID_ANY,
1324  style = wx.TE_MULTILINE,
1325  size = (-1, 75))
1326  if p.get('value', '') and os.path.isfile(p['value']):
1327  f = open(p['value'])
1328  ifbb.SetValue(''.join(f.readlines()))
1329  f.close()
1330 
1331  ifbb.Bind(wx.EVT_TEXT, self.OnFileText)
1332 
1333  btnLoad = wx.Button(parent = which_panel, id = wx.ID_ANY, label = _("&Load"))
1334  btnLoad.Bind(wx.EVT_BUTTON, self.OnFileLoad)
1335  btnSave = wx.Button(parent = which_panel, id = wx.ID_SAVEAS)
1336  btnSave.Bind(wx.EVT_BUTTON, self.OnFileSave)
1337 
1338  which_sizer.Add(item = wx.StaticText(parent = which_panel, id = wx.ID_ANY,
1339  label = _('or enter values interactively')),
1340  proportion = 0,
1341  flag = wx.EXPAND | wx.RIGHT | wx.LEFT | wx.BOTTOM, border = 5)
1342  which_sizer.Add(item = ifbb, proportion = 1,
1343  flag = wx.EXPAND | wx.RIGHT | wx.LEFT, border = 5)
1344  btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1345  btnSizer.Add(item = btnLoad, proportion = 0,
1346  flag = wx.ALIGN_RIGHT | wx.RIGHT, border = 10)
1347  btnSizer.Add(item = btnSave, proportion = 0,
1348  flag = wx.ALIGN_RIGHT)
1349  which_sizer.Add(item = btnSizer, proportion = 0,
1350  flag = wx.ALIGN_RIGHT | wx.RIGHT | wx.TOP, border = 5)
1351 
1352  p['wxId'].append(ifbb.GetId())
1353  p['wxId'].append(btnLoad.GetId())
1354  p['wxId'].append(btnSave.GetId())
1355 
1356  if self.parent.GetName() == 'MainFrame' and self.parent.modeler:
1357  parChk = wx.CheckBox(parent = which_panel, id = wx.ID_ANY,
1358  label = _("Parameterized in model"))
1359  parChk.SetName('ModelParam')
1360  parChk.SetValue(p.get('parameterized', False))
1361  if 'wxId' in p:
1362  p['wxId'].append(parChk.GetId())
1363  else:
1364  p['wxId'] = [ parChk.GetId() ]
1365  parChk.Bind(wx.EVT_CHECKBOX, self.OnSetValue)
1366  which_sizer.Add(item = parChk, proportion = 0,
1367  flag = wx.LEFT, border = 20)
1368 
1369  if title_txt is not None:
1370  # create tooltip if given
1371  if len(p['values_desc']) > 0:
1372  if tooltip:
1373  tooltip += 2 * os.linesep
1374  else:
1375  tooltip = ''
1376  if len(p['values']) == len(p['values_desc']):
1377  for i in range(len(p['values'])):
1378  tooltip += p['values'][i] + ': ' + p['values_desc'][i] + os.linesep
1379  tooltip.strip(os.linesep)
1380  if tooltip:
1381  title_txt.SetToolTipString(tooltip)
1382 
1383  if p == first_param:
1384  if 'wxId' in p and len(p['wxId']) > 0:
1385  win = self.FindWindowById(p['wxId'][0])
1386  win.SetFocus()
1387 
1388  #
1389  # set widget relations for OnUpdateSelection
1390  #
1391  pMap = None
1392  pLayer = []
1393  pDriver = None
1394  pDatabase = None
1395  pTable = None
1396  pColumn = []
1397  pGroup = None
1398  pSubGroup = None
1399  pDbase = None
1400  pLocation = None
1401  pMapset = None
1402  for p in self.task.params:
1403  if p.get('gisprompt', False) == False:
1404  continue
1405 
1406  prompt = p.get('element', '')
1407  if prompt in ('cell', 'vector'):
1408  name = p.get('name', '')
1409  if name in ('map', 'input'):
1410  pMap = p
1411  elif prompt == 'layer':
1412  pLayer.append(p)
1413  elif prompt == 'dbcolumn':
1414  pColumn.append(p)
1415  elif prompt == 'dbdriver':
1416  pDriver = p
1417  elif prompt == 'dbname':
1418  pDatabase = p
1419  elif prompt == 'dbtable':
1420  pTable = p
1421  elif prompt == 'group':
1422  pGroup = p
1423  elif prompt == 'subgroup':
1424  pSubGroup = p
1425  elif prompt == 'dbase':
1426  pDbase = p
1427  elif prompt == 'location':
1428  pLocation = p
1429  elif prompt == 'mapset':
1430  pMapset = p
1431 
1432  pColumnIds = []
1433  for p in pColumn:
1434  pColumnIds += p['wxId']
1435  pLayerIds = []
1436  for p in pLayer:
1437  pLayerIds += p['wxId']
1438 
1439  if pMap:
1440  pMap['wxId-bind'] = copy.copy(pColumnIds)
1441  if pLayer:
1442  pMap['wxId-bind'] += pLayerIds
1443  if pLayer:
1444  for p in pLayer:
1445  p['wxId-bind'] = copy.copy(pColumnIds)
1446 
1447  if pDriver and pTable:
1448  pDriver['wxId-bind'] = pTable['wxId']
1449 
1450  if pDatabase and pTable:
1451  pDatabase['wxId-bind'] = pTable['wxId']
1452 
1453  if pTable and pColumnIds:
1454  pTable['wxId-bind'] = pColumnIds
1455 
1456  if pGroup and pSubGroup:
1457  pGroup['wxId-bind'] = pSubGroup['wxId']
1458 
1459  if pDbase and pLocation:
1460  pDbase['wxId-bind'] = pLocation['wxId']
1461 
1462  if pLocation and pMapset:
1463  pLocation['wxId-bind'] = pMapset['wxId']
1464 
1465  if pLocation and pMapset and pMap:
1466  pLocation['wxId-bind'] += pMap['wxId']
1467  pMapset['wxId-bind'] = pMap['wxId']
1468 
1469  #
1470  # determine panel size
1471  #
1472  maxsizes = (0, 0)
1473  for section in sections:
1474  tab[section].SetSizer(tabsizer[section])
1475  tabsizer[section].Fit(tab[section])
1476  tab[section].Layout()
1477  minsecsizes = tabsizer[section].GetSize()
1478  maxsizes = map(lambda x: max(maxsizes[x], minsecsizes[x]), (0, 1))
1479 
1480  # TODO: be less arbitrary with these 600
1481  self.panelMinHeight = 100
1482  self.constrained_size = (min(600, maxsizes[0]) + 25, min(400, maxsizes[1]) + 25)
1483  for section in sections:
1484  tab[section].SetMinSize((self.constrained_size[0], self.panelMinHeight))
1485 
1486  if self.manualTab.IsLoaded():
1487  self.manualTab.SetMinSize((self.constrained_size[0], self.panelMinHeight))
1488 
1489  self.SetSizer(panelsizer)
1490  panelsizer.Fit(self.notebook)
1491 
1492  self.Bind(EVT_DIALOG_UPDATE, self.OnUpdateDialog)
1493 
1494  def _getValue(self, p):
1495  """!Get value or default value of given parameter
1496 
1497  @param p parameter directory
1498  """
1499  if p.get('value', '') != '':
1500  return p['value']
1501  return p.get('default', '')
1502 
1503  def OnFileLoad(self, event):
1504  """!Load file to interactive input"""
1505  me = event.GetId()
1506  win = dict()
1507  for p in self.task.params:
1508  if 'wxId' in p and me in p['wxId']:
1509  win['file'] = self.FindWindowById(p['wxId'][0])
1510  win['text'] = self.FindWindowById(p['wxId'][1])
1511  break
1512 
1513  if not win:
1514  return
1515 
1516  path = win['file'].GetValue()
1517  if not path:
1518  gcmd.GMessage(parent = self,
1519  message = _("Nothing to load."))
1520  return
1521 
1522  data = ''
1523  f = open(path, "r")
1524  try:
1525  data = f.read()
1526  finally:
1527  f.close()
1528 
1529  win['text'].SetValue(data)
1530 
1531  def OnFileSave(self, event):
1532  """!Save interactive input to the file"""
1533  wId = event.GetId()
1534  win = {}
1535  for p in self.task.params:
1536  if wId in p.get('wxId', []):
1537  win['file'] = self.FindWindowById(p['wxId'][0])
1538  win['text'] = self.FindWindowById(p['wxId'][1])
1539  break
1540 
1541  if not win:
1542  return
1543 
1544  text = win['text'].GetValue()
1545  if not text:
1546  gcmd.GMessage(parent = self,
1547  message = _("Nothing to save."))
1548  return
1549 
1550  dlg = wx.FileDialog(parent = self,
1551  message = _("Save input as..."),
1552  defaultDir = os.getcwd(),
1553  style = wx.SAVE | wx.FD_OVERWRITE_PROMPT)
1554 
1555  if dlg.ShowModal() == wx.ID_OK:
1556  path = dlg.GetPath()
1557  f = open(path, "w")
1558  try:
1559  f.write(text + os.linesep)
1560  finally:
1561  f.close()
1562 
1563  win['file'].SetValue(path)
1564 
1565  dlg.Destroy()
1566 
1567  def OnFileText(self, event):
1568  """File input interactively entered"""
1569  text = event.GetString()
1570  p = self.task.get_param(value = event.GetId(), element = 'wxId', raiseError = False)
1571  if not p:
1572  return # should not happen
1573  win = self.FindWindowById(p['wxId'][0])
1574  if text:
1575  filename = win.GetValue()
1576  if not filename:
1577  # outFile = tempfile.NamedTemporaryFile(mode = 'w+b')
1578  filename = grass.tempfile()
1579  win.SetValue(filename)
1580 
1581  f = open(filename, "w")
1582  try:
1583  f.write(text)
1584  if text[-1] != os.linesep:
1585  f.write(os.linesep)
1586  finally:
1587  f.close()
1588  else:
1589  win.SetValue('')
1590 
1591  def OnUpdateDialog(self, event):
1592  for fn, kwargs in event.data.iteritems():
1593  fn(**kwargs)
1594 
1595  self.parent.updateValuesHook()
1596 
1597  def OnVerbosity(self, event):
1598  """!Verbosity level changed"""
1599  verbose = self.FindWindowById(self.task.get_flag('verbose')['wxId'][0])
1600  quiet = self.FindWindowById(self.task.get_flag('quiet')['wxId'][0])
1601  if event.IsChecked():
1602  if event.GetId() == verbose.GetId():
1603  if quiet.IsChecked():
1604  quiet.SetValue(False)
1605  self.task.get_flag('quiet')['value'] = False
1606  else:
1607  if verbose.IsChecked():
1608  verbose.SetValue(False)
1609  self.task.get_flag('verbose')['value'] = False
1610 
1611  event.Skip()
1612 
1613  def OnPageChange(self, event):
1614  if not event:
1615  sel = self.notebook.GetSelection()
1616  else:
1617  sel = event.GetSelection()
1618 
1619  idx = self.notebook.GetPageIndexByName('manual')
1620  if idx > -1 and sel == idx:
1621  # calling LoadPage() is strangely time-consuming (only first call)
1622  # FIXME: move to helpPage.__init__()
1623  if not self.manualTab.IsLoaded():
1624  wx.Yield()
1625  self.manualTab.LoadPage()
1626 
1627  self.Layout()
1628 
1629  def OnColorChange(self, event):
1630  myId = event.GetId()
1631  for p in self.task.params:
1632  if 'wxId' in p and myId in p['wxId']:
1633  multiple = p['wxId'][1] is not None # multiple colors
1634  hasTransp = p['wxId'][2] is not None
1635  if multiple:
1636  # selected color is added at the end of textCtrl
1637  colorchooser = wx.FindWindowById(p['wxId'][0])
1638  new_color = colorchooser.GetValue()[:]
1639  new_label = rgb2str.get(new_color, ':'.join(map(str, new_color)))
1640  textCtrl = wx.FindWindowById(p['wxId'][1])
1641  val = textCtrl.GetValue()
1642  sep = ','
1643  if val and val[-1] != sep:
1644  val += sep
1645  val += new_label
1646  textCtrl.SetValue(val)
1647  p[ 'value' ] = val
1648  elif hasTransp and wx.FindWindowById(p['wxId'][2]).GetValue():
1649  p[ 'value' ] = 'none'
1650  else:
1651  colorchooser = wx.FindWindowById(p['wxId'][0])
1652  new_color = colorchooser.GetValue()[:]
1653  # This is weird: new_color is a 4-tuple and new_color[:] is a 3-tuple
1654  # under wx2.8.1
1655  new_label = rgb2str.get(new_color, ':'.join(map(str,new_color)))
1656  colorchooser.SetLabel(new_label)
1657  colorchooser.SetColour(new_color)
1658  colorchooser.Refresh()
1659  p[ 'value' ] = colorchooser.GetLabel()
1660  self.OnUpdateValues()
1661 
1662  def OnUpdateValues(self, event = None):
1663  """!If we were part of a richer interface, report back the
1664  current command being built.
1665 
1666  This method should be set by the parent of this panel if
1667  needed. It's a hook, actually. Beware of what is 'self' in
1668  the method def, though. It will be called with no arguments.
1669  """
1670  pass
1671 
1672  def OnCheckBoxMulti(self, event):
1673  """!Fill the values as a ','-separated string according to
1674  current status of the checkboxes.
1675  """
1676  me = event.GetId()
1677  theParam = None
1678  for p in self.task.params:
1679  if 'wxId' in p and me in p['wxId']:
1680  theParam = p
1681  myIndex = p['wxId'].index(me)
1682 
1683  # Unpack current value list
1684  currentValues = {}
1685  for isThere in theParam.get('value', '').split(','):
1686  currentValues[isThere] = 1
1687  theValue = theParam['values'][myIndex]
1688 
1689  if event.Checked():
1690  currentValues[ theValue ] = 1
1691  else:
1692  del currentValues[ theValue ]
1693 
1694  # Keep the original order, so that some defaults may be recovered
1695  currentValueList = []
1696  for v in theParam['values']:
1697  if v in currentValues:
1698  currentValueList.append(v)
1699 
1700  # Pack it back
1701  theParam['value'] = ','.join(currentValueList)
1702 
1703  self.OnUpdateValues()
1704 
1705  def OnSetValue(self, event):
1706  """!Retrieve the widget value and set the task value field
1707  accordingly.
1708 
1709  Use for widgets that have a proper GetValue() method, i.e. not
1710  for selectors.
1711  """
1712  myId = event.GetId()
1713  me = wx.FindWindowById(myId)
1714  name = me.GetName()
1715 
1716  found = False
1717  for porf in self.task.params + self.task.flags:
1718  if 'wxId' not in porf:
1719  continue
1720  if myId in porf['wxId']:
1721  found = True
1722  break
1723 
1724  if not found:
1725  return
1726 
1727  if name in ('DriverSelect', 'TableSelect',
1728  'LocationSelect', 'MapsetSelect', 'ProjSelect'):
1729  porf['value'] = me.GetStringSelection()
1730  elif name == 'GdalSelect':
1731  porf['value'] = event.dsn
1732  elif name == 'ModelParam':
1733  porf['parameterized'] = me.IsChecked()
1734  elif name == 'LayerSelect':
1735  porf['value'] = me.GetValue()
1736  else:
1737  porf['value'] = me.GetValue()
1738 
1739  self.OnUpdateValues(event)
1740 
1741  event.Skip()
1742 
1743  def OnSetSymbol(self, event):
1744  """!Shows dialog for symbol selection"""
1745  myId = event.GetId()
1746 
1747  for p in self.task.params:
1748  if 'wxId' in p and myId in p['wxId']:
1749  from gui_core.dialogs import SymbolDialog
1750  dlg = SymbolDialog(self, symbolPath = globalvar.ETCSYMBOLDIR,
1751  currentSymbol = p['value'])
1752  if dlg.ShowModal() == wx.ID_OK:
1753  img = dlg.GetSelectedSymbolPath()
1754  p['value'] = dlg.GetSelectedSymbolName()
1755 
1756  bitmapButton = wx.FindWindowById(p['wxId'][0])
1757  label = wx.FindWindowById(p['wxId'][1])
1758 
1759  bitmapButton.SetBitmapLabel(wx.Bitmap(img + '.png'))
1760  label.SetLabel(p['value'])
1761 
1762  self.OnUpdateValues(event)
1763 
1764  dlg.Destroy()
1765 
1766  def OnUpdateSelection(self, event):
1767  """!Update dialog (layers, tables, columns, etc.)
1768  """
1769  if not hasattr(self.parent, "updateThread"):
1770  if event:
1771  event.Skip()
1772  return
1773  if event:
1774  self.parent.updateThread.Update(UpdateDialog,
1775  self,
1776  event,
1777  event.GetId(),
1778  self.task)
1779  else:
1780  self.parent.updateThread.Update(UpdateDialog,
1781  self,
1782  None,
1783  None,
1784  self.task)
1785 
1786  def createCmd(self, ignoreErrors = False, ignoreRequired = False):
1787  """!Produce a command line string (list) or feeding into GRASS.
1788 
1789  @param ignoreErrors True then it will return whatever has been
1790  built so far, even though it would not be a correct command
1791  for GRASS
1792  """
1793  try:
1794  cmd = self.task.get_cmd(ignoreErrors = ignoreErrors,
1795  ignoreRequired = ignoreRequired)
1796  except ValueError, err:
1797  dlg = wx.MessageDialog(parent = self,
1798  message = unicode(err),
1799  caption = _("Error in %s") % self.task.name,
1800  style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
1801  dlg.ShowModal()
1802  dlg.Destroy()
1803  cmd = None
1804 
1805  return cmd
1806 
1807  def OnSize(self, event):
1808  width = event.GetSize()[0]
1809  fontsize = self.GetFont().GetPointSize()
1810  text_width = max(width / (fontsize - 3), 70)
1811 
1812  for id in self.label_id:
1813  win = self.FindWindowById(id)
1814  label = win.GetLabel()
1815  label_new = '\n'.join(textwrap.wrap(label, text_width))
1816  win.SetLabel(label_new)
1817 
1818  event.Skip()
1819 
1820 class GUI:
1821  def __init__(self, parent = None, show = True, modal = False,
1822  centreOnParent = False, checkError = False):
1823  """!Parses GRASS commands when module is imported and used from
1824  Layer Manager.
1825  """
1826  self.parent = parent
1827  self.show = show
1828  self.modal = modal
1829  self.centreOnParent = centreOnParent
1830  self.checkError = checkError
1831 
1832  self.grass_task = None
1833  self.cmd = list()
1834 
1835  global _blackList
1836  if self.parent:
1837  _blackList['enabled'] = True
1838  else:
1839  _blackList['enabled'] = False
1840 
1841  def GetCmd(self):
1842  """!Get validated command"""
1843  return self.cmd
1844 
1845  def ParseCommand(self, cmd, gmpath = None, completed = None):
1846  """!Parse command
1847 
1848  Note: cmd is given as list
1849 
1850  If command is given with options, return validated cmd list:
1851  - add key name for first parameter if not given
1852  - change mapname to mapname@mapset
1853  """
1854  start = time.time()
1855  dcmd_params = {}
1856  if completed == None:
1857  get_dcmd = None
1858  layer = None
1859  dcmd_params = None
1860  else:
1861  get_dcmd = completed[0]
1862  layer = completed[1]
1863  if completed[2]:
1864  dcmd_params.update(completed[2])
1865 
1866  # parse the interface decription
1867  try:
1868  global _blackList
1869  self.grass_task = gtask.parse_interface(gcmd.GetRealCmd(cmd[0]),
1870  blackList = _blackList)
1871  except (grass.ScriptError, ValueError), e:
1872  raise gcmd.GException(e.value)
1873 
1874  # if layer parameters previously set, re-insert them into dialog
1875  if completed is not None:
1876  if 'params' in dcmd_params:
1877  self.grass_task.params = dcmd_params['params']
1878  if 'flags' in dcmd_params:
1879  self.grass_task.flags = dcmd_params['flags']
1880 
1881  err = list()
1882  # update parameters if needed && validate command
1883  if len(cmd) > 1:
1884  i = 0
1885  cmd_validated = [cmd[0]]
1886  for option in cmd[1:]:
1887  if option[0] == '-': # flag
1888  if option[1] == '-':
1889  self.grass_task.set_flag(option[2:], True)
1890  else:
1891  self.grass_task.set_flag(option[1], True)
1892  cmd_validated.append(option)
1893  else: # parameter
1894  try:
1895  key, value = option.split('=', 1)
1896  except:
1897  params = self.grass_task.get_options()['params']
1898  if params:
1899  if i == 0: # add key name of first parameter if not given
1900  key = params[0]['name']
1901  value = option
1902  else:
1903  raise gcmd.GException, _("Unable to parse command '%s'") % ' '.join(cmd)
1904  else:
1905  continue
1906 
1907  element = self.grass_task.get_param(key, raiseError = False)
1908  if not element:
1909  err.append(_("%(cmd)s: parameter '%(key)s' not available") % \
1910  { 'cmd' : cmd[0],
1911  'key' : key })
1912  continue
1913  element = element['element']
1914 
1915  if element in ['cell', 'vector']:
1916  # mapname -> mapname@mapset
1917  if '@' not in value:
1918  mapset = grass.find_file(value, element)['mapset']
1919  curr_mapset = grass.gisenv()['MAPSET']
1920  if mapset and mapset != curr_mapset:
1921  value = value + '@' + mapset
1922 
1923  self.grass_task.set_param(key, value)
1924  cmd_validated.append(key + '=' + value)
1925  i += 1
1926 
1927  # update original command list
1928  cmd = cmd_validated
1929 
1930  if self.show is not None:
1931  self.mf = TaskFrame(parent = self.parent, ID = wx.ID_ANY,
1932  task_description = self.grass_task,
1933  get_dcmd = get_dcmd, layer = layer)
1934  else:
1935  self.mf = None
1936 
1937  if get_dcmd is not None:
1938  # update only propwin reference
1939  get_dcmd(dcmd = None, layer = layer, params = None,
1940  propwin = self.mf)
1941 
1942  if self.show is not None:
1943  self.mf.notebookpanel.OnUpdateSelection(None)
1944  if self.show is True:
1945  if self.parent and self.centreOnParent:
1946  self.mf.CentreOnParent()
1947  else:
1948  self.mf.CenterOnScreen()
1949  self.mf.Show(self.show)
1950  self.mf.MakeModal(self.modal)
1951  else:
1952  self.mf.OnApply(None)
1953 
1954  self.cmd = cmd
1955 
1956  if self.checkError:
1957  return self.grass_task, err
1958  else:
1959  return self.grass_task
1960 
1962  """!Get parameter key for input raster/vector map
1963 
1964  @param cmd module name
1965 
1966  @return parameter key
1967  @return None on failure
1968  """
1969  # parse the interface decription
1970  if not self.grass_task:
1971  enc = locale.getdefaultlocale()[1]
1972  if enc and enc.lower() == "cp932":
1973  p = re.compile('encoding="' + enc + '"', re.IGNORECASE)
1974  tree = etree.fromstring(p.sub('encoding="utf-8"',
1975  gtask.get_interface_description(cmd).decode(enc).encode('utf-8')))
1976  else:
1977  tree = etree.fromstring(gtask.get_interface_description(cmd))
1978  self.grass_task = gtask.processTask(tree).get_task()
1979 
1980  for p in self.grass_task.params:
1981  if p.get('name', '') in ('input', 'map'):
1982  age = p.get('age', '')
1983  prompt = p.get('prompt', '')
1984  element = p.get('element', '')
1985  if age == 'old' and \
1986  element in ('cell', 'grid3', 'vector') and \
1987  prompt in ('raster', '3d-raster', 'vector'):
1988  return p.get('name', None)
1989  return None
1990 
1991 class GrassGUIApp(wx.App):
1992  """!Stand-alone GRASS command GUI
1993  """
1994  def __init__(self, grass_task):
1995  self.grass_task = grass_task
1996  wx.App.__init__(self, False)
1997 
1998  def OnInit(self):
1999  msg = self.grass_task.get_error_msg()
2000  if msg:
2001  gcmd.GError(msg + '\n\nTry to set up GRASS_ADDON_PATH variable.')
2002  return True
2003 
2004  self.mf = TaskFrame(parent = None, ID = wx.ID_ANY, task_description = self.grass_task)
2005  self.mf.CentreOnScreen()
2006  self.mf.Show(True)
2007  self.SetTopWindow(self.mf)
2008 
2009  return True
2010 
2011 if __name__ == "__main__":
2012  import gettext
2013  gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
2014 
2015  if len(sys.argv) == 1:
2016  sys.exit(_("usage: %s <grass command>") % sys.argv[0])
2017 
2018  if sys.argv[1] != 'test':
2019  q = wx.LogNull()
2020  cmd = utils.split(sys.argv[1])
2021  task = gtask.grassTask(gcmd.GetRealCmd(cmd[0]))
2022  task.set_options(cmd[1:])
2023  app = GrassGUIApp(task)
2024  app.MainLoop()
2025  else: #Test
2026  # Test grassTask from within a GRASS session
2027  if os.getenv("GISBASE") is not None:
2028  task = gtask.grassTask("d.vect")
2029  task.get_param('map')['value'] = "map_name"
2030  task.get_flag('v')['value'] = True
2031  task.get_param('layer')['value'] = 1
2032  task.get_param('bcolor')['value'] = "red"
2033  assert ' '.join(task.get_cmd()) == "d.vect -v map = map_name layer = 1 bcolor = red"
2034  # Test interface building with handmade grassTask,
2035  # possibly outside of a GRASS session.
2036  task = gtask.grassTask()
2037  task.name = "TestTask"
2038  task.description = "This is an artificial grassTask() object intended for testing purposes."
2039  task.keywords = ["grass","test","task"]
2040  task.params = [
2041  {
2042  "name" : "text",
2043  "description" : "Descriptions go into tooltips if labels are present, like this one",
2044  "label" : "Enter some text",
2045  },{
2046  "name" : "hidden_text",
2047  "description" : "This text should not appear in the form",
2048  "hidden" : True
2049  },{
2050  "name" : "text_default",
2051  "description" : "Enter text to override the default",
2052  "default" : "default text"
2053  },{
2054  "name" : "text_prefilled",
2055  "description" : "You should see a friendly welcome message here",
2056  "value" : "hello, world"
2057  },{
2058  "name" : "plain_color",
2059  "description" : "This is a plain color, and it is a compulsory parameter",
2060  "required" : False,
2061  "gisprompt" : True,
2062  "prompt" : "color"
2063  },{
2064  "name" : "transparent_color",
2065  "description" : "This color becomes transparent when set to none",
2066  "guisection" : "tab",
2067  "gisprompt" : True,
2068  "prompt" : "color"
2069  },{
2070  "name" : "multi",
2071  "description" : "A multiple selection",
2072  'default': u'red,green,blue',
2073  'gisprompt': False,
2074  'guisection': 'tab',
2075  'multiple': u'yes',
2076  'type': u'string',
2077  'value': '',
2078  'values': ['red', 'green', u'yellow', u'blue', u'purple', u'other']
2079  },{
2080  "name" : "single",
2081  "description" : "A single multiple-choice selection",
2082  'values': ['red', 'green', u'yellow', u'blue', u'purple', u'other'],
2083  "guisection" : "tab"
2084  },{
2085  "name" : "large_multi",
2086  "description" : "A large multiple selection",
2087  "gisprompt" : False,
2088  "multiple" : "yes",
2089  # values must be an array of strings
2090  "values" : str2rgb.keys() + map(str, str2rgb.values())
2091  },{
2092  "name" : "a_file",
2093  "description" : "A file selector",
2094  "gisprompt" : True,
2095  "element" : "file"
2096  }
2097  ]
2098  task.flags = [
2099  {
2100  "name" : "a",
2101  "description" : "Some flag, will appear in Main since it is required",
2102  "required" : True
2103  },{
2104  "name" : "b",
2105  "description" : "pre-filled flag, will appear in options since it is not required",
2106  "value" : True
2107  },{
2108  "name" : "hidden_flag",
2109  "description" : "hidden flag, should not be changeable",
2110  "hidden" : "yes",
2111  "value" : True
2112  }
2113  ]
2114  q = wx.LogNull()
2115  GrassGUIApp(task).MainLoop()
2116 
def decode
Definition: core.py:80
def createCmd
Produce a command line string (list) or feeding into GRASS.
Definition: forms.py:1786
def normalize_whitespace
Remove redundant whitespace from a string.
Definition: core/utils.py:33
def GetValue
Definition: widgets.py:118
def GetCommandInputMapParamKey
Get parameter key for input raster/vector map.
Definition: forms.py:1961
def __init__
Definition: forms.py:348
def OnSetSymbol
Shows dialog for symbol selection.
Definition: forms.py:1743
def OnColorChange
Definition: forms.py:1629
def OnFileLoad
Load file to interactive input.
Definition: forms.py:1503
def createCmd
Create command string (python list)
Definition: forms.py:719
#define min(x, y)
Definition: draw2.c:68
Creates combo box for selecting data layers defined for vector.
Definition: gselect.py:661
def OnSetValue
Retrieve the widget value and set the task value field accordingly.
Definition: forms.py:1705
def ParseCommand
Parse command.
Definition: forms.py:1845
Core GUI widgets.
grass_task
Definition: forms.py:1832
centreOnParent
Definition: forms.py:1829
def OnPageChange
Definition: forms.py:1613
checkError
Definition: forms.py:1830
def SetValue
Definition: widgets.py:115
#define max(x, y)
Definition: draw2.c:69
def UpdateDialog
Definition: forms.py:294
def _getValue
Get value or default value of given parameter.
Definition: forms.py:1494
def text_beautify
Definition: forms.py:138
def OnUpdateSelection
Update dialog (layers, tables, columns, etc.)
Definition: forms.py:1766
def escape_ampersand
Escapes ampersands with additional ampersand for GUI.
Definition: forms.py:150
def __init__
Parses GRASS commands when module is imported and used from Layer Manager.
Definition: forms.py:1822
Various dialogs used in wxGUI.
def OnVerbosity
Verbosity level changed.
Definition: forms.py:1597
def GetRealCmd
Return real command name - only for MS Windows.
Definition: gcmd.py:59
def split
Platform spefic shlex.split.
Definition: core/utils.py:37
def OnOK
OK button pressed.
Definition: forms.py:619
Widget for selecting input raster/vector map used by r.proj/v.proj modules.
Definition: gselect.py:1783
def GetCmd
Get validated command.
Definition: forms.py:1841
Creates combo box for selecting attribute tables from the database.
Definition: gselect.py:742
Widget for selecting GRASS Database.
Definition: gselect.py:875
def OnAbort
Abort running command.
Definition: forms.py:670
Creates combo box for selecting database driver.
Definition: gselect.py:717
def OnApply
Apply the command.
Definition: forms.py:625
def color_resolve
Definition: forms.py:124
Widget for selecting GRASS location.
Definition: gselect.py:885
Run command in separate thread.
Definition: gcmd.py:305
Update dialog widgets in the thread.
Definition: forms.py:297
def OnRun
Run the command.
Definition: forms.py:642
Stand-alone GRASS command GUI.
Definition: forms.py:1991
def OnFileSave
Save interactive input to the file.
Definition: forms.py:1531
Help window.
Widget for selecting GRASS mapset.
Definition: gselect.py:911
Widget for selecting subgroups.
Definition: gselect.py:966
goutput
add &#39;command output&#39; tab regardless standalone dialog
Definition: forms.py:787
Creates combo box for selecting columns in the attribute table for a vector map.
Definition: gselect.py:781
def updateValuesHook
Update status bar data.
Definition: forms.py:551
def OnCopy
Copy the command.
Definition: forms.py:676
Creates combo box for selecting database driver.
Definition: gselect.py:731
def OnCheckBoxMulti
Fill the values as a &#39;,&#39;-separated string according to current status of the checkboxes.
Definition: forms.py:1672
def __init__
Definition: forms.py:728
def OnDone
This function is launched from OnRun() when command is finished.
Definition: forms.py:582
def OnUpdateValues
If we were part of a richer interface, report back the current command being built.
Definition: forms.py:1662
def OnCancel
Cancel button pressed.
Definition: forms.py:689
Update dialog widgets in the thread.
Definition: forms.py:154
def OnUpdateDialog
Definition: forms.py:1591
Default GUI settings.
tuple range
Definition: tools.py:1406
Class providing information about attribute tables linked to a vector map.
Definition: gselect.py:549
def OnKeyUp
Key released (check hot-keys)
Definition: forms.py:557
def OnHelp
Show manual page (switch to the &#39;Manual&#39; notebook page)
Definition: forms.py:710
tuple fn
if textDict[&#39;border&#39;] != &#39;none&#39; and not rot: units = UnitConversion(self) borderWidth = units...
def OnFileText
Definition: forms.py:1567
Command output widgets.
This is the Frame containing the dialog for options input.
Definition: forms.py:333
A panel containing a notebook dividing in tabs the different guisections of the GRASS cmd...
Definition: forms.py:724