|
GRASS Programmer's Manual
6.5.svn(2012)-r51648
|
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()