12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157 |
- """!
- @package prompt.py
- @brief wxGUI command prompt
- Classes:
- - PromptListCtrl
- - TextCtrlAutoComplete
- - GPrompt
- - GPromptPopUp
- - GPromptSTC
- (C) 2009-2011 by the GRASS Development Team
- This program is free software under the GNU General Public
- License (>=v2). Read the file COPYING that comes with GRASS
- for details.
- @author Martin Landa <landa.martin gmail.com>
- @author Michael Barton <michael.barton@asu.edu>
- @author Vaclav Petras <wenzeslaus gmail.com> (copy&paste customization)
- """
- import os
- import sys
- import copy
- import difflib
- import codecs
- import wx
- import wx.stc
- import wx.lib.mixins.listctrl as listmix
- from grass.script import core as grass
- from grass.script import task as gtask
- import globalvar
- import menudata
- import menuform
- import gcmd
- import utils
- class PromptListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
- """!PopUp window used by GPromptPopUp"""
- def __init__(self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition,
- size = wx.DefaultSize, style = 0):
- wx.ListCtrl.__init__(self, parent, id, pos, size, style)
- listmix.ListCtrlAutoWidthMixin.__init__(self)
-
- class TextCtrlAutoComplete(wx.ComboBox, listmix.ColumnSorterMixin):
- """!Auto complete text area used by GPromptPopUp"""
- def __init__ (self, parent, statusbar,
- id = wx.ID_ANY, choices = [], **kwargs):
- """!Constructor works just like wx.TextCtrl except you can pass in a
- list of choices. You can also change the choice list at any time
- by calling setChoices.
-
- Inspired by http://wiki.wxpython.org/TextCtrlAutoComplete
- """
- self.statusbar = statusbar
-
- if 'style' in kwargs:
- kwargs['style'] = wx.TE_PROCESS_ENTER | kwargs['style']
- else:
- kwargs['style'] = wx.TE_PROCESS_ENTER
-
- wx.ComboBox.__init__(self, parent, id, **kwargs)
-
- # some variables
- self._choices = choices
- self._hideOnNoMatch = True
- self._module = None # currently selected module
- self._choiceType = None # type of choice (module, params, flags, raster, vector ...)
- self._screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
- self._historyItem = 0 # last item
-
- # sort variable needed by listmix
- self.itemDataMap = dict()
-
- # widgets
- try:
- self.dropdown = wx.PopupWindow(self)
- except NotImplementedError:
- self.Destroy()
- raise NotImplementedError
-
- # create the list and bind the events
- self.dropdownlistbox = PromptListCtrl(parent = self.dropdown,
- style = wx.LC_REPORT | wx.LC_SINGLE_SEL | \
- wx.LC_SORT_ASCENDING | wx.LC_NO_HEADER,
- pos = wx.Point(0, 0))
-
- listmix.ColumnSorterMixin.__init__(self, 1)
-
- # set choices (list of GRASS modules)
- self._choicesCmd = globalvar.grassCmd['all']
- self._choicesMap = dict()
- for type in ('raster', 'vector'):
- self._choicesMap[type] = grass.list_strings(type = type[:4])
- # first search for GRASS module
- self.SetChoices(self._choicesCmd)
-
- self.SetMinSize(self.GetSize())
-
- # bindings...
- self.Bind(wx.EVT_KILL_FOCUS, self.OnControlChanged)
- self.Bind(wx.EVT_TEXT, self.OnEnteredText)
- self.Bind(wx.EVT_TEXT_ENTER, self.OnRunCmd)
- self.Bind(wx.EVT_KEY_DOWN , self.OnKeyDown)
- ### self.Bind(wx.EVT_LEFT_DOWN, self.OnClick)
- # if need drop down on left click
- self.dropdown.Bind(wx.EVT_LISTBOX , self.OnListItemSelected, self.dropdownlistbox)
- self.dropdownlistbox.Bind(wx.EVT_LEFT_DOWN, self.OnListClick)
- self.dropdownlistbox.Bind(wx.EVT_LEFT_DCLICK, self.OnListDClick)
- self.dropdownlistbox.Bind(wx.EVT_LIST_COL_CLICK, self.OnListColClick)
- self.Bind(wx.EVT_COMBOBOX, self.OnCommandSelect)
-
- def _updateDataList(self, choices):
- """!Update data list"""
- # delete, if need, all the previous data
- if self.dropdownlistbox.GetColumnCount() != 0:
- self.dropdownlistbox.DeleteAllColumns()
- self.dropdownlistbox.DeleteAllItems()
- # and update the dict
- if choices:
- for numVal, data in enumerate(choices):
- self.itemDataMap[numVal] = data
- else:
- numVal = 0
- self.SetColumnCount(numVal)
-
- def _setListSize(self):
- """!Set list size"""
- choices = self._choices
- longest = 0
- for choice in choices:
- longest = max(len(choice), longest)
- longest += 3
- itemcount = min(len( choices ), 7) + 2
- charheight = self.dropdownlistbox.GetCharHeight()
- charwidth = self.dropdownlistbox.GetCharWidth()
- self.popupsize = wx.Size(charwidth*longest, charheight*itemcount)
- self.dropdownlistbox.SetSize(self.popupsize)
- self.dropdown.SetClientSize(self.popupsize)
-
- def _showDropDown(self, show = True):
- """!Either display the drop down list (show = True) or hide it
- (show = False).
- """
- if show:
- size = self.dropdown.GetSize()
- width, height = self.GetSizeTuple()
- x, y = self.ClientToScreenXY(0, height)
- if size.GetWidth() != width:
- size.SetWidth(width)
- self.dropdown.SetSize(size)
- self.dropdownlistbox.SetSize(self.dropdown.GetClientSize())
- if (y + size.GetHeight()) < self._screenheight:
- self.dropdown.SetPosition(wx.Point(x, y))
- else:
- self.dropdown.SetPosition(wx.Point(x, y - height - size.GetHeight()))
-
- self.dropdown.Show(show)
-
- def _listItemVisible(self):
- """!Moves the selected item to the top of the list ensuring it is
- always visible.
- """
- toSel = self.dropdownlistbox.GetFirstSelected()
- if toSel == -1:
- return
- self.dropdownlistbox.EnsureVisible(toSel)
- def _setModule(self, name):
- """!Set module's choices (flags, parameters)"""
- # get module's description
- if name in self._choicesCmd and not self._module:
- try:
- self._module = gtask.parse_interface(name)
- except IOError:
- self._module = None
-
- # set choices (flags)
- self._choicesMap['flag'] = self._module.get_list_flags()
- for idx in range(len(self._choicesMap['flag'])):
- item = self._choicesMap['flag'][idx]
- desc = self._module.get_flag(item)['label']
- if not desc:
- desc = self._module.get_flag(item)['description']
-
- self._choicesMap['flag'][idx] = '%s (%s)' % (item, desc)
-
- # set choices (parameters)
- self._choicesMap['param'] = self._module.get_list_params()
- for idx in range(len(self._choicesMap['param'])):
- item = self._choicesMap['param'][idx]
- desc = self._module.get_param(item)['label']
- if not desc:
- desc = self._module.get_param(item)['description']
-
- self._choicesMap['param'][idx] = '%s (%s)' % (item, desc)
-
- def _setValueFromSelected(self):
- """!Sets the wx.TextCtrl value from the selected wx.ListCtrl item.
- Will do nothing if no item is selected in the wx.ListCtrl.
- """
- sel = self.dropdownlistbox.GetFirstSelected()
- if sel < 0:
- return
-
- if self._colFetch != -1:
- col = self._colFetch
- else:
- col = self._colSearch
- itemtext = self.dropdownlistbox.GetItem(sel, col).GetText()
-
- cmd = utils.split(str(self.GetValue()))
- if len(cmd) > 0 and cmd[0] in self._choicesCmd:
- # -> append text (skip last item)
- if self._choiceType == 'param':
- itemtext = itemtext.split(' ')[0]
- self.SetValue(' '.join(cmd) + ' ' + itemtext + '=')
- optType = self._module.get_param(itemtext)['prompt']
- if optType in ('raster', 'vector'):
- # -> raster/vector map
- self.SetChoices(self._choicesMap[optType], optType)
- elif self._choiceType == 'flag':
- itemtext = itemtext.split(' ')[0]
- if len(itemtext) > 1:
- prefix = '--'
- else:
- prefix = '-'
- self.SetValue(' '.join(cmd[:-1]) + ' ' + prefix + itemtext)
- elif self._choiceType in ('raster', 'vector'):
- self.SetValue(' '.join(cmd[:-1]) + ' ' + cmd[-1].split('=', 1)[0] + '=' + itemtext)
- else:
- # -> reset text
- self.SetValue(itemtext + ' ')
-
- # define module
- self._setModule(itemtext)
-
- # use parameters as default choices
- self._choiceType = 'param'
- self.SetChoices(self._choicesMap['param'], type = 'param')
-
- self.SetInsertionPointEnd()
-
- self._showDropDown(False)
-
- def GetListCtrl(self):
- """!Method required by listmix.ColumnSorterMixin"""
- return self.dropdownlistbox
-
- def SetChoices(self, choices, type = 'module'):
- """!Sets the choices available in the popup wx.ListBox.
- The items will be sorted case insensitively.
- @param choices list of choices
- @param type type of choices (module, param, flag, raster, vector)
- """
- self._choices = choices
- self._choiceType = type
-
- self.dropdownlistbox.SetWindowStyleFlag(wx.LC_REPORT | wx.LC_SINGLE_SEL |
- wx.LC_SORT_ASCENDING | wx.LC_NO_HEADER)
- if not isinstance(choices, list):
- self._choices = [ x for x in choices ]
- if self._choiceType not in ('raster', 'vector'):
- # do not sort raster/vector maps
- utils.ListSortLower(self._choices)
-
- self._updateDataList(self._choices)
-
- self.dropdownlistbox.InsertColumn(0, "")
- for num, colVal in enumerate(self._choices):
- index = self.dropdownlistbox.InsertImageStringItem(sys.maxint, colVal, -1)
- self.dropdownlistbox.SetStringItem(index, 0, colVal)
- self.dropdownlistbox.SetItemData(index, num)
- self._setListSize()
-
- # there is only one choice for both search and fetch if setting a single column:
- self._colSearch = 0
- self._colFetch = -1
- def OnClick(self, event):
- """Left mouse button pressed"""
- sel = self.dropdownlistbox.GetFirstSelected()
- if not self.dropdown.IsShown():
- if sel > -1:
- self.dropdownlistbox.Select(sel)
- else:
- self.dropdownlistbox.Select(0)
- self._listItemVisible()
- self._showDropDown()
- else:
- self.dropdown.Hide()
-
- def OnCommandSelect(self, event):
- """!Command selected from history"""
- self._historyItem = event.GetSelection() - len(self.GetItems())
- self.SetFocus()
-
- def OnListClick(self, evt):
- """!Left mouse button pressed"""
- toSel, flag = self.dropdownlistbox.HitTest( evt.GetPosition() )
- #no values on poition, return
- if toSel == -1: return
- self.dropdownlistbox.Select(toSel)
- def OnListDClick(self, evt):
- """!Mouse button double click"""
- self._setValueFromSelected()
- def OnListColClick(self, evt):
- """!Left mouse button pressed on column"""
- col = evt.GetColumn()
- # reverse the sort
- if col == self._colSearch:
- self._ascending = not self._ascending
- self.SortListItems( evt.GetColumn(), ascending=self._ascending )
- self._colSearch = evt.GetColumn()
- evt.Skip()
- def OnListItemSelected(self, event):
- """!Item selected"""
- self._setValueFromSelected()
- event.Skip()
- def OnEnteredText(self, event):
- """!Text entered"""
- text = event.GetString()
-
- if not text:
- # control is empty; hide dropdown if shown:
- if self.dropdown.IsShown():
- self._showDropDown(False)
- event.Skip()
- return
-
- try:
- cmd = utils.split(str(text))
- except ValueError, e:
- self.statusbar.SetStatusText(str(e))
- cmd = text.split(' ')
- pattern = str(text)
-
- if len(cmd) > 0 and cmd[0] in self._choicesCmd and not self._module:
- self._setModule(cmd[0])
- elif len(cmd) > 1 and cmd[0] in self._choicesCmd:
- if self._module:
- if len(cmd[-1].split('=', 1)) == 1:
- # new option
- if cmd[-1][0] == '-':
- # -> flags
- self.SetChoices(self._choicesMap['flag'], type = 'flag')
- pattern = cmd[-1].lstrip('-')
- else:
- # -> options
- self.SetChoices(self._choicesMap['param'], type = 'param')
- pattern = cmd[-1]
- else:
- # value
- pattern = cmd[-1].split('=', 1)[1]
- else:
- # search for GRASS modules
- if self._module:
- # -> switch back to GRASS modules list
- self.SetChoices(self._choicesCmd)
- self._module = None
- self._choiceType = None
-
- self._choiceType
- self._choicesMap
- found = False
- choices = self._choices
- for numCh, choice in enumerate(choices):
- if choice.lower().startswith(pattern):
- found = True
- if found:
- self._showDropDown(True)
- item = self.dropdownlistbox.GetItem(numCh)
- toSel = item.GetId()
- self.dropdownlistbox.Select(toSel)
- break
-
- if not found:
- self.dropdownlistbox.Select(self.dropdownlistbox.GetFirstSelected(), False)
- if self._hideOnNoMatch:
- self._showDropDown(False)
- if self._module and '=' not in cmd[-1]:
- message = ''
- if cmd[-1][0] == '-': # flag
- message = _("Warning: flag <%(flag)s> not found in '%(module)s'") % \
- { 'flag' : cmd[-1][1:], 'module' : self._module.name }
- else: # option
- message = _("Warning: option <%(param)s> not found in '%(module)s'") % \
- { 'param' : cmd[-1], 'module' : self._module.name }
- self.statusbar.SetStatusText(message)
-
- if self._module and len(cmd[-1]) == 2 and cmd[-1][-2] == '=':
- optType = self._module.get_param(cmd[-1][:-2])['prompt']
- if optType in ('raster', 'vector'):
- # -> raster/vector map
- self.SetChoices(self._choicesMap[optType], optType)
-
- self._listItemVisible()
-
- event.Skip()
-
- def OnKeyDown (self, event):
- """!Do some work when the user press on the keys: up and down:
- move the cursor left and right: move the search
- """
- skip = True
- sel = self.dropdownlistbox.GetFirstSelected()
- visible = self.dropdown.IsShown()
- KC = event.GetKeyCode()
-
- if KC == wx.WXK_RIGHT:
- # right -> show choices
- if sel < (self.dropdownlistbox.GetItemCount() - 1):
- self.dropdownlistbox.Select(sel + 1)
- self._listItemVisible()
- self._showDropDown()
- skip = False
- elif KC == wx.WXK_UP:
- if visible:
- if sel > 0:
- self.dropdownlistbox.Select(sel - 1)
- self._listItemVisible()
- self._showDropDown()
- skip = False
- else:
- self._historyItem -= 1
- try:
- self.SetValue(self.GetItems()[self._historyItem])
- except IndexError:
- self._historyItem += 1
- elif KC == wx.WXK_DOWN:
- if visible:
- if sel < (self.dropdownlistbox.GetItemCount() - 1):
- self.dropdownlistbox.Select(sel + 1)
- self._listItemVisible()
- self._showDropDown()
- skip = False
- else:
- if self._historyItem < -1:
- self._historyItem += 1
- self.SetValue(self.GetItems()[self._historyItem])
-
- if visible:
- if event.GetKeyCode() == wx.WXK_RETURN:
- self._setValueFromSelected()
- skip = False
- if event.GetKeyCode() == wx.WXK_ESCAPE:
- self._showDropDown(False)
- skip = False
- if skip:
- event.Skip()
-
- def OnControlChanged(self, event):
- """!Control changed"""
- if self.IsShown():
- self._showDropDown(False)
-
- event.Skip()
- class GPrompt(object):
- """!Abstract class for interactive wxGUI prompt
- See subclass GPromptPopUp and GPromptSTC.
- """
- def __init__(self, parent):
- self.parent = parent # GMConsole
- self.panel = self.parent.GetPanel()
-
- if self.parent.parent.GetName() not in ("LayerManager", "Modeler"):
- self.standAlone = True
- else:
- self.standAlone = False
-
- # dictionary of modules (description, keywords, ...)
- if not self.standAlone:
- if self.parent.parent.GetName() == 'Modeler':
- self.moduleDesc = menudata.ManagerData().GetModules()
- else:
- self.moduleDesc = parent.parent.menubar.GetData().GetModules()
- self.moduleList = self._getListOfModules()
- self.mapList = self._getListOfMaps()
- self.mapsetList = utils.ListOfMapsets()
- else:
- self.moduleDesc = self.moduleList = self.mapList = None
-
- # auto complete items
- self.autoCompList = list()
- self.autoCompFilter = None
-
- # command description (gtask.grassTask)
- self.cmdDesc = None
- self.cmdbuffer = self._readHistory()
- self.cmdindex = len(self.cmdbuffer)
-
- def _readHistory(self):
- """!Get list of commands from history file"""
- hist = list()
- env = grass.gisenv()
- try:
- fileHistory = codecs.open(os.path.join(env['GISDBASE'],
- env['LOCATION_NAME'],
- env['MAPSET'],
- '.bash_history'),
- encoding = 'utf-8', mode = 'r', errors='replace')
- except IOError:
- return hist
-
- try:
- for line in fileHistory.readlines():
- hist.append(line.replace('\n', ''))
- finally:
- fileHistory.close()
-
- return hist
- def GetCommandDesc(self, cmd):
- """!Get description for given command"""
- if cmd in self.moduleDesc:
- return self.moduleDesc[cmd]['desc']
-
- return ''
-
- def GetCommandItems(self):
- """!Get list of available commands"""
- items = list()
-
- if self.autoCompFilter is not None:
- mList = self.autoCompFilter
- else:
- mList = self.moduleList
-
- if not mList:
- return items
-
- prefixes = mList.keys()
- prefixes.sort()
-
- for prefix in prefixes:
- for command in mList[prefix]:
- name = prefix + '.' + command
- if name not in items:
- items.append(name)
-
- items.sort()
-
- return items
-
- def _getListOfModules(self):
- """!Get list of modules"""
- result = dict()
- for module in globalvar.grassCmd['all']:
- try:
- group, name = module.split('.',1)
- except ValueError:
- continue # TODO
-
- if group not in result:
- result[group] = list()
- result[group].append(name)
-
- # for better auto-completion:
- # not only result['r']={...,'colors.out',...}, but also result['r.colors']={'out',...}
- for i in range(len(name.split('.'))-1):
- group = '.'.join([group,name.split('.',1)[0]])
- name = name.split('.',1)[1]
- if group not in result:
- result[group] = list()
- result[group].append(name)
-
- # sort list of names
- for group in result.keys():
- result[group].sort()
-
- return result
-
- def _getListOfMaps(self):
- """!Get list of maps"""
- result = dict()
- result['raster'] = grass.list_strings('rast')
- result['vector'] = grass.list_strings('vect')
-
- return result
- def OnRunCmd(self, event):
- """!Run command"""
- cmdString = event.GetString()
-
- if self.standAlone:
- return
-
- if cmdString[:2] == 'd.' and not self.parent.curr_page:
- self.parent.NewDisplay(show=True)
-
- cmd = utils.split(cmdString)
- if len(cmd) > 1:
- self.parent.RunCmd(cmd, switchPage = True)
- else:
- self.parent.RunCmd(cmd, switchPage = False)
-
- self.OnUpdateStatusBar(None)
-
- def OnUpdateStatusBar(self, event):
- """!Update Layer Manager status bar"""
- if self.standAlone:
- return
-
- if event is None:
- self.parent.parent.statusbar.SetStatusText("")
- else:
- self.parent.parent.statusbar.SetStatusText(_("Type GRASS command and run by pressing ENTER"))
- event.Skip()
-
- def GetPanel(self):
- """!Get main widget panel"""
- return self.panel
- def GetInput(self):
- """!Get main prompt widget"""
- return self.input
-
- def SetFilter(self, data, module = True):
- """!Set filter
- @param data data dict
- @param module True to filter modules, otherwise data
- """
- if module:
- if data:
- self.moduleList = data
- else:
- self.moduleList = self._getListOfModules()
- else:
- if data:
- self.dataList = data
- else:
- self.dataList = self._getListOfMaps()
-
- class GPromptPopUp(GPrompt, TextCtrlAutoComplete):
- """!Interactive wxGUI prompt - popup version"""
- def __init__(self, parent):
- GPrompt.__init__(self, parent)
-
- ### todo: fix TextCtrlAutoComplete to work also on Macs
- ### reason: missing wx.PopupWindow()
- try:
- TextCtrlAutoComplete.__init__(self, parent = self.panel, id = wx.ID_ANY,
- value = "",
- style = wx.TE_LINEWRAP | wx.TE_PROCESS_ENTER,
- statusbar = self.parent.parent.statusbar)
- self.SetItems(self._readHistory())
- except NotImplementedError:
- # wx.PopupWindow may be not available in wxMac
- # see http://trac.wxwidgets.org/ticket/9377
- wx.TextCtrl.__init__(parent = self.panel, id = wx.ID_ANY,
- value = "",
- style=wx.TE_LINEWRAP | wx.TE_PROCESS_ENTER,
- size = (-1, 25))
- self.searchBy.Enable(False)
- self.search.Enable(False)
-
- self.SetFont(wx.Font(10, wx.FONTFAMILY_MODERN, wx.NORMAL, wx.NORMAL, 0, ''))
-
- wx.CallAfter(self.SetInsertionPoint, 0)
-
- # bidnings
- self.Bind(wx.EVT_TEXT_ENTER, self.OnRunCmd)
- self.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
-
- def OnCmdErase(self, event):
- """!Erase command prompt"""
- self.input.SetValue('')
- class GPromptSTC(GPrompt, wx.stc.StyledTextCtrl):
- """!Styled wxGUI prompt with autocomplete and calltips"""
- def __init__(self, parent, id = wx.ID_ANY, margin = False):
- GPrompt.__init__(self, parent)
- wx.stc.StyledTextCtrl.__init__(self, self.panel, id)
-
- #
- # styles
- #
- self.SetWrapMode(True)
- self.SetUndoCollection(True)
-
- #
- # create command and map lists for autocompletion
- #
- self.AutoCompSetIgnoreCase(False)
-
- #
- # line margins
- #
- # TODO print number only from cmdlog
- self.SetMarginWidth(1, 0)
- self.SetMarginWidth(2, 0)
- if margin:
- self.SetMarginType(0, wx.stc.STC_MARGIN_NUMBER)
- self.SetMarginWidth(0, 30)
- else:
- self.SetMarginWidth(0, 0)
-
- #
- # miscellaneous
- #
- self.SetViewWhiteSpace(False)
- self.SetUseTabs(False)
- self.UsePopUp(True)
- self.SetSelBackground(True, "#FFFF00")
- self.SetUseHorizontalScrollBar(True)
-
- #
- # bindings
- #
- self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
- self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
- self.Bind(wx.stc.EVT_STC_AUTOCOMP_SELECTION, self.OnItemSelected)
- self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemChanged)
-
- def OnTextSelectionChanged(self, event):
- """!Copy selected text to clipboard and skip event.
- The same function is in GMStc class (goutput.py).
- """
- self.Copy()
- event.Skip()
-
- def OnItemChanged(self, event):
- """!Change text in statusbar
- if the item selection in the auto-completion list is changed"""
- # list of commands
- if self.toComplete['entity'] == 'command':
- item = self.toComplete['cmd'].rpartition('.')[0] + '.' + self.autoCompList[event.GetIndex()]
- try:
- desc = self.moduleDesc[item]['desc']
- except KeyError:
- desc = ''
- self.ShowStatusText(desc)
- # list of flags
- elif self.toComplete['entity'] == 'flags':
- desc = self.cmdDesc.get_flag(self.autoCompList[event.GetIndex()])['description']
- self.ShowStatusText(desc)
- # list of parameters
- elif self.toComplete['entity'] == 'params':
- item = self.cmdDesc.get_param(self.autoCompList[event.GetIndex()])
- desc = item['name'] + '=' + item['type']
- if not item['required']:
- desc = '[' + desc + ']'
- desc += ': ' + item['description']
- self.ShowStatusText(desc)
- # list of flags and commands
- elif self.toComplete['entity'] == 'params+flags':
- if self.autoCompList[event.GetIndex()][0] == '-':
- desc = self.cmdDesc.get_flag(self.autoCompList[event.GetIndex()].strip('-'))['description']
- else:
- item = self.cmdDesc.get_param(self.autoCompList[event.GetIndex()])
- desc = item['name'] + '=' + item['type']
- if not item['required']:
- desc = '[' + desc + ']'
- desc += ': ' + item['description']
- self.ShowStatusText(desc)
- else:
- self.ShowStatusText('')
-
- def OnItemSelected(self, event):
- """!Item selected from the list"""
- lastWord = self.GetWordLeft()
- # to insert selection correctly if selected word partly matches written text
- match = difflib.SequenceMatcher(None, event.GetText(), lastWord)
- matchTuple = match.find_longest_match(0, len(event.GetText()), 0, len(lastWord))
-
- compl = event.GetText()[matchTuple[2]:]
- text = self.GetTextLeft() + compl
- # add space or '=' at the end
- end = '='
- for char in ('.','-','='):
- if text.split(' ')[-1].find(char) >= 0:
- end = ' '
-
- compl += end
- text += end
- self.AddText(compl)
- pos = len(text)
- self.SetCurrentPos(pos)
-
- cmd = text.strip().split(' ')[0]
- if not self.cmdDesc or cmd != self.cmdDesc.get_name():
- if cmd in ('r.mapcalc', 'r3.mapcalc') and \
- self.parent.parent.GetName() == 'LayerManager':
- self.parent.parent.OnMapCalculator(event = None, cmd = [cmd])
- # add command to history & clean prompt
- self.UpdateCmdHistory([cmd])
- self.OnCmdErase(None)
- else:
- try:
- self.cmdDesc = gtask.parse_interface(cmd)
- except IOError:
- self.cmdDesc = None
-
- def UpdateCmdHistory(self, cmd):
- """!Update command history
-
- @param cmd command given as a list
- """
- # add command to history
- self.cmdbuffer.append(' '.join(cmd))
-
- # keep command history to a managable size
- if len(self.cmdbuffer) > 200:
- del self.cmdbuffer[0]
- self.cmdindex = len(self.cmdbuffer)
-
- def EntityToComplete(self):
- """!Determines which part of command (flags, parameters) should
- be completed at current cursor position"""
- entry = self.GetTextLeft()
- toComplete = dict()
- try:
- cmd = entry.split()[0].strip()
- except IndexError:
- return None
-
- if len(utils.split(str(entry))) > 1:
- if cmd in globalvar.grassCmd['all']:
- toComplete['cmd'] = cmd
- if entry[-1] == ' ':
- words = entry.split(' ')
- if any(word.startswith('-') for word in words):
- toComplete['entity'] = 'params'
- else:
- toComplete['entity'] = 'params+flags'
- else:
- # get word left from current position
- word = self.GetWordLeft(withDelimiter = True)
-
- if word[0] == '=' and word[-1] == '@':
- toComplete['entity'] = 'mapsets'
- elif word[0] == '=':
- # get name of parameter
- paramName = self.GetWordLeft(withDelimiter = False, ignoredDelimiter = '=').strip('=')
- if paramName:
- try:
- param = self.cmdDesc.get_param(paramName)
- except (ValueError, AttributeError):
- return None
- else:
- return None
-
- if param['values']:
- toComplete['entity'] = 'param values'
- elif param['prompt'] == 'raster' and param['element'] == 'cell':
- toComplete['entity'] = 'raster map'
- elif param['prompt'] == 'vector' and param['element'] == 'vector':
- toComplete['entity'] = 'vector map'
- elif word[0] == '-':
- toComplete['entity'] = 'flags'
- elif word[0] == ' ':
- toComplete['entity'] = 'params'
- else:
- return None
- else:
- toComplete['entity'] = 'command'
- toComplete['cmd'] = cmd
-
- return toComplete
-
- def GetWordLeft(self, withDelimiter = False, ignoredDelimiter = None):
- """!Get word left from current cursor position. The beginning
- of the word is given by space or chars: .,-=
-
- @param withDelimiter returns the word with the initial delimeter
- @param ignoredDelimiter finds the word ignoring certain delimeter
- """
- textLeft = self.GetTextLeft()
-
- parts = list()
- if ignoredDelimiter is None:
- ignoredDelimiter = ''
-
- for char in set(' .,-=') - set(ignoredDelimiter):
- if not withDelimiter:
- delimiter = ''
- else:
- delimiter = char
- parts.append(delimiter + textLeft.rpartition(char)[2])
- return min(parts, key=lambda x: len(x))
-
- def ShowList(self):
- """!Show sorted auto-completion list if it is not empty"""
- if len(self.autoCompList) > 0:
- self.autoCompList.sort()
- self.AutoCompShow(lenEntered = 0, itemList = ' '.join(self.autoCompList))
-
- def OnKeyPressed(self, event):
- """!Key press capture for autocompletion, calltips, and command history
- @todo event.ControlDown() for manual autocomplete
- """
- # keycodes used: "." = 46, "=" = 61, "-" = 45
- pos = self.GetCurrentPos()
- #complete command after pressing '.'
- if event.GetKeyCode() == 46 and not event.ShiftDown():
- self.autoCompList = list()
- entry = self.GetTextLeft()
- self.InsertText(pos, '.')
- self.CharRight()
- self.toComplete = self.EntityToComplete()
- try:
- if self.toComplete['entity'] == 'command':
- self.autoCompList = self.moduleList[entry.strip()]
- except (KeyError, TypeError):
- return
- self.ShowList()
- # complete flags after pressing '-'
- elif event.GetKeyCode() == 45 and not event.ShiftDown():
- self.autoCompList = list()
- entry = self.GetTextLeft()
- self.InsertText(pos, '-')
- self.CharRight()
- self.toComplete = self.EntityToComplete()
- if self.toComplete['entity'] == 'flags' and self.cmdDesc:
- if self.GetTextLeft()[-2:] == ' -': # complete e.g. --quite
- for flag in self.cmdDesc.get_options()['flags']:
- if len(flag['name']) == 1:
- self.autoCompList.append(flag['name'])
- else:
- for flag in self.cmdDesc.get_options()['flags']:
- if len(flag['name']) > 1:
- self.autoCompList.append(flag['name'])
- self.ShowList()
-
- # complete map or values after parameter
- elif event.GetKeyCode() == 61 and not event.ShiftDown():
- self.autoCompList = list()
- self.InsertText(pos, '=')
- self.CharRight()
- self.toComplete = self.EntityToComplete()
- if self.toComplete and 'entity' in self.toComplete:
- if self.toComplete['entity'] == 'raster map':
- self.autoCompList = self.mapList['raster']
- elif self.toComplete['entity'] == 'vector map':
- self.autoCompList = self.mapList['vector']
- elif self.toComplete['entity'] == 'param values':
- param = self.GetWordLeft(withDelimiter = False, ignoredDelimiter='=').strip(' =')
- self.autoCompList = self.cmdDesc.get_param(param)['values']
- self.ShowList()
-
- # complete mapset ('@')
- elif event.GetKeyCode() == 50 and event.ShiftDown():
- self.autoCompList = list()
- self.InsertText(pos, '@')
- self.CharRight()
- self.toComplete = self.EntityToComplete()
-
- if self.toComplete and self.toComplete['entity'] == 'mapsets':
- self.autoCompList = self.mapsetList
- self.ShowList()
-
- # complete after pressing CTRL + Space
- elif event.GetKeyCode() == wx.WXK_SPACE and event.ControlDown():
- self.autoCompList = list()
- self.toComplete = self.EntityToComplete()
- if self.toComplete is None:
- return
- #complete command
- if self.toComplete['entity'] == 'command':
- for command in globalvar.grassCmd['all']:
- if command.find(self.toComplete['cmd']) == 0:
- dotNumber = list(self.toComplete['cmd']).count('.')
- self.autoCompList.append(command.split('.',dotNumber)[-1])
-
-
- # complete flags in such situations (| is cursor):
- # r.colors -| ...w, q, l
- # r.colors -w| ...w, q, l
- elif self.toComplete['entity'] == 'flags' and self.cmdDesc:
- for flag in self.cmdDesc.get_options()['flags']:
- if len(flag['name']) == 1:
- self.autoCompList.append(flag['name'])
-
- # complete parameters in such situations (| is cursor):
- # r.colors -w | ...color, map, rast, rules
- # r.colors col| ...color
- elif self.toComplete['entity'] == 'params' and self.cmdDesc:
- for param in self.cmdDesc.get_options()['params']:
- if param['name'].find(self.GetWordLeft(withDelimiter=False)) == 0:
- self.autoCompList.append(param['name'])
-
- # complete flags or parameters in such situations (| is cursor):
- # r.colors | ...-w, -q, -l, color, map, rast, rules
- # r.colors color=grey | ...-w, -q, -l, color, map, rast, rules
- elif self.toComplete['entity'] == 'params+flags' and self.cmdDesc:
- self.autoCompList = list()
-
- for param in self.cmdDesc.get_options()['params']:
- self.autoCompList.append(param['name'])
- for flag in self.cmdDesc.get_options()['flags']:
- if len(flag['name']) == 1:
- self.autoCompList.append('-' + flag['name'])
- else:
- self.autoCompList.append('--' + flag['name'])
-
- self.ShowList()
-
- # complete map or values after parameter
- # r.buffer input=| ...list of raster maps
- # r.buffer units=| ... feet, kilometers, ...
- elif self.toComplete['entity'] == 'raster map':
- self.autoCompList = list()
- self.autoCompList = self.mapList['raster']
- elif self.toComplete['entity'] == 'vector map':
- self.autoCompList = list()
- self.autoCompList = self.mapList['vector']
- elif self.toComplete['entity'] == 'param values':
- self.autoCompList = list()
- param = self.GetWordLeft(withDelimiter = False, ignoredDelimiter='=').strip(' =')
- self.autoCompList = self.cmdDesc.get_param(param)['values']
-
- self.ShowList()
- elif event.GetKeyCode() == wx.WXK_TAB:
- # show GRASS command calltips (to hide press 'ESC')
- entry = self.GetTextLeft()
- try:
- cmd = entry.split()[0].strip()
- except IndexError:
- cmd = ''
-
- if cmd not in globalvar.grassCmd['all']:
- return
-
- info = gtask.command_info(cmd)
-
- self.CallTipSetBackground("#f4f4d1")
- self.CallTipSetForeground("BLACK")
- self.CallTipShow(pos, info['usage'] + '\n\n' + info['description'])
-
-
- elif event.GetKeyCode() in [wx.WXK_UP, wx.WXK_DOWN] and \
- not self.AutoCompActive():
- # Command history using up and down
- if len(self.cmdbuffer) < 1:
- return
-
- self.DocumentEnd()
-
- # move through command history list index values
- if event.GetKeyCode() == wx.WXK_UP:
- self.cmdindex = self.cmdindex - 1
- if event.GetKeyCode() == wx.WXK_DOWN:
- self.cmdindex = self.cmdindex + 1
- if self.cmdindex < 0:
- self.cmdindex = 0
- if self.cmdindex > len(self.cmdbuffer) - 1:
- self.cmdindex = len(self.cmdbuffer) - 1
-
- try:
- txt = self.cmdbuffer[self.cmdindex]
- except:
- txt = ''
-
- # clear current line and insert command history
- self.DelLineLeft()
- self.DelLineRight()
- pos = self.GetCurrentPos()
- self.InsertText(pos,txt)
- self.LineEnd()
- self.parent.parent.statusbar.SetStatusText('')
-
- elif event.GetKeyCode() == wx.WXK_RETURN and \
- self.AutoCompActive() == False:
- # run command on line when <return> is pressed
-
- if self.parent.GetName() == "ModelerDialog":
- self.parent.OnOk(None)
- return
-
- # find the command to run
- line = self.GetCurLine()[0].strip()
- if len(line) == 0:
- return
-
- # parse command into list
- try:
- cmd = utils.split(str(line))
- except UnicodeError:
- cmd = utils.split(utils.EncodeString((line)))
- cmd = map(utils.DecodeString, cmd)
-
- # send the command list to the processor
- if cmd[0] in ('r.mapcalc', 'r3.mapcalc') and len(cmd) == 1:
- self.parent.parent.OnMapCalculator(event = None, cmd = cmd)
- else:
- self.parent.RunCmd(cmd)
-
- # add command to history & clean prompt
- self.UpdateCmdHistory(cmd)
- self.OnCmdErase(None)
- self.parent.parent.statusbar.SetStatusText('')
-
- elif event.GetKeyCode() == wx.WXK_SPACE:
- items = self.GetTextLeft().split()
- if len(items) == 1:
- cmd = items[0].strip()
- if cmd in globalvar.grassCmd['all'] and \
- cmd != 'r.mapcalc' and \
- (not self.cmdDesc or cmd != self.cmdDesc.get_name()):
- try:
- self.cmdDesc = gtask.parse_interface(cmd)
- except IOError:
- self.cmdDesc = None
- event.Skip()
-
- else:
- event.Skip()
- def ShowStatusText(self, text):
- """!Sets statusbar text, if it's too long, it is cut off"""
- maxLen = self.parent.parent.statusbar.GetFieldRect(0).GetWidth()/ 7 # any better way?
- if len(text) < maxLen:
- self.parent.parent.statusbar.SetStatusText(text)
- else:
- self.parent.parent.statusbar.SetStatusText(text[:maxLen]+'...')
-
-
- def GetTextLeft(self):
- """!Returns all text left of the caret"""
- pos = self.GetCurrentPos()
- self.HomeExtend()
- entry = self.GetSelectedText()
- self.SetCurrentPos(pos)
-
- return entry
-
- def OnDestroy(self, event):
- """!The clipboard contents can be preserved after
- the app has exited"""
- wx.TheClipboard.Flush()
- event.Skip()
- def OnCmdErase(self, event):
- """!Erase command prompt"""
- self.Home()
- self.DelLineRight()
|