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