GRASS Programmer's Manual
6.4.2(2012)
|
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()