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