GRASS Programmer's Manual  6.5.svn(2014)-r66266
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
prompt.py
Go to the documentation of this file.
1 """!
2 @package gui_core.prompt
3 
4 @brief wxGUI command prompt
5 
6 Classes:
7  - prompt::PromptListCtrl
8  - prompt::TextCtrlAutoComplete
9  - prompt::GPrompt
10  - prompt::GPromptPopUp
11  - prompt::GPromptSTC
12 
13 (C) 2009-2011 by the GRASS Development Team
14 
15 This program is free software under the GNU General Public License
16 (>=v2). Read the file COPYING that comes with GRASS for details.
17 
18 @author Martin Landa <landa.martin gmail.com>
19 @author Michael Barton <michael.barton@asu.edu>
20 @author Vaclav Petras <wenzeslaus gmail.com> (copy&paste customization)
21 """
22 
23 import os
24 import sys
25 import difflib
26 import codecs
27 
28 import wx
29 import wx.stc
30 import wx.lib.mixins.listctrl as listmix
31 
32 from grass.script import core as grass
33 from grass.script import task as gtask
34 
35 from core import globalvar
36 from core import utils
37 from lmgr.menudata import ManagerData
38 from core.gcmd import EncodeString, DecodeString, GetRealCmd
39 
40 class PromptListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
41  """!PopUp window used by GPromptPopUp"""
42  def __init__(self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition,
43  size = wx.DefaultSize, style = 0):
44  wx.ListCtrl.__init__(self, parent, id, pos, size, style)
45  listmix.ListCtrlAutoWidthMixin.__init__(self)
46 
47 class TextCtrlAutoComplete(wx.ComboBox, listmix.ColumnSorterMixin):
48  """!Auto complete text area used by GPromptPopUp"""
49  def __init__ (self, parent, statusbar,
50  id = wx.ID_ANY, choices = [], **kwargs):
51  """!Constructor works just like wx.TextCtrl except you can pass in a
52  list of choices. You can also change the choice list at any time
53  by calling setChoices.
54 
55  Inspired by http://wiki.wxpython.org/TextCtrlAutoComplete
56  """
57  self.statusbar = statusbar
58 
59  if 'style' in kwargs:
60  kwargs['style'] = wx.TE_PROCESS_ENTER | kwargs['style']
61  else:
62  kwargs['style'] = wx.TE_PROCESS_ENTER
63 
64  wx.ComboBox.__init__(self, parent, id, **kwargs)
65 
66  # some variables
67  self._choices = choices
68  self._hideOnNoMatch = True
69  self._module = None # currently selected module
70  self._choiceType = None # type of choice (module, params, flags, raster, vector ...)
71  self._screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
72  self._historyItem = 0 # last item
73 
74  # sort variable needed by listmix
75  self.itemDataMap = dict()
76 
77  # widgets
78  try:
79  self.dropdown = wx.PopupWindow(self)
80  except NotImplementedError:
81  self.Destroy()
82  raise NotImplementedError
83 
84  # create the list and bind the events
85  self.dropdownlistbox = PromptListCtrl(parent = self.dropdown,
86  style = wx.LC_REPORT | wx.LC_SINGLE_SEL | \
87  wx.LC_SORT_ASCENDING | wx.LC_NO_HEADER,
88  pos = wx.Point(0, 0))
89 
90  listmix.ColumnSorterMixin.__init__(self, 1)
91 
92  # set choices (list of GRASS modules)
93  self._choicesCmd = globalvar.grassCmd
94  self._choicesMap = dict()
95  for type in ('raster', 'vector'):
96  self._choicesMap[type] = grass.list_strings(type = type[:4])
97  # first search for GRASS module
98  self.SetChoices(self._choicesCmd)
99 
100  self.SetMinSize(self.GetSize())
101 
102  # bindings...
103  self.Bind(wx.EVT_KILL_FOCUS, self.OnControlChanged)
104  self.Bind(wx.EVT_TEXT, self.OnEnteredText)
105  self.Bind(wx.EVT_TEXT_ENTER, self.OnRunCmd)
106  self.Bind(wx.EVT_KEY_DOWN , self.OnKeyDown)
107  ### self.Bind(wx.EVT_LEFT_DOWN, self.OnClick)
108 
109  # if need drop down on left click
110  self.dropdown.Bind(wx.EVT_LISTBOX , self.OnListItemSelected, self.dropdownlistbox)
111  self.dropdownlistbox.Bind(wx.EVT_LEFT_DOWN, self.OnListClick)
112  self.dropdownlistbox.Bind(wx.EVT_LEFT_DCLICK, self.OnListDClick)
113  self.dropdownlistbox.Bind(wx.EVT_LIST_COL_CLICK, self.OnListColClick)
114 
115  self.Bind(wx.EVT_COMBOBOX, self.OnCommandSelect)
116 
117  def _updateDataList(self, choices):
118  """!Update data list"""
119  # delete, if need, all the previous data
120  if self.dropdownlistbox.GetColumnCount() != 0:
121  self.dropdownlistbox.DeleteAllColumns()
122  self.dropdownlistbox.DeleteAllItems()
123  # and update the dict
124  if choices:
125  for numVal, data in enumerate(choices):
126  self.itemDataMap[numVal] = data
127  else:
128  numVal = 0
129  self.SetColumnCount(numVal)
130 
131  def _setListSize(self):
132  """!Set list size"""
133  choices = self._choices
134  longest = 0
135  for choice in choices:
136  longest = max(len(choice), longest)
137  longest += 3
138  itemcount = min(len( choices ), 7) + 2
139  charheight = self.dropdownlistbox.GetCharHeight()
140  charwidth = self.dropdownlistbox.GetCharWidth()
141  self.popupsize = wx.Size(charwidth*longest, charheight*itemcount)
142  self.dropdownlistbox.SetSize(self.popupsize)
143  self.dropdown.SetClientSize(self.popupsize)
144 
145  def _showDropDown(self, show = True):
146  """!Either display the drop down list (show = True) or hide it
147  (show = False).
148  """
149  if show:
150  size = self.dropdown.GetSize()
151  width, height = self.GetSizeTuple()
152  x, y = self.ClientToScreenXY(0, height)
153  if size.GetWidth() != width:
154  size.SetWidth(width)
155  self.dropdown.SetSize(size)
156  self.dropdownlistbox.SetSize(self.dropdown.GetClientSize())
157  if (y + size.GetHeight()) < self._screenheight:
158  self.dropdown.SetPosition(wx.Point(x, y))
159  else:
160  self.dropdown.SetPosition(wx.Point(x, y - height - size.GetHeight()))
161 
162  self.dropdown.Show(show)
163 
164  def _listItemVisible(self):
165  """!Moves the selected item to the top of the list ensuring it is
166  always visible.
167  """
168  toSel = self.dropdownlistbox.GetFirstSelected()
169  if toSel == -1:
170  return
171  self.dropdownlistbox.EnsureVisible(toSel)
172 
173  def _setModule(self, name):
174  """!Set module's choices (flags, parameters)"""
175  # get module's description
176  if name in self._choicesCmd and not self._module:
177  try:
178  self._module = gtask.parse_interface(name)
179  except IOError:
180  self._module = None
181 
182  # set choices (flags)
183  self._choicesMap['flag'] = self._module.get_list_flags()
184  for idx in range(len(self._choicesMap['flag'])):
185  item = self._choicesMap['flag'][idx]
186  desc = self._module.get_flag(item)['label']
187  if not desc:
188  desc = self._module.get_flag(item)['description']
189 
190  self._choicesMap['flag'][idx] = '%s (%s)' % (item, desc)
191 
192  # set choices (parameters)
193  self._choicesMap['param'] = self._module.get_list_params()
194  for idx in range(len(self._choicesMap['param'])):
195  item = self._choicesMap['param'][idx]
196  desc = self._module.get_param(item)['label']
197  if not desc:
198  desc = self._module.get_param(item)['description']
199 
200  self._choicesMap['param'][idx] = '%s (%s)' % (item, desc)
201 
202  def _setValueFromSelected(self):
203  """!Sets the wx.TextCtrl value from the selected wx.ListCtrl item.
204  Will do nothing if no item is selected in the wx.ListCtrl.
205  """
206  sel = self.dropdownlistbox.GetFirstSelected()
207  if sel < 0:
208  return
209 
210  if self._colFetch != -1:
211  col = self._colFetch
212  else:
213  col = self._colSearch
214  itemtext = self.dropdownlistbox.GetItem(sel, col).GetText()
215 
216  cmd = utils.split(str(self.GetValue()))
217  if len(cmd) > 0 and cmd[0] in self._choicesCmd:
218  # -> append text (skip last item)
219  if self._choiceType == 'param':
220  itemtext = itemtext.split(' ')[0]
221  self.SetValue(' '.join(cmd) + ' ' + itemtext + '=')
222  optType = self._module.get_param(itemtext)['prompt']
223  if optType in ('raster', 'vector'):
224  # -> raster/vector map
225  self.SetChoices(self._choicesMap[optType], optType)
226  elif self._choiceType == 'flag':
227  itemtext = itemtext.split(' ')[0]
228  if len(itemtext) > 1:
229  prefix = '--'
230  else:
231  prefix = '-'
232  self.SetValue(' '.join(cmd[:-1]) + ' ' + prefix + itemtext)
233  elif self._choiceType in ('raster', 'vector'):
234  self.SetValue(' '.join(cmd[:-1]) + ' ' + cmd[-1].split('=', 1)[0] + '=' + itemtext)
235  else:
236  # -> reset text
237  self.SetValue(itemtext + ' ')
238 
239  # define module
240  self._setModule(itemtext)
241 
242  # use parameters as default choices
243  self._choiceType = 'param'
244  self.SetChoices(self._choicesMap['param'], type = 'param')
245 
246  self.SetInsertionPointEnd()
247 
248  self._showDropDown(False)
249 
250  def GetListCtrl(self):
251  """!Method required by listmix.ColumnSorterMixin"""
252  return self.dropdownlistbox
253 
254  def SetChoices(self, choices, type = 'module'):
255  """!Sets the choices available in the popup wx.ListBox.
256  The items will be sorted case insensitively.
257 
258  @param choices list of choices
259  @param type type of choices (module, param, flag, raster, vector)
260  """
261  self._choices = choices
262  self._choiceType = type
263 
264  self.dropdownlistbox.SetWindowStyleFlag(wx.LC_REPORT | wx.LC_SINGLE_SEL |
265  wx.LC_SORT_ASCENDING | wx.LC_NO_HEADER)
266  if not isinstance(choices, list):
267  self._choices = [ x for x in choices ]
268  if self._choiceType not in ('raster', 'vector'):
269  # do not sort raster/vector maps
271 
272  self._updateDataList(self._choices)
273 
274  self.dropdownlistbox.InsertColumn(0, "")
275  for num, colVal in enumerate(self._choices):
276  index = self.dropdownlistbox.InsertImageStringItem(sys.maxint, colVal, -1)
277  self.dropdownlistbox.SetStringItem(index, 0, colVal)
278  self.dropdownlistbox.SetItemData(index, num)
279  self._setListSize()
280 
281  # there is only one choice for both search and fetch if setting a single column:
282  self._colSearch = 0
283  self._colFetch = -1
284 
285  def OnClick(self, event):
286  """Left mouse button pressed"""
287  sel = self.dropdownlistbox.GetFirstSelected()
288  if not self.dropdown.IsShown():
289  if sel > -1:
290  self.dropdownlistbox.Select(sel)
291  else:
292  self.dropdownlistbox.Select(0)
293  self._listItemVisible()
294  self._showDropDown()
295  else:
296  self.dropdown.Hide()
297 
298  def OnCommandSelect(self, event):
299  """!Command selected from history"""
300  self._historyItem = event.GetSelection() - len(self.GetItems())
301  self.SetFocus()
302 
303  def OnListClick(self, evt):
304  """!Left mouse button pressed"""
305  toSel, flag = self.dropdownlistbox.HitTest( evt.GetPosition() )
306  #no values on poition, return
307  if toSel == -1: return
308  self.dropdownlistbox.Select(toSel)
309 
310  def OnListDClick(self, evt):
311  """!Mouse button double click"""
312  self._setValueFromSelected()
313 
314  def OnListColClick(self, evt):
315  """!Left mouse button pressed on column"""
316  col = evt.GetColumn()
317  # reverse the sort
318  if col == self._colSearch:
319  self._ascending = not self._ascending
320  self.SortListItems( evt.GetColumn(), ascending=self._ascending )
321  self._colSearch = evt.GetColumn()
322  evt.Skip()
323 
324  def OnListItemSelected(self, event):
325  """!Item selected"""
326  self._setValueFromSelected()
327  event.Skip()
328 
329  def OnEnteredText(self, event):
330  """!Text entered"""
331  text = event.GetString()
332 
333  if not text:
334  # control is empty; hide dropdown if shown:
335  if self.dropdown.IsShown():
336  self._showDropDown(False)
337  event.Skip()
338  return
339 
340  try:
341  cmd = utils.split(str(text))
342  except ValueError, e:
343  self.statusbar.SetStatusText(str(e))
344  cmd = text.split(' ')
345  pattern = str(text)
346 
347  if len(cmd) > 0 and cmd[0] in self._choicesCmd and not self._module:
348  self._setModule(cmd[0])
349  elif len(cmd) > 1 and cmd[0] in self._choicesCmd:
350  if self._module:
351  if len(cmd[-1].split('=', 1)) == 1:
352  # new option
353  if cmd[-1][0] == '-':
354  # -> flags
355  self.SetChoices(self._choicesMap['flag'], type = 'flag')
356  pattern = cmd[-1].lstrip('-')
357  else:
358  # -> options
359  self.SetChoices(self._choicesMap['param'], type = 'param')
360  pattern = cmd[-1]
361  else:
362  # value
363  pattern = cmd[-1].split('=', 1)[1]
364  else:
365  # search for GRASS modules
366  if self._module:
367  # -> switch back to GRASS modules list
368  self.SetChoices(self._choicesCmd)
369  self._module = None
370  self._choiceType = None
371 
372  self._choiceType
373  self._choicesMap
374  found = False
375  choices = self._choices
376  for numCh, choice in enumerate(choices):
377  if choice.lower().startswith(pattern):
378  found = True
379  if found:
380  self._showDropDown(True)
381  item = self.dropdownlistbox.GetItem(numCh)
382  toSel = item.GetId()
383  self.dropdownlistbox.Select(toSel)
384  break
385 
386  if not found:
387  self.dropdownlistbox.Select(self.dropdownlistbox.GetFirstSelected(), False)
388  if self._hideOnNoMatch:
389  self._showDropDown(False)
390  if self._module and '=' not in cmd[-1]:
391  message = ''
392  if cmd[-1][0] == '-': # flag
393  message = _("Warning: flag <%(flag)s> not found in '%(module)s'") % \
394  { 'flag' : cmd[-1][1:], 'module' : self._module.name }
395  else: # option
396  message = _("Warning: option <%(param)s> not found in '%(module)s'") % \
397  { 'param' : cmd[-1], 'module' : self._module.name }
398  self.statusbar.SetStatusText(message)
399 
400  if self._module and len(cmd[-1]) == 2 and cmd[-1][-2] == '=':
401  optType = self._module.get_param(cmd[-1][:-2])['prompt']
402  if optType in ('raster', 'vector'):
403  # -> raster/vector map
404  self.SetChoices(self._choicesMap[optType], optType)
405 
406  self._listItemVisible()
407 
408  event.Skip()
409 
410  def OnKeyDown (self, event):
411  """!Do some work when the user press on the keys: up and down:
412  move the cursor left and right: move the search
413  """
414  skip = True
415  sel = self.dropdownlistbox.GetFirstSelected()
416  visible = self.dropdown.IsShown()
417  KC = event.GetKeyCode()
418 
419  if KC == wx.WXK_RIGHT:
420  # right -> show choices
421  if sel < (self.dropdownlistbox.GetItemCount() - 1):
422  self.dropdownlistbox.Select(sel + 1)
423  self._listItemVisible()
424  self._showDropDown()
425  skip = False
426  elif KC == wx.WXK_UP:
427  if visible:
428  if sel > 0:
429  self.dropdownlistbox.Select(sel - 1)
430  self._listItemVisible()
431  self._showDropDown()
432  skip = False
433  else:
434  self._historyItem -= 1
435  try:
436  self.SetValue(self.GetItems()[self._historyItem])
437  except IndexError:
438  self._historyItem += 1
439  elif KC == wx.WXK_DOWN:
440  if visible:
441  if sel < (self.dropdownlistbox.GetItemCount() - 1):
442  self.dropdownlistbox.Select(sel + 1)
443  self._listItemVisible()
444  self._showDropDown()
445  skip = False
446  else:
447  if self._historyItem < -1:
448  self._historyItem += 1
449  self.SetValue(self.GetItems()[self._historyItem])
450 
451  if visible:
452  if event.GetKeyCode() == wx.WXK_RETURN:
453  self._setValueFromSelected()
454  skip = False
455  if event.GetKeyCode() == wx.WXK_ESCAPE:
456  self._showDropDown(False)
457  skip = False
458  if skip:
459  event.Skip()
460 
461  def OnControlChanged(self, event):
462  """!Control changed"""
463  if self.IsShown():
464  self._showDropDown(False)
465 
466  event.Skip()
467 
468 class GPrompt(object):
469  """!Abstract class for interactive wxGUI prompt
470 
471  See subclass GPromptPopUp and GPromptSTC.
472  """
473  def __init__(self, parent):
474  self.parent = parent # GMConsole
475  self.panel = self.parent.GetPanel()
476 
477  if self.parent.parent.GetName() not in ("LayerManager", "Modeler"):
478  self.standAlone = True
479  else:
480  self.standAlone = False
481 
482  # dictionary of modules (description, keywords, ...)
483  if not self.standAlone:
484  if self.parent.parent.GetName() == 'Modeler':
485  self.moduleDesc = ManagerData().GetModules()
486  else:
487  self.moduleDesc = parent.parent.menubar.GetData().GetModules()
489  self.mapList = self._getListOfMaps()
491  else:
492  self.moduleDesc = self.moduleList = self.mapList = None
493 
494  # auto complete items
495  self.autoCompList = list()
496  self.autoCompFilter = None
497 
498  # command description (gtask.grassTask)
499  self.cmdDesc = None
500  self.cmdbuffer = self._readHistory()
501  self.cmdindex = len(self.cmdbuffer)
502 
503  # list of traced commands
504  self.commands = list()
505 
506  def _readHistory(self):
507  """!Get list of commands from history file"""
508  hist = list()
509  env = grass.gisenv()
510  try:
511  fileHistory = codecs.open(os.path.join(env['GISDBASE'],
512  env['LOCATION_NAME'],
513  env['MAPSET'],
514  '.bash_history'),
515  encoding = 'utf-8', mode = 'r', errors='replace')
516  except IOError:
517  return hist
518 
519  try:
520  for line in fileHistory.readlines():
521  hist.append(line.replace('\n', ''))
522  finally:
523  fileHistory.close()
524 
525  return hist
526 
527  def GetCommandDesc(self, cmd):
528  """!Get description for given command"""
529  if cmd in self.moduleDesc:
530  return self.moduleDesc[cmd]['desc']
531 
532  return ''
533 
534  def GetCommandItems(self):
535  """!Get list of available commands"""
536  items = list()
537 
538  if self.autoCompFilter is not None:
539  mList = self.autoCompFilter
540  else:
541  mList = self.moduleList
542 
543  if not mList:
544  return items
545 
546  prefixes = mList.keys()
547  prefixes.sort()
548 
549  for prefix in prefixes:
550  for command in mList[prefix]:
551  name = prefix + '.' + command
552  if name not in items:
553  items.append(name)
554 
555  items.sort()
556 
557  return items
558 
559  def _getListOfModules(self):
560  """!Get list of modules"""
561  result = dict()
562  for module in globalvar.grassCmd:
563  try:
564  group, name = module.split('.',1)
565  except ValueError:
566  continue # TODO
567 
568  if group not in result:
569  result[group] = list()
570  result[group].append(name)
571 
572  # for better auto-completion:
573  # not only result['r']={...,'colors.out',...}, but also result['r.colors']={'out',...}
574  for i in range(len(name.split('.'))-1):
575  group = '.'.join([group,name.split('.',1)[0]])
576  name = name.split('.',1)[1]
577  if group not in result:
578  result[group] = list()
579  result[group].append(name)
580 
581  # sort list of names
582  for group in result.keys():
583  result[group].sort()
584 
585  return result
586 
587  def _getListOfMaps(self):
588  """!Get list of maps"""
589  result = dict()
590  result['raster'] = grass.list_strings('rast')
591  result['vector'] = grass.list_strings('vect')
592 
593  return result
594 
595  def _runCmd(self, cmdString):
596  """!Run command
597 
598  @param cmdString command to run (given as a string)
599  """
600  if self.parent.GetName() == "ModelerDialog":
601  self.parent.OnOk(None)
602  return
603 
604  if not cmdString or self.standAlone:
605  return
606 
607  if cmdString[:2] == 'd.' and not self.parent.parent.GetMapDisplay():
608  self.parent.parent.NewDisplay(show = True)
609 
610  self.commands.append(cmdString) # trace commands
611 
612  # parse command into list
613  try:
614  cmd = utils.split(str(cmdString))
615  except UnicodeError:
616  cmd = utils.split(EncodeString((cmdString)))
617  cmd = map(DecodeString, cmd)
618 
619  # send the command list to the processor
620  if cmd[0] in ('r.mapcalc', 'r3.mapcalc') and len(cmd) == 1:
621  self.parent.parent.OnMapCalculator(event = None, cmd = cmd)
622  else:
623  self.parent.RunCmd(cmd)
624 
625  # add command to history & clean prompt
626  self.UpdateCmdHistory(cmd)
627  self.OnCmdErase(None)
628  self.parent.parent.statusbar.SetStatusText('')
629 
630  def OnUpdateStatusBar(self, event):
631  """!Update Layer Manager status bar"""
632  if self.standAlone:
633  return
634 
635  if event is None:
636  self.parent.parent.statusbar.SetStatusText("")
637  else:
638  self.parent.parent.statusbar.SetStatusText(_("Type GRASS command and run by pressing ENTER"))
639  event.Skip()
640 
641  def GetPanel(self):
642  """!Get main widget panel"""
643  return self.panel
644 
645  def GetInput(self):
646  """!Get main prompt widget"""
647  return self.input
648 
649  def SetFilter(self, data, module = True):
650  """!Set filter
651 
652  @param data data dict
653  @param module True to filter modules, otherwise data
654  """
655  if module:
656  if data:
657  self.moduleList = data
658  else:
659  self.moduleList = self._getListOfModules()
660  else:
661  if data:
662  self.dataList = data
663  else:
664  self.dataList = self._getListOfMaps()
665 
666  def GetCommands(self):
667  """!Get list of launched commands"""
668  return self.commands
669 
670  def ClearCommands(self):
671  """!Clear list of commands"""
672  del self.commands[:]
673 
675  """!Interactive wxGUI prompt - popup version"""
676  def __init__(self, parent):
677  GPrompt.__init__(self, parent)
678 
679  ### todo: fix TextCtrlAutoComplete to work also on Macs
680  ### reason: missing wx.PopupWindow()
681  try:
682  TextCtrlAutoComplete.__init__(self, parent = self.panel, id = wx.ID_ANY,
683  value = "",
684  style = wx.TE_LINEWRAP | wx.TE_PROCESS_ENTER,
685  statusbar = self.parent.parent.statusbar)
686  self.SetItems(self._readHistory())
687  except NotImplementedError:
688  # wx.PopupWindow may be not available in wxMac
689  # see http://trac.wxwidgets.org/ticket/9377
690  wx.TextCtrl.__init__(parent = self.panel, id = wx.ID_ANY,
691  value = "",
692  style=wx.TE_LINEWRAP | wx.TE_PROCESS_ENTER,
693  size = (-1, 25))
694  self.searchBy.Enable(False)
695  self.search.Enable(False)
696 
697  self.SetFont(wx.Font(10, wx.FONTFAMILY_MODERN, wx.NORMAL, wx.NORMAL, 0, ''))
698 
699  wx.CallAfter(self.SetInsertionPoint, 0)
700 
701  # bidnings
702  self.Bind(wx.EVT_TEXT_ENTER, self.OnRunCmd)
703  self.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
704 
705  def OnCmdErase(self, event):
706  """!Erase command prompt"""
707  self.input.SetValue('')
708 
709  def OnRunCmd(self, event):
710  """!Run command"""
711  self._runCmd(event.GetString())
712 
713 class GPromptSTC(GPrompt, wx.stc.StyledTextCtrl):
714  """!Styled wxGUI prompt with autocomplete and calltips"""
715  def __init__(self, parent, id = wx.ID_ANY, margin = False):
716  GPrompt.__init__(self, parent)
717  wx.stc.StyledTextCtrl.__init__(self, self.panel, id)
718 
719  #
720  # styles
721  #
722  self.SetWrapMode(True)
723  self.SetUndoCollection(True)
724 
725  #
726  # create command and map lists for autocompletion
727  #
728  self.AutoCompSetIgnoreCase(False)
729 
730  #
731  # line margins
732  #
733  # TODO print number only from cmdlog
734  self.SetMarginWidth(1, 0)
735  self.SetMarginWidth(2, 0)
736  if margin:
737  self.SetMarginType(0, wx.stc.STC_MARGIN_NUMBER)
738  self.SetMarginWidth(0, 30)
739  else:
740  self.SetMarginWidth(0, 0)
741 
742  #
743  # miscellaneous
744  #
745  self.SetViewWhiteSpace(False)
746  self.SetUseTabs(False)
747  self.UsePopUp(True)
748  self.SetSelBackground(True, "#FFFF00")
749  self.SetUseHorizontalScrollBar(True)
750 
751  #
752  # bindings
753  #
754  self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
755  self.Bind(wx.EVT_CHAR, self.OnChar)
756  self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
757  self.Bind(wx.stc.EVT_STC_AUTOCOMP_SELECTION, self.OnItemSelected)
758  self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemChanged)
759 
760  def OnTextSelectionChanged(self, event):
761  """!Copy selected text to clipboard and skip event.
762  The same function is in GMStc class (goutput.py).
763  """
764  self.Copy()
765  event.Skip()
766 
767  def OnItemChanged(self, event):
768  """!Change text in statusbar
769  if the item selection in the auto-completion list is changed"""
770  # list of commands
771  if self.toComplete['entity'] == 'command':
772  item = self.toComplete['cmd'].rpartition('.')[0] + '.' + self.autoCompList[event.GetIndex()]
773  try:
774  desc = self.moduleDesc[item]['desc']
775  except KeyError:
776  desc = ''
777  self.ShowStatusText(desc)
778  # list of flags
779  elif self.toComplete['entity'] == 'flags':
780  desc = self.cmdDesc.get_flag(self.autoCompList[event.GetIndex()])['description']
781  self.ShowStatusText(desc)
782  # list of parameters
783  elif self.toComplete['entity'] == 'params':
784  item = self.cmdDesc.get_param(self.autoCompList[event.GetIndex()])
785  desc = item['name'] + '=' + item['type']
786  if not item['required']:
787  desc = '[' + desc + ']'
788  desc += ': ' + item['description']
789  self.ShowStatusText(desc)
790  # list of flags and commands
791  elif self.toComplete['entity'] == 'params+flags':
792  if self.autoCompList[event.GetIndex()][0] == '-':
793  desc = self.cmdDesc.get_flag(self.autoCompList[event.GetIndex()].strip('-'))['description']
794  else:
795  item = self.cmdDesc.get_param(self.autoCompList[event.GetIndex()])
796  desc = item['name'] + '=' + item['type']
797  if not item['required']:
798  desc = '[' + desc + ']'
799  desc += ': ' + item['description']
800  self.ShowStatusText(desc)
801  else:
802  self.ShowStatusText('')
803 
804  def OnItemSelected(self, event):
805  """!Item selected from the list"""
806  lastWord = self.GetWordLeft()
807  # to insert selection correctly if selected word partly matches written text
808  match = difflib.SequenceMatcher(None, event.GetText(), lastWord)
809  matchTuple = match.find_longest_match(0, len(event.GetText()), 0, len(lastWord))
810 
811  compl = event.GetText()[matchTuple[2]:]
812  text = self.GetTextLeft() + compl
813  # add space or '=' at the end
814  end = '='
815  for char in ('.','-','='):
816  if text.split(' ')[-1].find(char) >= 0:
817  end = ' '
818 
819  compl += end
820  text += end
821 
822  self.AddText(compl)
823  pos = len(text)
824  self.SetCurrentPos(pos)
825 
826  cmd = text.strip().split(' ')[0]
827 
828  if not self.cmdDesc or cmd != self.cmdDesc.get_name():
829  if cmd in ('r.mapcalc', 'v.type'):
830  cmd = cmd + '_wrapper'
831 
832  if cmd in ('r.mapcalc', 'r3.mapcalc') and \
833  self.parent.parent.GetName() == 'LayerManager':
834  self.parent.parent.OnMapCalculator(event = None, cmd = [cmd])
835  # add command to history & clean prompt
836  self.UpdateCmdHistory([cmd])
837  self.OnCmdErase(None)
838  else:
839  try:
840  self.cmdDesc = gtask.parse_interface(GetRealCmd(cmd))
841  except IOError:
842  self.cmdDesc = None
843 
844  def UpdateCmdHistory(self, cmd):
845  """!Update command history
846 
847  @param cmd command given as a list
848  """
849  # add command to history
850  self.cmdbuffer.append(' '.join(cmd))
851 
852  # keep command history to a managable size
853  if len(self.cmdbuffer) > 200:
854  del self.cmdbuffer[0]
855  self.cmdindex = len(self.cmdbuffer)
856 
857  def EntityToComplete(self):
858  """!Determines which part of command (flags, parameters) should
859  be completed at current cursor position"""
860  entry = self.GetTextLeft()
861  toComplete = dict(cmd=None, entity=None)
862  try:
863  cmd = entry.split()[0].strip()
864  except IndexError:
865  return toComplete
866 
867  try:
868  splitted = utils.split(str(entry))
869  except ValueError: # No closing quotation error
870  return toComplete
871  if len(splitted) > 0 and cmd in globalvar.grassCmd:
872  toComplete['cmd'] = cmd
873  if entry[-1] == ' ':
874  words = entry.split(' ')
875  if any(word.startswith('-') for word in words):
876  toComplete['entity'] = 'params'
877  else:
878  toComplete['entity'] = 'params+flags'
879  else:
880  # get word left from current position
881  word = self.GetWordLeft(withDelimiter = True)
882 
883  if word[0] == '=' and word[-1] == '@':
884  toComplete['entity'] = 'mapsets'
885  elif word[0] == '=':
886  # get name of parameter
887  paramName = self.GetWordLeft(withDelimiter = False, ignoredDelimiter = '=').strip('=')
888  if paramName:
889  try:
890  param = self.cmdDesc.get_param(paramName)
891  except (ValueError, AttributeError):
892  return toComplete
893  else:
894  return toComplete
895 
896  if param['values']:
897  toComplete['entity'] = 'param values'
898  elif param['prompt'] == 'raster' and param['element'] == 'cell':
899  toComplete['entity'] = 'raster map'
900  elif param['prompt'] == 'vector' and param['element'] == 'vector':
901  toComplete['entity'] = 'vector map'
902  elif word[0] == '-':
903  toComplete['entity'] = 'flags'
904  elif word[0] == ' ':
905  toComplete['entity'] = 'params'
906  else:
907  toComplete['entity'] = 'command'
908  toComplete['cmd'] = cmd
909 
910  return toComplete
911 
912  def GetWordLeft(self, withDelimiter = False, ignoredDelimiter = None):
913  """!Get word left from current cursor position. The beginning
914  of the word is given by space or chars: .,-=
915 
916  @param withDelimiter returns the word with the initial delimeter
917  @param ignoredDelimiter finds the word ignoring certain delimeter
918  """
919  textLeft = self.GetTextLeft()
920 
921  parts = list()
922  if ignoredDelimiter is None:
923  ignoredDelimiter = ''
924 
925  for char in set(' .,-=') - set(ignoredDelimiter):
926  if not withDelimiter:
927  delimiter = ''
928  else:
929  delimiter = char
930  parts.append(delimiter + textLeft.rpartition(char)[2])
931  return min(parts, key=lambda x: len(x))
932 
933  def ShowList(self):
934  """!Show sorted auto-completion list if it is not empty"""
935  if len(self.autoCompList) > 0:
936  self.autoCompList.sort()
937  self.AutoCompShow(lenEntered = 0, itemList = ' '.join(self.autoCompList))
938 
939  def OnKeyPressed(self, event):
940  """!Key pressed capture special treatment for tabulator to show help"""
941  pos = self.GetCurrentPos()
942  if event.GetKeyCode() == wx.WXK_TAB:
943  # show GRASS command calltips (to hide press 'ESC')
944  entry = self.GetTextLeft()
945  try:
946  cmd = entry.split()[0].strip()
947  except IndexError:
948  cmd = ''
949 
950  if cmd not in globalvar.grassCmd:
951  return
952 
953  info = gtask.command_info(GetRealCmd(cmd))
954 
955  self.CallTipSetBackground("#f4f4d1")
956  self.CallTipSetForeground("BLACK")
957  self.CallTipShow(pos, info['usage'] + '\n\n' + info['description'])
958  elif event.GetKeyCode() in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER) and \
959  not self.AutoCompActive():
960  # run command on line when <return> is pressed
961  self._runCmd(self.GetCurLine()[0].strip())
962  elif event.GetKeyCode() in [wx.WXK_UP, wx.WXK_DOWN] and \
963  not self.AutoCompActive():
964  # Command history using up and down
965  if len(self.cmdbuffer) < 1:
966  return
967 
968  self.DocumentEnd()
969 
970  # move through command history list index values
971  if event.GetKeyCode() == wx.WXK_UP:
972  self.cmdindex = self.cmdindex - 1
973  if event.GetKeyCode() == wx.WXK_DOWN:
974  self.cmdindex = self.cmdindex + 1
975  if self.cmdindex < 0:
976  self.cmdindex = 0
977  if self.cmdindex > len(self.cmdbuffer) - 1:
978  self.cmdindex = len(self.cmdbuffer) - 1
979 
980  try:
981  txt = self.cmdbuffer[self.cmdindex]
982  except KeyError:
983  txt = ''
984 
985  # clear current line and insert command history
986  self.DelLineLeft()
987  self.DelLineRight()
988  pos = self.GetCurrentPos()
989  self.InsertText(pos, txt)
990  self.LineEnd()
991 
992  self.ShowStatusText('')
993  else:
994  event.Skip()
995 
996  def OnChar(self, event):
997  """!Key char capture for autocompletion, calltips, and command history
998 
999  @todo event.ControlDown() for manual autocomplete
1000  """
1001  # keycodes used: "." = 46, "=" = 61, "-" = 45
1002  pos = self.GetCurrentPos()
1003  # complete command after pressing '.'
1004  if event.GetKeyCode() == 46:
1005  self.autoCompList = list()
1006  entry = self.GetTextLeft()
1007  self.InsertText(pos, '.')
1008  self.CharRight()
1010  try:
1011  if self.toComplete['entity'] == 'command':
1012  for command in globalvar.grassCmd:
1013  try:
1014  if command.find(self.toComplete['cmd']) == 0:
1015  dotNumber = list(self.toComplete['cmd']).count('.')
1016  self.autoCompList.append(command.split('.',dotNumber)[-1])
1017  except UnicodeDecodeError, e: # TODO: fix it
1018  sys.stderr.write(DecodeString(command) + ": " + unicode(e))
1019 
1020  except (KeyError, TypeError):
1021  return
1022  self.ShowList()
1023 
1024  # complete flags after pressing '-'
1025  elif (event.GetKeyCode() == 45) \
1026  or event.GetKeyCode() == wx.WXK_NUMPAD_SUBTRACT \
1027  or event.GetKeyCode() == wx.WXK_SUBTRACT:
1028  self.autoCompList = list()
1029  entry = self.GetTextLeft()
1030  self.InsertText(pos, '-')
1031  self.CharRight()
1032  self.toComplete = self.EntityToComplete()
1033  if self.toComplete['entity'] == 'flags' and self.cmdDesc:
1034  if self.GetTextLeft()[-2:] == ' -': # complete e.g. --quite
1035  for flag in self.cmdDesc.get_options()['flags']:
1036  if len(flag['name']) == 1:
1037  self.autoCompList.append(flag['name'])
1038  else:
1039  for flag in self.cmdDesc.get_options()['flags']:
1040  if len(flag['name']) > 1:
1041  self.autoCompList.append(flag['name'])
1042  self.ShowList()
1043 
1044  # complete map or values after parameter
1045  elif event.GetKeyCode() == 61:
1046  self.autoCompList = list()
1047  self.InsertText(pos, '=')
1048  self.CharRight()
1049  self.toComplete = self.EntityToComplete()
1050  if self.toComplete['entity'] == 'raster map':
1051  self.autoCompList = self.mapList['raster']
1052  elif self.toComplete['entity'] == 'vector map':
1053  self.autoCompList = self.mapList['vector']
1054  elif self.toComplete['entity'] == 'param values':
1055  param = self.GetWordLeft(withDelimiter = False, ignoredDelimiter='=').strip(' =')
1056  self.autoCompList = self.cmdDesc.get_param(param)['values']
1057  self.ShowList()
1058 
1059  # complete mapset ('@')
1060  elif event.GetKeyCode() == 64:
1061  self.autoCompList = list()
1062  self.InsertText(pos, '@')
1063  self.CharRight()
1064  self.toComplete = self.EntityToComplete()
1065 
1066  if self.toComplete['entity'] == 'mapsets':
1067  self.autoCompList = self.mapsetList
1068  self.ShowList()
1069 
1070  # complete after pressing CTRL + Space
1071  elif event.GetKeyCode() == wx.WXK_SPACE and event.ControlDown():
1072  self.autoCompList = list()
1073  self.toComplete = self.EntityToComplete()
1074  if self.toComplete is None:
1075  return
1076 
1077  #complete command
1078  if self.toComplete['entity'] == 'command':
1079  for command in globalvar.grassCmd:
1080  if command.find(self.toComplete['cmd']) == 0:
1081  dotNumber = list(self.toComplete['cmd']).count('.')
1082  self.autoCompList.append(command.split('.',dotNumber)[-1])
1083 
1084 
1085  # complete flags in such situations (| is cursor):
1086  # r.colors -| ...w, q, l
1087  # r.colors -w| ...w, q, l
1088  elif self.toComplete['entity'] == 'flags' and self.cmdDesc:
1089  for flag in self.cmdDesc.get_options()['flags']:
1090  if len(flag['name']) == 1:
1091  self.autoCompList.append(flag['name'])
1092 
1093  # complete parameters in such situations (| is cursor):
1094  # r.colors -w | ...color, map, rast, rules
1095  # r.colors col| ...color
1096  elif self.toComplete['entity'] == 'params' and self.cmdDesc:
1097  for param in self.cmdDesc.get_options()['params']:
1098  if param['name'].find(self.GetWordLeft(withDelimiter=False)) == 0:
1099  self.autoCompList.append(param['name'])
1100 
1101  # complete flags or parameters in such situations (| is cursor):
1102  # r.colors | ...-w, -q, -l, color, map, rast, rules
1103  # r.colors color=grey | ...-w, -q, -l, color, map, rast, rules
1104  elif self.toComplete['entity'] == 'params+flags' and self.cmdDesc:
1105  self.autoCompList = list()
1106 
1107  for param in self.cmdDesc.get_options()['params']:
1108  self.autoCompList.append(param['name'])
1109  for flag in self.cmdDesc.get_options()['flags']:
1110  if len(flag['name']) == 1:
1111  self.autoCompList.append('-' + flag['name'])
1112  else:
1113  self.autoCompList.append('--' + flag['name'])
1114 
1115  self.ShowList()
1116 
1117  # complete map or values after parameter
1118  # r.buffer input=| ...list of raster maps
1119  # r.buffer units=| ... feet, kilometers, ...
1120  elif self.toComplete['entity'] == 'raster map':
1121  self.autoCompList = list()
1122  self.autoCompList = self.mapList['raster']
1123  elif self.toComplete['entity'] == 'vector map':
1124  self.autoCompList = list()
1125  self.autoCompList = self.mapList['vector']
1126  elif self.toComplete['entity'] == 'param values':
1127  self.autoCompList = list()
1128  param = self.GetWordLeft(withDelimiter = False, ignoredDelimiter='=').strip(' =')
1129  self.autoCompList = self.cmdDesc.get_param(param)['values']
1130 
1131  self.ShowList()
1132 
1133  elif event.GetKeyCode() == wx.WXK_SPACE:
1134  items = self.GetTextLeft().split()
1135  if len(items) == 1:
1136  cmd = items[0].strip()
1137  if cmd in globalvar.grassCmd and \
1138  cmd != 'r.mapcalc' and \
1139  (not self.cmdDesc or cmd != self.cmdDesc.get_name()):
1140 
1141  try:
1142  self.cmdDesc = gtask.parse_interface(GetRealCmd(cmd))
1143  except IOError:
1144  self.cmdDesc = None
1145  event.Skip()
1146 
1147  else:
1148  event.Skip()
1149 
1150  def ShowStatusText(self, text):
1151  """!Sets statusbar text, if it's too long, it is cut off"""
1152  maxLen = self.parent.parent.statusbar.GetFieldRect(0).GetWidth()/ 7 # any better way?
1153  if len(text) < maxLen:
1154  self.parent.parent.statusbar.SetStatusText(text)
1155  else:
1156  self.parent.parent.statusbar.SetStatusText(text[:maxLen]+'...')
1157 
1158 
1159  def GetTextLeft(self):
1160  """!Returns all text left of the caret"""
1161  pos = self.GetCurrentPos()
1162  self.HomeExtend()
1163  entry = self.GetSelectedText()
1164  self.SetCurrentPos(pos)
1165 
1166  return entry
1167 
1168  def OnDestroy(self, event):
1169  """!The clipboard contents can be preserved after
1170  the app has exited"""
1171  wx.TheClipboard.Flush()
1172  event.Skip()
1173 
1174  def OnCmdErase(self, event):
1175  """!Erase command prompt"""
1176  self.Home()
1177  self.DelLineRight()
def GetCommandDesc
Get description for given command.
Definition: prompt.py:527
def __init__
Constructor works just like wx.TextCtrl except you can pass in a list of choices. ...
Definition: prompt.py:50
def EncodeString
Return encoded string using system locales.
Definition: gcmd.py:85
def DecodeString
Decode string using system encoding.
Definition: gcmd.py:69
wxGUI command interface
def GetListCtrl
Method required by listmix.ColumnSorterMixin.
Definition: prompt.py:250
PopUp window used by GPromptPopUp.
Definition: prompt.py:40
def __init__
Definition: prompt.py:473
def _listItemVisible
Moves the selected item to the top of the list ensuring it is always visible.
Definition: prompt.py:164
def _getListOfModules
Get list of modules.
Definition: prompt.py:559
#define min(x, y)
Definition: draw2.c:68
def SetChoices
Sets the choices available in the popup wx.ListBox.
Definition: prompt.py:254
def OnTextSelectionChanged
Copy selected text to clipboard and skip event.
Definition: prompt.py:760
def OnCmdErase
Erase command prompt.
Definition: prompt.py:705
def OnUpdateStatusBar
Update Layer Manager status bar.
Definition: prompt.py:630
def ShowStatusText
Sets statusbar text, if it&#39;s too long, it is cut off.
Definition: prompt.py:1150
def _runCmd
Run command.
Definition: prompt.py:595
#define max(x, y)
Definition: draw2.c:69
Interactive wxGUI prompt - popup version.
Definition: prompt.py:674
def GetInput
Get main prompt widget.
Definition: prompt.py:645
def _setListSize
Set list size.
Definition: prompt.py:131
def GetRealCmd
Return real command name - only for MS Windows.
Definition: gcmd.py:59
def GetWordLeft
Get word left from current cursor position.
Definition: prompt.py:912
def split
Platform spefic shlex.split.
Definition: core/utils.py:37
def OnKeyPressed
Key pressed capture special treatment for tabulator to show help.
Definition: prompt.py:939
def ListOfMapsets
Get list of available/accessible mapsets.
Definition: core/utils.py:245
def EntityToComplete
Determines which part of command (flags, parameters) should be completed at current cursor position...
Definition: prompt.py:857
def OnKeyDown
Do some work when the user press on the keys: up and down: move the cursor left and right: move the s...
Definition: prompt.py:410
def OnItemSelected
Item selected from the list.
Definition: prompt.py:804
def _setModule
Set module&#39;s choices (flags, parameters)
Definition: prompt.py:173
def _setValueFromSelected
Sets the wx.TextCtrl value from the selected wx.ListCtrl item.
Definition: prompt.py:202
def ListSortLower
Sort list items (not case-sensitive)
Definition: core/utils.py:287
def OnRunCmd
Run command.
Definition: prompt.py:709
def OnCommandSelect
Command selected from history.
Definition: prompt.py:298
def OnListItemSelected
Item selected.
Definition: prompt.py:324
def OnItemChanged
Change text in statusbar if the item selection in the auto-completion list is changed.
Definition: prompt.py:767
Abstract class for interactive wxGUI prompt.
Definition: prompt.py:468
def GetCommands
Get list of launched commands.
Definition: prompt.py:666
def OnDestroy
The clipboard contents can be preserved after the app has exited.
Definition: prompt.py:1168
def _updateDataList
Update data list.
Definition: prompt.py:117
def _getListOfMaps
Get list of maps.
Definition: prompt.py:587
def OnListClick
Left mouse button pressed.
Definition: prompt.py:303
def GetCommandItems
Get list of available commands.
Definition: prompt.py:534
def OnListColClick
Left mouse button pressed on column.
Definition: prompt.py:314
def ShowList
Show sorted auto-completion list if it is not empty.
Definition: prompt.py:933
def UpdateCmdHistory
Update command history.
Definition: prompt.py:844
def SetFilter
Set filter.
Definition: prompt.py:649
def ClearCommands
Clear list of commands.
Definition: prompt.py:670
def OnEnteredText
Text entered.
Definition: prompt.py:329
def OnListDClick
Mouse button double click.
Definition: prompt.py:310
def _showDropDown
Either display the drop down list (show = True) or hide it (show = False).
Definition: prompt.py:145
def GetTextLeft
Returns all text left of the caret.
Definition: prompt.py:1159
tuple range
Definition: tools.py:1406
def OnChar
Key char capture for autocompletion, calltips, and command history.
Definition: prompt.py:996
Auto complete text area used by GPromptPopUp.
Definition: prompt.py:47
def OnControlChanged
Control changed.
Definition: prompt.py:461
Styled wxGUI prompt with autocomplete and calltips.
Definition: prompt.py:713
string set
def _readHistory
Get list of commands from history file.
Definition: prompt.py:506
def OnCmdErase
Erase command prompt.
Definition: prompt.py:1174
def GetPanel
Get main widget panel.
Definition: prompt.py:641