GRASS Programmer's Manual  6.4.2(2012)
prompt.py
Go to the documentation of this file.
00001 """!
00002 @package prompt.py
00003 
00004 @brief wxGUI command prompt
00005 
00006 Classes:
00007  - PromptListCtrl
00008  - TextCtrlAutoComplete
00009  - GPrompt
00010  - GPromptPopUp
00011  - GPromptSTC
00012 
00013 (C) 2009-2011 by the GRASS Development Team
00014 This program is free software under the GNU General Public
00015 License (>=v2). Read the file COPYING that comes with GRASS
00016 for details.
00017 
00018 @author Martin Landa <landa.martin gmail.com>
00019 @author Michael Barton <michael.barton@asu.edu>
00020 @author Vaclav Petras <wenzeslaus gmail.com> (copy&paste customization)
00021 """
00022 
00023 import os
00024 import sys
00025 import copy
00026 import difflib
00027 import codecs
00028 
00029 import wx
00030 import wx.stc
00031 import wx.lib.mixins.listctrl as listmix
00032 
00033 from grass.script import core as grass
00034 from grass.script import task as gtask
00035 
00036 import globalvar
00037 import menudata
00038 import menuform
00039 import gcmd
00040 import utils
00041 
00042 class PromptListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
00043     """!PopUp window used by GPromptPopUp"""
00044     def __init__(self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition,
00045                  size = wx.DefaultSize, style = 0):
00046         wx.ListCtrl.__init__(self, parent, id, pos, size, style)
00047         listmix.ListCtrlAutoWidthMixin.__init__(self)
00048         
00049 class TextCtrlAutoComplete(wx.ComboBox, listmix.ColumnSorterMixin):
00050     """!Auto complete text area used by GPromptPopUp"""
00051     def __init__ (self, parent, statusbar,
00052                   id = wx.ID_ANY, choices = [], **kwargs):
00053         """!Constructor works just like wx.TextCtrl except you can pass in a
00054         list of choices.  You can also change the choice list at any time
00055         by calling setChoices.
00056         
00057         Inspired by http://wiki.wxpython.org/TextCtrlAutoComplete
00058         """
00059         self.statusbar = statusbar
00060         
00061         if 'style' in kwargs:
00062             kwargs['style'] = wx.TE_PROCESS_ENTER | kwargs['style']
00063         else:
00064             kwargs['style'] = wx.TE_PROCESS_ENTER
00065         
00066         wx.ComboBox.__init__(self, parent, id, **kwargs)
00067         
00068         # some variables
00069         self._choices = choices
00070         self._hideOnNoMatch = True
00071         self._module = None      # currently selected module
00072         self._choiceType = None  # type of choice (module, params, flags, raster, vector ...)
00073         self._screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
00074         self._historyItem = 0   # last item
00075         
00076         # sort variable needed by listmix
00077         self.itemDataMap = dict()
00078         
00079         # widgets
00080         try:
00081             self.dropdown = wx.PopupWindow(self)
00082         except NotImplementedError:
00083             self.Destroy()
00084             raise NotImplementedError
00085         
00086         # create the list and bind the events
00087         self.dropdownlistbox = PromptListCtrl(parent = self.dropdown,
00088                                               style = wx.LC_REPORT | wx.LC_SINGLE_SEL | \
00089                                                   wx.LC_SORT_ASCENDING | wx.LC_NO_HEADER,
00090                                               pos = wx.Point(0, 0))
00091         
00092         listmix.ColumnSorterMixin.__init__(self, 1)
00093         
00094         # set choices (list of GRASS modules)
00095         self._choicesCmd = globalvar.grassCmd['all']
00096         self._choicesMap = dict()
00097         for type in ('raster', 'vector'):
00098             self._choicesMap[type] = grass.list_strings(type = type[:4])
00099         # first search for GRASS module
00100         self.SetChoices(self._choicesCmd)
00101         
00102         self.SetMinSize(self.GetSize())
00103         
00104         # bindings...
00105         self.Bind(wx.EVT_KILL_FOCUS, self.OnControlChanged)
00106         self.Bind(wx.EVT_TEXT, self.OnEnteredText)
00107         self.Bind(wx.EVT_TEXT_ENTER, self.OnRunCmd)
00108         self.Bind(wx.EVT_KEY_DOWN , self.OnKeyDown)
00109         ### self.Bind(wx.EVT_LEFT_DOWN, self.OnClick)
00110 
00111         # if need drop down on left click
00112         self.dropdown.Bind(wx.EVT_LISTBOX , self.OnListItemSelected, self.dropdownlistbox)
00113         self.dropdownlistbox.Bind(wx.EVT_LEFT_DOWN, self.OnListClick)
00114         self.dropdownlistbox.Bind(wx.EVT_LEFT_DCLICK, self.OnListDClick)
00115         self.dropdownlistbox.Bind(wx.EVT_LIST_COL_CLICK, self.OnListColClick)
00116 
00117         self.Bind(wx.EVT_COMBOBOX, self.OnCommandSelect)
00118         
00119     def _updateDataList(self, choices):
00120         """!Update data list"""
00121         # delete, if need, all the previous data
00122         if self.dropdownlistbox.GetColumnCount() != 0:
00123             self.dropdownlistbox.DeleteAllColumns()
00124             self.dropdownlistbox.DeleteAllItems()
00125         # and update the dict
00126         if choices:
00127             for numVal, data in enumerate(choices):
00128                 self.itemDataMap[numVal] = data
00129         else:
00130             numVal = 0
00131         self.SetColumnCount(numVal)
00132     
00133     def _setListSize(self):
00134         """!Set list size"""
00135         choices = self._choices
00136         longest = 0
00137         for choice in choices:
00138             longest = max(len(choice), longest)
00139         longest += 3
00140         itemcount = min(len( choices ), 7) + 2
00141         charheight = self.dropdownlistbox.GetCharHeight()
00142         charwidth = self.dropdownlistbox.GetCharWidth()
00143         self.popupsize = wx.Size(charwidth*longest, charheight*itemcount)
00144         self.dropdownlistbox.SetSize(self.popupsize)
00145         self.dropdown.SetClientSize(self.popupsize)
00146         
00147     def _showDropDown(self, show = True):
00148         """!Either display the drop down list (show = True) or hide it
00149         (show = False).
00150         """
00151         if show:
00152             size = self.dropdown.GetSize()
00153             width, height = self.GetSizeTuple()
00154             x, y = self.ClientToScreenXY(0, height)
00155             if size.GetWidth() != width:
00156                 size.SetWidth(width)
00157                 self.dropdown.SetSize(size)
00158                 self.dropdownlistbox.SetSize(self.dropdown.GetClientSize())
00159             if (y + size.GetHeight()) < self._screenheight:
00160                 self.dropdown.SetPosition(wx.Point(x, y))
00161             else:
00162                 self.dropdown.SetPosition(wx.Point(x, y - height - size.GetHeight()))
00163         
00164         self.dropdown.Show(show)
00165     
00166     def _listItemVisible(self):
00167         """!Moves the selected item to the top of the list ensuring it is
00168         always visible.
00169         """
00170         toSel = self.dropdownlistbox.GetFirstSelected()
00171         if toSel == -1:
00172             return
00173         self.dropdownlistbox.EnsureVisible(toSel)
00174 
00175     def _setModule(self, name):
00176         """!Set module's choices (flags, parameters)""" 
00177         # get module's description
00178         if name in self._choicesCmd and not self._module:
00179             try:
00180                 self._module = gtask.parse_interface(name)
00181             except IOError:
00182                 self._module = None
00183              
00184         # set choices (flags)
00185         self._choicesMap['flag'] = self._module.get_list_flags()
00186         for idx in range(len(self._choicesMap['flag'])):
00187             item = self._choicesMap['flag'][idx]
00188             desc = self._module.get_flag(item)['label']
00189             if not desc:
00190                 desc = self._module.get_flag(item)['description']
00191             
00192             self._choicesMap['flag'][idx] = '%s (%s)' % (item, desc)
00193         
00194         # set choices (parameters)
00195         self._choicesMap['param'] = self._module.get_list_params()
00196         for idx in range(len(self._choicesMap['param'])):
00197             item = self._choicesMap['param'][idx]
00198             desc = self._module.get_param(item)['label']
00199             if not desc:
00200                 desc = self._module.get_param(item)['description']
00201             
00202             self._choicesMap['param'][idx] = '%s (%s)' % (item, desc)
00203     
00204     def _setValueFromSelected(self):
00205          """!Sets the wx.TextCtrl value from the selected wx.ListCtrl item.
00206          Will do nothing if no item is selected in the wx.ListCtrl.
00207          """
00208          sel = self.dropdownlistbox.GetFirstSelected()
00209          if sel < 0:
00210              return
00211          
00212          if self._colFetch != -1:
00213              col = self._colFetch
00214          else:
00215              col = self._colSearch
00216          itemtext = self.dropdownlistbox.GetItem(sel, col).GetText()
00217          
00218          cmd = utils.split(str(self.GetValue()))
00219          if len(cmd) > 0 and cmd[0] in self._choicesCmd:
00220              # -> append text (skip last item)
00221              if self._choiceType == 'param':
00222                  itemtext = itemtext.split(' ')[0]
00223                  self.SetValue(' '.join(cmd) + ' ' + itemtext + '=')
00224                  optType = self._module.get_param(itemtext)['prompt']
00225                  if optType in ('raster', 'vector'):
00226                      # -> raster/vector map
00227                      self.SetChoices(self._choicesMap[optType], optType)
00228              elif self._choiceType == 'flag':
00229                  itemtext = itemtext.split(' ')[0]
00230                  if len(itemtext) > 1:
00231                      prefix = '--'
00232                  else:
00233                      prefix = '-'
00234                  self.SetValue(' '.join(cmd[:-1]) + ' ' + prefix + itemtext)
00235              elif self._choiceType in ('raster', 'vector'):
00236                  self.SetValue(' '.join(cmd[:-1]) + ' ' + cmd[-1].split('=', 1)[0] + '=' + itemtext)
00237          else:
00238              # -> reset text
00239              self.SetValue(itemtext + ' ')
00240              
00241              # define module
00242              self._setModule(itemtext)
00243              
00244              # use parameters as default choices
00245              self._choiceType = 'param'
00246              self.SetChoices(self._choicesMap['param'], type = 'param')
00247          
00248          self.SetInsertionPointEnd()
00249          
00250          self._showDropDown(False)
00251          
00252     def GetListCtrl(self):
00253         """!Method required by listmix.ColumnSorterMixin"""
00254         return self.dropdownlistbox
00255     
00256     def SetChoices(self, choices, type = 'module'):
00257         """!Sets the choices available in the popup wx.ListBox.
00258         The items will be sorted case insensitively.
00259 
00260         @param choices list of choices
00261         @param type type of choices (module, param, flag, raster, vector)
00262         """
00263         self._choices = choices
00264         self._choiceType = type
00265         
00266         self.dropdownlistbox.SetWindowStyleFlag(wx.LC_REPORT | wx.LC_SINGLE_SEL |
00267                                                 wx.LC_SORT_ASCENDING | wx.LC_NO_HEADER)
00268         if not isinstance(choices, list):
00269             self._choices = [ x for x in choices ]
00270         if self._choiceType not in ('raster', 'vector'):
00271             # do not sort raster/vector maps
00272             utils.ListSortLower(self._choices)
00273         
00274         self._updateDataList(self._choices)
00275         
00276         self.dropdownlistbox.InsertColumn(0, "")
00277         for num, colVal in enumerate(self._choices):
00278             index = self.dropdownlistbox.InsertImageStringItem(sys.maxint, colVal, -1)
00279             self.dropdownlistbox.SetStringItem(index, 0, colVal)
00280             self.dropdownlistbox.SetItemData(index, num)
00281         self._setListSize()
00282         
00283         # there is only one choice for both search and fetch if setting a single column:
00284         self._colSearch = 0
00285         self._colFetch = -1
00286 
00287     def OnClick(self, event):
00288         """Left mouse button pressed"""
00289         sel = self.dropdownlistbox.GetFirstSelected()
00290         if not self.dropdown.IsShown():
00291             if sel > -1:
00292                 self.dropdownlistbox.Select(sel)
00293             else:
00294                 self.dropdownlistbox.Select(0)
00295             self._listItemVisible()
00296             self._showDropDown()
00297         else:
00298             self.dropdown.Hide()
00299         
00300     def OnCommandSelect(self, event):
00301         """!Command selected from history"""
00302         self._historyItem = event.GetSelection() - len(self.GetItems())
00303         self.SetFocus()
00304         
00305     def OnListClick(self, evt):
00306         """!Left mouse button pressed"""
00307         toSel, flag = self.dropdownlistbox.HitTest( evt.GetPosition() )
00308         #no values on poition, return
00309         if toSel == -1: return
00310         self.dropdownlistbox.Select(toSel)
00311 
00312     def OnListDClick(self, evt):
00313         """!Mouse button double click"""
00314         self._setValueFromSelected()
00315 
00316     def OnListColClick(self, evt):
00317         """!Left mouse button pressed on column"""
00318         col = evt.GetColumn()
00319         # reverse the sort
00320         if col == self._colSearch:
00321             self._ascending = not self._ascending
00322         self.SortListItems( evt.GetColumn(), ascending=self._ascending )
00323         self._colSearch = evt.GetColumn()
00324         evt.Skip()
00325 
00326     def OnListItemSelected(self, event):
00327         """!Item selected"""
00328         self._setValueFromSelected()
00329         event.Skip()
00330 
00331     def OnEnteredText(self, event):
00332         """!Text entered"""
00333         text = event.GetString()
00334         
00335         if not text:
00336             # control is empty; hide dropdown if shown:
00337             if self.dropdown.IsShown():
00338                 self._showDropDown(False)
00339             event.Skip()
00340             return
00341         
00342         try:
00343             cmd = utils.split(str(text))
00344         except ValueError, e:
00345             self.statusbar.SetStatusText(str(e))
00346             cmd = text.split(' ')
00347         pattern = str(text)
00348         
00349         if len(cmd) > 0 and cmd[0] in self._choicesCmd and not self._module:
00350             self._setModule(cmd[0])
00351         elif len(cmd) > 1 and cmd[0] in self._choicesCmd:
00352             if self._module:
00353                 if len(cmd[-1].split('=', 1)) == 1:
00354                     # new option
00355                     if cmd[-1][0] == '-':
00356                         # -> flags
00357                         self.SetChoices(self._choicesMap['flag'], type = 'flag')
00358                         pattern = cmd[-1].lstrip('-')
00359                     else:
00360                         # -> options
00361                         self.SetChoices(self._choicesMap['param'], type = 'param')
00362                         pattern = cmd[-1]
00363                 else:
00364                     # value
00365                     pattern = cmd[-1].split('=', 1)[1]
00366         else:
00367             # search for GRASS modules
00368             if self._module:
00369                 # -> switch back to GRASS modules list
00370                 self.SetChoices(self._choicesCmd)
00371                 self._module = None
00372                 self._choiceType = None
00373         
00374         self._choiceType
00375         self._choicesMap
00376         found = False
00377         choices = self._choices
00378         for numCh, choice in enumerate(choices):
00379             if choice.lower().startswith(pattern):
00380                 found = True
00381             if found:
00382                 self._showDropDown(True)
00383                 item = self.dropdownlistbox.GetItem(numCh)
00384                 toSel = item.GetId()
00385                 self.dropdownlistbox.Select(toSel)
00386                 break
00387         
00388         if not found:
00389             self.dropdownlistbox.Select(self.dropdownlistbox.GetFirstSelected(), False)
00390             if self._hideOnNoMatch:
00391                 self._showDropDown(False)
00392                 if self._module and '=' not in cmd[-1]:
00393                     message = ''
00394                     if cmd[-1][0] == '-': # flag
00395                         message = _("Warning: flag <%(flag)s> not found in '%(module)s'") % \
00396                             { 'flag' : cmd[-1][1:], 'module' : self._module.name }
00397                     else: # option
00398                         message = _("Warning: option <%(param)s> not found in '%(module)s'") % \
00399                             { 'param' : cmd[-1], 'module' : self._module.name }
00400                     self.statusbar.SetStatusText(message)
00401         
00402         if self._module and len(cmd[-1]) == 2 and cmd[-1][-2] == '=':
00403             optType = self._module.get_param(cmd[-1][:-2])['prompt']
00404             if optType in ('raster', 'vector'):
00405                 # -> raster/vector map
00406                 self.SetChoices(self._choicesMap[optType], optType)
00407         
00408         self._listItemVisible()
00409         
00410         event.Skip()
00411         
00412     def OnKeyDown (self, event):
00413         """!Do some work when the user press on the keys: up and down:
00414         move the cursor left and right: move the search
00415         """
00416         skip = True
00417         sel = self.dropdownlistbox.GetFirstSelected()
00418         visible = self.dropdown.IsShown()
00419         KC = event.GetKeyCode()
00420         
00421         if KC == wx.WXK_RIGHT:
00422             # right -> show choices
00423             if sel < (self.dropdownlistbox.GetItemCount() - 1):
00424                 self.dropdownlistbox.Select(sel + 1)
00425                 self._listItemVisible()
00426             self._showDropDown()
00427             skip = False
00428         elif KC == wx.WXK_UP:
00429             if visible:
00430                 if sel > 0:
00431                     self.dropdownlistbox.Select(sel - 1)
00432                     self._listItemVisible()
00433                 self._showDropDown()
00434                 skip = False
00435             else:
00436                 self._historyItem -= 1
00437                 try:
00438                     self.SetValue(self.GetItems()[self._historyItem])
00439                 except IndexError:
00440                     self._historyItem += 1
00441         elif KC == wx.WXK_DOWN:
00442             if visible:
00443                 if sel < (self.dropdownlistbox.GetItemCount() - 1):
00444                     self.dropdownlistbox.Select(sel + 1)
00445                     self._listItemVisible()
00446                 self._showDropDown()
00447                 skip = False
00448             else:
00449                 if self._historyItem < -1:
00450                     self._historyItem += 1
00451                     self.SetValue(self.GetItems()[self._historyItem])
00452         
00453         if visible:
00454             if event.GetKeyCode() == wx.WXK_RETURN:
00455                 self._setValueFromSelected()
00456                 skip = False
00457             if event.GetKeyCode() == wx.WXK_ESCAPE:
00458                 self._showDropDown(False)
00459                 skip = False
00460         if skip:
00461             event.Skip()
00462         
00463     def OnControlChanged(self, event):
00464         """!Control changed"""
00465         if self.IsShown():
00466             self._showDropDown(False)
00467         
00468         event.Skip()
00469 
00470 class GPrompt(object):
00471     """!Abstract class for interactive wxGUI prompt
00472 
00473     See subclass GPromptPopUp and GPromptSTC.
00474     """
00475     def __init__(self, parent):
00476         self.parent = parent                 # GMConsole
00477         self.panel  = self.parent.GetPanel()
00478         
00479         if self.parent.parent.GetName() not in ("LayerManager", "Modeler"):
00480             self.standAlone = True
00481         else:
00482             self.standAlone = False
00483         
00484         # dictionary of modules (description, keywords, ...)
00485         if not self.standAlone:
00486             if self.parent.parent.GetName() == 'Modeler':
00487                 self.moduleDesc = menudata.ManagerData().GetModules()
00488             else:
00489                 self.moduleDesc = parent.parent.menubar.GetData().GetModules()
00490             self.moduleList = self._getListOfModules()
00491             self.mapList = self._getListOfMaps()
00492         else:
00493             self.moduleDesc = self.moduleList = self.mapList = None
00494         
00495         # auto complete items
00496         self.autoCompList   = list()
00497         self.autoCompFilter = None
00498         
00499         # command description (gtask.grassTask)
00500         self.cmdDesc = None
00501         self.cmdbuffer = self._readHistory()
00502         self.cmdindex = len(self.cmdbuffer)
00503         
00504     def _readHistory(self):
00505         """!Get list of commands from history file"""
00506         hist = list()
00507         env = grass.gisenv()
00508         try:
00509             fileHistory = codecs.open(os.path.join(env['GISDBASE'],
00510                                                    env['LOCATION_NAME'],
00511                                                    env['MAPSET'],
00512                                                    '.bash_history'),
00513                                       encoding = 'utf-8', mode = 'r', errors='replace')
00514         except IOError:
00515             return hist
00516         
00517         try:
00518             for line in fileHistory.readlines():
00519                 hist.append(line.replace('\n', ''))
00520         finally:
00521             fileHistory.close()
00522         
00523         return hist
00524 
00525     def GetCommandDesc(self, cmd):
00526         """!Get description for given command"""
00527         if cmd in self.moduleDesc:
00528             return self.moduleDesc[cmd]['desc']
00529         
00530         return ''
00531     
00532     def GetCommandItems(self):
00533         """!Get list of available commands"""
00534         items = list()
00535         
00536         if self.autoCompFilter is not None:
00537             mList = self.autoCompFilter
00538         else:
00539             mList = self.moduleList
00540             
00541         if not mList:
00542             return items
00543         
00544         prefixes = mList.keys()
00545         prefixes.sort()
00546         
00547         for prefix in prefixes:
00548             for command in mList[prefix]:
00549                 name = prefix + '.' + command
00550                 if name not in items:
00551                     items.append(name)
00552                 
00553         items.sort()
00554         
00555         return items
00556     
00557     def _getListOfModules(self):
00558         """!Get list of modules"""
00559         result = dict()
00560         for module in globalvar.grassCmd['all']:
00561             try:
00562                 group, name = module.split('.',1)
00563             except ValueError:
00564                 continue # TODO
00565             
00566             if group not in result:
00567                 result[group] = list()
00568             result[group].append(name)
00569             
00570             # for better auto-completion: 
00571             # not only result['r']={...,'colors.out',...}, but also result['r.colors']={'out',...}
00572             for i in range(len(name.split('.'))-1):
00573                 group = '.'.join([group,name.split('.',1)[0]])
00574                 name = name.split('.',1)[1]
00575                 if group not in result:
00576                     result[group] = list()
00577                 result[group].append(name)
00578       
00579         # sort list of names
00580         for group in result.keys():
00581             result[group].sort()
00582         
00583         return result
00584     
00585     def _getListOfMaps(self):
00586         """!Get list of maps"""
00587         result = dict()
00588         result['raster'] = grass.list_strings('rast')
00589         result['vector'] = grass.list_strings('vect')
00590         
00591         return result
00592 
00593     def OnRunCmd(self, event):
00594         """!Run command"""
00595         cmdString = event.GetString()
00596         
00597         if self.standAlone:
00598             return
00599         
00600         if cmdString[:2] == 'd.' and not self.parent.curr_page:
00601             self.parent.NewDisplay(show=True)
00602         
00603         cmd = utils.split(cmdString)
00604         if len(cmd) > 1:
00605             self.parent.RunCmd(cmd, switchPage = True)
00606         else:
00607             self.parent.RunCmd(cmd, switchPage = False)
00608         
00609         self.OnUpdateStatusBar(None)
00610         
00611     def OnUpdateStatusBar(self, event):
00612         """!Update Layer Manager status bar"""
00613         if self.standAlone:
00614             return
00615         
00616         if event is None:
00617             self.parent.parent.statusbar.SetStatusText("")
00618         else:
00619             self.parent.parent.statusbar.SetStatusText(_("Type GRASS command and run by pressing ENTER"))
00620             event.Skip()
00621         
00622     def GetPanel(self):
00623         """!Get main widget panel"""
00624         return self.panel
00625 
00626     def GetInput(self):
00627         """!Get main prompt widget"""
00628         return self.input
00629     
00630     def SetFilter(self, data, module = True):
00631         """!Set filter
00632 
00633         @param data data dict
00634         @param module True to filter modules, otherwise data
00635         """
00636         if module:
00637             if data:
00638                 self.moduleList = data
00639             else:
00640                 self.moduleList = self._getListOfModules()
00641         else:
00642             if data:
00643                 self.dataList = data
00644             else:
00645                 self.dataList = self._getListOfMaps()
00646         
00647 class GPromptPopUp(GPrompt, TextCtrlAutoComplete):
00648     """!Interactive wxGUI prompt - popup version"""
00649     def __init__(self, parent):
00650         GPrompt.__init__(self, parent)
00651         
00652         ### todo: fix TextCtrlAutoComplete to work also on Macs
00653         ### reason: missing wx.PopupWindow()
00654         try:
00655             TextCtrlAutoComplete.__init__(self, parent = self.panel, id = wx.ID_ANY,
00656                                           value = "",
00657                                           style = wx.TE_LINEWRAP | wx.TE_PROCESS_ENTER,
00658                                           statusbar = self.parent.parent.statusbar)
00659             self.SetItems(self._readHistory())
00660         except NotImplementedError:
00661             # wx.PopupWindow may be not available in wxMac
00662             # see http://trac.wxwidgets.org/ticket/9377
00663             wx.TextCtrl.__init__(parent = self.panel, id = wx.ID_ANY,
00664                                  value = "",
00665                                  style=wx.TE_LINEWRAP | wx.TE_PROCESS_ENTER,
00666                                  size = (-1, 25))
00667             self.searchBy.Enable(False)
00668             self.search.Enable(False)
00669         
00670         self.SetFont(wx.Font(10, wx.FONTFAMILY_MODERN, wx.NORMAL, wx.NORMAL, 0, ''))
00671         
00672         wx.CallAfter(self.SetInsertionPoint, 0)
00673         
00674         # bidnings
00675         self.Bind(wx.EVT_TEXT_ENTER, self.OnRunCmd)
00676         self.Bind(wx.EVT_TEXT,       self.OnUpdateStatusBar)
00677         
00678     def OnCmdErase(self, event):
00679         """!Erase command prompt"""
00680         self.input.SetValue('')
00681 
00682 class GPromptSTC(GPrompt, wx.stc.StyledTextCtrl):
00683     """!Styled wxGUI prompt with autocomplete and calltips"""    
00684     def __init__(self, parent, id = wx.ID_ANY, margin = False):
00685         GPrompt.__init__(self, parent)
00686         wx.stc.StyledTextCtrl.__init__(self, self.panel, id)
00687         
00688         #
00689         # styles
00690         #                
00691         self.SetWrapMode(True)
00692         self.SetUndoCollection(True)        
00693         
00694         #
00695         # create command and map lists for autocompletion
00696         #
00697         self.AutoCompSetIgnoreCase(False) 
00698         
00699         #
00700         # line margins
00701         #
00702         # TODO print number only from cmdlog
00703         self.SetMarginWidth(1, 0)
00704         self.SetMarginWidth(2, 0)
00705         if margin:
00706             self.SetMarginType(0, wx.stc.STC_MARGIN_NUMBER)
00707             self.SetMarginWidth(0, 30)
00708         else:
00709             self.SetMarginWidth(0, 0)
00710         
00711         #
00712         # miscellaneous
00713         #
00714         self.SetViewWhiteSpace(False)
00715         self.SetUseTabs(False)
00716         self.UsePopUp(True)
00717         self.SetSelBackground(True, "#FFFF00")
00718         self.SetUseHorizontalScrollBar(True)
00719         
00720         #
00721         # bindings
00722         #
00723         self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
00724         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
00725         self.Bind(wx.stc.EVT_STC_AUTOCOMP_SELECTION, self.OnItemSelected)
00726         self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemChanged)
00727         
00728     def OnTextSelectionChanged(self, event):
00729         """!Copy selected text to clipboard and skip event.
00730         The same function is in GMStc class (goutput.py).
00731         """
00732         self.Copy()
00733         event.Skip()
00734         
00735     def OnItemChanged(self, event):
00736         """!Change text in statusbar 
00737         if the item selection in the auto-completion list is changed"""
00738         # list of commands
00739         if self.toComplete['entity'] == 'command':
00740             item = self.toComplete['cmd'].rpartition('.')[0] + '.' + self.autoCompList[event.GetIndex()] 
00741             try:
00742                 desc = self.moduleDesc[item]['desc']        
00743             except KeyError:
00744                 desc = '' 
00745             self.ShowStatusText(desc)
00746         # list of flags    
00747         elif self.toComplete['entity'] == 'flags':
00748             desc = self.cmdDesc.get_flag(self.autoCompList[event.GetIndex()])['description']
00749             self.ShowStatusText(desc)
00750         # list of parameters
00751         elif self.toComplete['entity'] == 'params':
00752             item = self.cmdDesc.get_param(self.autoCompList[event.GetIndex()])
00753             desc = item['name'] + '=' + item['type']
00754             if not item['required']:
00755                 desc = '[' + desc + ']'
00756             desc += ': ' + item['description']
00757             self.ShowStatusText(desc)
00758         # list of flags and commands       
00759         elif self.toComplete['entity'] == 'params+flags':
00760             if self.autoCompList[event.GetIndex()][0] == '-':
00761                 desc = self.cmdDesc.get_flag(self.autoCompList[event.GetIndex()].strip('-'))['description']
00762             else:
00763                 item = self.cmdDesc.get_param(self.autoCompList[event.GetIndex()])
00764                 desc = item['name'] + '=' + item['type']
00765                 if not item['required']:
00766                     desc = '[' + desc + ']'
00767                 desc += ': ' + item['description']
00768             self.ShowStatusText(desc)
00769         else:
00770             self.ShowStatusText('')
00771             
00772     def OnItemSelected(self, event):
00773         """!Item selected from the list"""
00774         lastWord = self.GetWordLeft()
00775         # to insert selection correctly if selected word partly matches written text
00776         match = difflib.SequenceMatcher(None, event.GetText(), lastWord)
00777         matchTuple = match.find_longest_match(0, len(event.GetText()), 0, len(lastWord))
00778     
00779         compl = event.GetText()[matchTuple[2]:]
00780         text = self.GetTextLeft() + compl
00781         # add space or '=' at the end
00782         end = '='
00783         for char in ('.','-','='):
00784             if text.split(' ')[-1].find(char) >= 0:
00785                 end = ' '
00786         
00787         compl += end
00788         text += end
00789 
00790         self.AddText(compl)
00791         pos = len(text)
00792         self.SetCurrentPos(pos)
00793         
00794         cmd = text.strip().split(' ')[0]
00795 
00796         if not self.cmdDesc or cmd != self.cmdDesc.get_name():
00797             if cmd in ('r.mapcalc', 'r3.mapcalc'):
00798                 self.parent.parent.OnMapCalculator(event = None, cmd = [cmd])
00799                 # add command to history & clean prompt
00800                 self.UpdateCmdHistory([cmd])
00801                 self.OnCmdErase(None)
00802             else:
00803                 if sys.platform == 'win32':
00804                     if cmd in globalvar.grassCmd['script']:
00805                         cmd += globalvar.EXT_SCT
00806                 try:
00807                     self.cmdDesc = gtask.parse_interface(cmd)
00808                 except IOError:
00809                     self.cmdDesc = None
00810         
00811     def UpdateCmdHistory(self, cmd):
00812         """!Update command history
00813         
00814         @param cmd command given as a list
00815         """
00816         # add command to history    
00817         self.cmdbuffer.append(' '.join(cmd))
00818         
00819         # keep command history to a managable size
00820         if len(self.cmdbuffer) > 200:
00821             del self.cmdbuffer[0]
00822         self.cmdindex = len(self.cmdbuffer)
00823         
00824     def EntityToComplete(self):
00825         """!Determines which part of command (flags, parameters) should
00826         be completed at current cursor position"""
00827         entry = self.GetTextLeft()
00828         toComplete = dict()
00829         try:
00830             cmd = entry.split()[0].strip()
00831         except IndexError:
00832             return None
00833         
00834         if len(entry.split(' ')) > 1:
00835             if cmd in globalvar.grassCmd['all']:
00836                 toComplete['cmd'] = cmd
00837                 if entry[-1] == ' ':
00838                     words = entry.split(' ')
00839                     if any(word.startswith('-') for word in words):
00840                         toComplete['entity'] = 'params'
00841                         return toComplete
00842                     else:
00843                         toComplete['entity'] = 'params+flags'
00844                         return toComplete
00845                     
00846                 else:
00847                     #get word left from current position
00848                     word = self.GetWordLeft(withDelimiter = True)
00849                     if word[0] == '=':
00850                         # get name of parameter
00851                         paramName = self.GetWordLeft(withDelimiter = False, ignoredDelimiter = '=').strip('=')
00852                         if paramName:
00853                             try:
00854                                 param = self.cmdDesc.get_param(paramName)
00855                             except (ValueError, AttributeError):
00856                                 return
00857                         else:
00858                             return
00859 
00860                         if param['values']:
00861                             toComplete['entity'] = 'param values'
00862                             return toComplete
00863                         elif param['prompt'] == 'raster' and param['element'] == 'cell':
00864                             toComplete['entity'] = 'raster map'
00865                             return toComplete
00866                         elif param['prompt'] == 'vector' and param['element'] == 'vector':
00867                             toComplete['entity'] = 'vector map'
00868                             return toComplete
00869                     elif word[0] == '-':
00870                         toComplete['entity'] = 'flags'
00871                         return toComplete
00872                     elif word[0] == ' ':
00873                         toComplete['entity'] = 'params'
00874                         return toComplete
00875                                        
00876             else:
00877                 return None
00878         else:
00879             toComplete['entity'] = 'command'
00880             toComplete['cmd'] = cmd
00881             return toComplete
00882     
00883     def GetWordLeft(self, withDelimiter = False, ignoredDelimiter = None):
00884         """!Get word left from current cursor position. The beginning
00885         of the word is given by space or chars: .,-= 
00886         
00887         @param withDelimiter returns the word with the initial delimeter
00888         @param ignoredDelimiter finds the word ignoring certain delimeter
00889         """
00890         textLeft = self.GetTextLeft()
00891         
00892         parts = list()
00893         if ignoredDelimiter is None:
00894             ignoredDelimiter = ''
00895         
00896         for char in set(' .,-=') - set(ignoredDelimiter):
00897             if not withDelimiter:
00898                 delimiter = ''
00899             else:
00900                 delimiter = char
00901             parts.append(delimiter + textLeft.rpartition(char)[2])
00902         return min(parts, key=lambda x: len(x))
00903          
00904     def ShowList(self):
00905         """!Show sorted auto-completion list if it is not empty"""
00906         if len(self.autoCompList) > 0:
00907             self.autoCompList.sort()
00908             self.AutoCompShow(lenEntered = 0, itemList = ' '.join(self.autoCompList))    
00909         
00910     def OnKeyPressed(self, event):
00911         """!Key press capture for autocompletion, calltips, and command history
00912 
00913         @todo event.ControlDown() for manual autocomplete
00914         """
00915         # keycodes used: "." = 46, "=" = 61, "-" = 45 
00916         pos = self.GetCurrentPos()
00917         #complete command after pressing '.'
00918         if event.GetKeyCode() == 46 and not event.ShiftDown():
00919             self.autoCompList = list()
00920             entry = self.GetTextLeft()
00921             self.InsertText(pos, '.')
00922             self.CharRight()
00923             self.toComplete = self.EntityToComplete()
00924             try:
00925                 if self.toComplete['entity'] == 'command': 
00926                     self.autoCompList = self.moduleList[entry.strip()]
00927             except (KeyError, TypeError):
00928                 return
00929             self.ShowList()
00930 
00931         # complete flags after pressing '-'       
00932         elif event.GetKeyCode() == 45 and not event.ShiftDown(): 
00933             self.autoCompList = list()
00934             entry = self.GetTextLeft()
00935             self.InsertText(pos, '-')
00936             self.CharRight()
00937             self.toComplete = self.EntityToComplete()
00938             if self.toComplete['entity'] == 'flags' and self.cmdDesc:
00939                 if self.GetTextLeft()[-2:] == ' -': # complete e.g. --quite
00940                     for flag in self.cmdDesc.get_options()['flags']:
00941                         if len(flag['name']) == 1:
00942                             self.autoCompList.append(flag['name'])
00943                 else:
00944                     for flag in self.cmdDesc.get_options()['flags']:
00945                         if len(flag['name']) > 1:
00946                             self.autoCompList.append(flag['name'])            
00947             self.ShowList()
00948             
00949         # complete map or values after parameter
00950         elif event.GetKeyCode() == 61 and not event.ShiftDown():
00951             self.autoCompList = list()
00952             self.InsertText(pos, '=')
00953             self.CharRight()
00954             self.toComplete = self.EntityToComplete()
00955             if self.toComplete:
00956                 if self.toComplete['entity'] == 'raster map':
00957                     self.autoCompList = self.mapList['raster']
00958                 elif self.toComplete['entity'] == 'vector map':
00959                     self.autoCompList = self.mapList['vector']
00960                 elif self.toComplete['entity'] == 'param values':
00961                     param = self.GetWordLeft(withDelimiter = False, ignoredDelimiter='=').strip(' =')
00962                     self.autoCompList = self.cmdDesc.get_param(param)['values']
00963             self.ShowList()
00964             
00965         # complete after pressing CTRL + Space          
00966         elif event.GetKeyCode() == wx.WXK_SPACE and event.ControlDown():
00967             self.autoCompList = list()
00968             self.toComplete = self.EntityToComplete()
00969             if self.toComplete is None:
00970                 return 
00971 
00972             #complete command
00973             if self.toComplete['entity'] == 'command':
00974                 for command in globalvar.grassCmd['all']:
00975                     if command.find(self.toComplete['cmd']) == 0:
00976                         dotNumber = list(self.toComplete['cmd']).count('.') 
00977                         self.autoCompList.append(command.split('.',dotNumber)[-1])
00978                 
00979             
00980             # complete flags in such situations (| is cursor):
00981             # r.colors -| ...w, q, l
00982             # r.colors -w| ...w, q, l  
00983             elif self.toComplete['entity'] == 'flags' and self.cmdDesc:
00984                 for flag in self.cmdDesc.get_options()['flags']:
00985                     if len(flag['name']) == 1:
00986                         self.autoCompList.append(flag['name'])
00987                     
00988             # complete parameters in such situations (| is cursor):
00989             # r.colors -w | ...color, map, rast, rules
00990             # r.colors col| ...color
00991             elif self.toComplete['entity'] == 'params' and self.cmdDesc:
00992                 for param in self.cmdDesc.get_options()['params']:
00993                     if param['name'].find(self.GetWordLeft(withDelimiter=False)) == 0:
00994                         self.autoCompList.append(param['name'])           
00995             
00996             # complete flags or parameters in such situations (| is cursor):
00997             # r.colors | ...-w, -q, -l, color, map, rast, rules
00998             # r.colors color=grey | ...-w, -q, -l, color, map, rast, rules
00999             elif self.toComplete['entity'] == 'params+flags' and self.cmdDesc:
01000                 self.autoCompList = list()
01001                 
01002                 for param in self.cmdDesc.get_options()['params']:
01003                     self.autoCompList.append(param['name'])
01004                 for flag in self.cmdDesc.get_options()['flags']:
01005                     if len(flag['name']) == 1:
01006                         self.autoCompList.append('-' + flag['name'])
01007                     else:
01008                         self.autoCompList.append('--' + flag['name'])
01009                     
01010                 self.ShowList() 
01011                    
01012             # complete map or values after parameter  
01013             # r.buffer input=| ...list of raster maps
01014             # r.buffer units=| ... feet, kilometers, ...   
01015             elif self.toComplete['entity'] == 'raster map':
01016                 self.autoCompList = list()
01017                 self.autoCompList = self.mapList['raster']
01018             elif self.toComplete['entity'] == 'vector map':
01019                 self.autoCompList = list()
01020                 self.autoCompList = self.mapList['vector']
01021             elif self.toComplete['entity'] == 'param values':
01022                 self.autoCompList = list()
01023                 param = self.GetWordLeft(withDelimiter = False, ignoredDelimiter='=').strip(' =')
01024                 self.autoCompList = self.cmdDesc.get_param(param)['values']
01025                 
01026             self.ShowList()
01027 
01028         elif event.GetKeyCode() == wx.WXK_TAB:
01029             # show GRASS command calltips (to hide press 'ESC')
01030             entry = self.GetTextLeft()
01031             try:
01032                 cmd = entry.split()[0].strip()
01033             except IndexError:
01034                 cmd = ''
01035             
01036             if cmd not in globalvar.grassCmd['all']:
01037                 return
01038             
01039             if sys.platform == 'win32':
01040                 if cmd in globalvar.grassCmd['script']:
01041                     cmd += globalvar.EXT_SCT
01042             
01043             info = gtask.command_info(cmd)
01044             
01045             self.CallTipSetBackground("#f4f4d1")
01046             self.CallTipSetForeground("BLACK")
01047             self.CallTipShow(pos, info['usage'] + '\n\n' + info['description'])
01048             
01049             
01050         elif event.GetKeyCode() in [wx.WXK_UP, wx.WXK_DOWN] and \
01051                  not self.AutoCompActive():
01052             # Command history using up and down   
01053             if len(self.cmdbuffer) < 1:
01054                 return
01055             
01056             self.DocumentEnd()
01057             
01058             # move through command history list index values
01059             if event.GetKeyCode() == wx.WXK_UP:
01060                 self.cmdindex = self.cmdindex - 1
01061             if event.GetKeyCode() == wx.WXK_DOWN:
01062                 self.cmdindex = self.cmdindex + 1
01063             if self.cmdindex < 0:
01064                 self.cmdindex = 0
01065             if self.cmdindex > len(self.cmdbuffer) - 1:
01066                 self.cmdindex = len(self.cmdbuffer) - 1
01067             
01068             try:
01069                 txt = self.cmdbuffer[self.cmdindex]
01070             except:
01071                 txt = ''
01072             
01073             # clear current line and insert command history    
01074             self.DelLineLeft()
01075             self.DelLineRight()
01076             pos = self.GetCurrentPos()            
01077             self.InsertText(pos,txt)
01078             self.LineEnd()
01079             self.parent.parent.statusbar.SetStatusText('')
01080             
01081         elif event.GetKeyCode() == wx.WXK_RETURN and \
01082                 self.AutoCompActive() == False:
01083             # run command on line when <return> is pressed
01084             
01085             if self.parent.GetName() == "ModelerDialog":
01086                 self.parent.OnOk(None)
01087                 return
01088             
01089             # find the command to run
01090             line = self.GetCurLine()[0].strip()
01091             if len(line) == 0:
01092                 return
01093             
01094             # parse command into list
01095             try:
01096                 cmd = utils.split(str(line))
01097             except UnicodeError:
01098                 cmd = utils.split(utils.EncodeString((line)))
01099             cmd = map(utils.DecodeString, cmd)
01100             
01101             #  send the command list to the processor 
01102             if cmd[0] in ('r.mapcalc', 'r3.mapcalc') and len(cmd) == 1:
01103                 self.parent.parent.OnMapCalculator(event = None, cmd = cmd)
01104             else:
01105                 self.parent.RunCmd(cmd)
01106             
01107             # add command to history & clean prompt
01108             self.UpdateCmdHistory(cmd)
01109             self.OnCmdErase(None)
01110             self.parent.parent.statusbar.SetStatusText('')
01111             
01112         elif event.GetKeyCode() == wx.WXK_SPACE:
01113             items = self.GetTextLeft().split()
01114             if len(items) == 1:
01115                 cmd = items[0].strip()
01116                 if cmd in globalvar.grassCmd['all'] and \
01117                         cmd != 'r.mapcalc' and \
01118                         (not self.cmdDesc or cmd != self.cmdDesc.get_name()):
01119                     if sys.platform == 'win32':
01120                         if cmd in globalvar.grassCmd['script']:
01121                             cmd += globalvar.EXT_SCT
01122                     try:
01123                         self.cmdDesc = gtask.parse_interface(cmd)
01124                     except IOError:
01125                         self.cmdDesc = None
01126             event.Skip()
01127         
01128         else:
01129             event.Skip()
01130 
01131     def ShowStatusText(self, text):
01132         """!Sets statusbar text, if it's too long, it is cut off"""
01133         maxLen = self.parent.parent.statusbar.GetFieldRect(0).GetWidth()/ 7 # any better way?
01134         if len(text) < maxLen:
01135             self.parent.parent.statusbar.SetStatusText(text)
01136         else:
01137             self.parent.parent.statusbar.SetStatusText(text[:maxLen]+'...')
01138         
01139         
01140     def GetTextLeft(self):
01141         """!Returns all text left of the caret"""
01142         pos = self.GetCurrentPos()
01143         self.HomeExtend()
01144         entry = self.GetSelectedText()
01145         self.SetCurrentPos(pos)
01146         
01147         return entry
01148     
01149     def OnDestroy(self, event):
01150         """!The clipboard contents can be preserved after
01151         the app has exited"""
01152         wx.TheClipboard.Flush()
01153         event.Skip()
01154 
01155     def OnCmdErase(self, event):
01156         """!Erase command prompt"""
01157         self.Home()
01158         self.DelLineRight()
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines