2 @package gui_core.prompt
4 @brief wxGUI command prompt
7 - prompt::PromptListCtrl
8 - prompt::TextCtrlAutoComplete
10 - prompt::GPromptPopUp
13 (C) 2009-2011 by the GRASS Development Team
15 This program is free software under the GNU General Public License
16 (>=v2). Read the file COPYING that comes with GRASS for details.
18 @author Martin Landa <landa.martin gmail.com>
19 @author Michael Barton <michael.barton@asu.edu>
20 @author Vaclav Petras <wenzeslaus gmail.com> (copy&paste customization)
30 import wx.lib.mixins.listctrl
as listmix
35 from core
import globalvar
36 from core
import utils
37 from lmgr.menudata
import ManagerData
38 from core.gcmd import EncodeString, DecodeString, GetRealCmd
41 """!PopUp window used by GPromptPopUp"""
42 def __init__(self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition,
43 size = wx.DefaultSize, style = 0):
44 wx.ListCtrl.__init__(self, parent, id, pos, size, style)
45 listmix.ListCtrlAutoWidthMixin.__init__(self)
48 """!Auto complete text area used by GPromptPopUp"""
49 def __init__ (self, parent, statusbar,
50 id = wx.ID_ANY, choices = [], **kwargs):
51 """!Constructor works just like wx.TextCtrl except you can pass in a
52 list of choices. You can also change the choice list at any time
53 by calling setChoices.
55 Inspired by http://wiki.wxpython.org/TextCtrlAutoComplete
60 kwargs[
'style'] = wx.TE_PROCESS_ENTER | kwargs[
'style']
62 kwargs[
'style'] = wx.TE_PROCESS_ENTER
64 wx.ComboBox.__init__(self, parent, id, **kwargs)
71 self.
_screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
80 except NotImplementedError:
82 raise NotImplementedError
86 style = wx.LC_REPORT | wx.LC_SINGLE_SEL | \
87 wx.LC_SORT_ASCENDING | wx.LC_NO_HEADER,
90 listmix.ColumnSorterMixin.__init__(self, 1)
95 for type
in (
'raster',
'vector'):
96 self.
_choicesMap[type] = grass.list_strings(type = type[:4])
100 self.SetMinSize(self.GetSize())
105 self.Bind(wx.EVT_TEXT_ENTER, self.OnRunCmd)
106 self.Bind(wx.EVT_KEY_DOWN , self.
OnKeyDown)
111 self.dropdownlistbox.Bind(wx.EVT_LEFT_DOWN, self.
OnListClick)
112 self.dropdownlistbox.Bind(wx.EVT_LEFT_DCLICK, self.
OnListDClick)
113 self.dropdownlistbox.Bind(wx.EVT_LIST_COL_CLICK, self.
OnListColClick)
117 def _updateDataList(self, choices):
118 """!Update data list"""
120 if self.dropdownlistbox.GetColumnCount() != 0:
121 self.dropdownlistbox.DeleteAllColumns()
122 self.dropdownlistbox.DeleteAllItems()
125 for numVal, data
in enumerate(choices):
129 self.SetColumnCount(numVal)
131 def _setListSize(self):
135 for choice
in choices:
136 longest =
max(len(choice), longest)
138 itemcount =
min(len( choices ), 7) + 2
139 charheight = self.dropdownlistbox.GetCharHeight()
140 charwidth = self.dropdownlistbox.GetCharWidth()
141 self.
popupsize = wx.Size(charwidth*longest, charheight*itemcount)
142 self.dropdownlistbox.SetSize(self.
popupsize)
143 self.dropdown.SetClientSize(self.
popupsize)
145 def _showDropDown(self, show = True):
146 """!Either display the drop down list (show = True) or hide it
150 size = self.dropdown.GetSize()
151 width, height = self.GetSizeTuple()
152 x, y = self.ClientToScreenXY(0, height)
153 if size.GetWidth() != width:
155 self.dropdown.SetSize(size)
156 self.dropdownlistbox.SetSize(self.dropdown.GetClientSize())
158 self.dropdown.SetPosition(wx.Point(x, y))
160 self.dropdown.SetPosition(wx.Point(x, y - height - size.GetHeight()))
162 self.dropdown.Show(show)
164 def _listItemVisible(self):
165 """!Moves the selected item to the top of the list ensuring it is
168 toSel = self.dropdownlistbox.GetFirstSelected()
171 self.dropdownlistbox.EnsureVisible(toSel)
173 def _setModule(self, name):
174 """!Set module's choices (flags, parameters)"""
178 self.
_module = gtask.parse_interface(name)
183 self.
_choicesMap[
'flag'] = self._module.get_list_flags()
186 desc = self._module.get_flag(item)[
'label']
188 desc = self._module.get_flag(item)[
'description']
190 self.
_choicesMap[
'flag'][idx] =
'%s (%s)' % (item, desc)
193 self.
_choicesMap[
'param'] = self._module.get_list_params()
196 desc = self._module.get_param(item)[
'label']
198 desc = self._module.get_param(item)[
'description']
200 self.
_choicesMap[
'param'][idx] =
'%s (%s)' % (item, desc)
202 def _setValueFromSelected(self):
203 """!Sets the wx.TextCtrl value from the selected wx.ListCtrl item.
204 Will do nothing if no item is selected in the wx.ListCtrl.
206 sel = self.dropdownlistbox.GetFirstSelected()
214 itemtext = self.dropdownlistbox.GetItem(sel, col).GetText()
220 itemtext = itemtext.split(
' ')[0]
221 self.SetValue(
' '.join(cmd) +
' ' + itemtext +
'=')
222 optType = self._module.get_param(itemtext)[
'prompt']
223 if optType
in (
'raster',
'vector'):
227 itemtext = itemtext.split(
' ')[0]
228 if len(itemtext) > 1:
232 self.SetValue(
' '.join(cmd[:-1]) +
' ' + prefix + itemtext)
234 self.SetValue(
' '.join(cmd[:-1]) +
' ' + cmd[-1].
split(
'=', 1)[0] +
'=' + itemtext)
237 self.SetValue(itemtext +
' ')
246 self.SetInsertionPointEnd()
251 """!Method required by listmix.ColumnSorterMixin"""
255 """!Sets the choices available in the popup wx.ListBox.
256 The items will be sorted case insensitively.
258 @param choices list of choices
259 @param type type of choices (module, param, flag, raster, vector)
264 self.dropdownlistbox.SetWindowStyleFlag(wx.LC_REPORT | wx.LC_SINGLE_SEL |
265 wx.LC_SORT_ASCENDING | wx.LC_NO_HEADER)
266 if not isinstance(choices, list):
267 self.
_choices = [ x
for x
in choices ]
274 self.dropdownlistbox.InsertColumn(0,
"")
275 for num, colVal
in enumerate(self.
_choices):
276 index = self.dropdownlistbox.InsertImageStringItem(sys.maxint, colVal, -1)
277 self.dropdownlistbox.SetStringItem(index, 0, colVal)
278 self.dropdownlistbox.SetItemData(index, num)
286 """Left mouse button pressed"""
287 sel = self.dropdownlistbox.GetFirstSelected()
288 if not self.dropdown.IsShown():
290 self.dropdownlistbox.Select(sel)
292 self.dropdownlistbox.Select(0)
299 """!Command selected from history"""
300 self.
_historyItem = event.GetSelection() - len(self.GetItems())
304 """!Left mouse button pressed"""
305 toSel, flag = self.dropdownlistbox.HitTest( evt.GetPosition() )
307 if toSel == -1:
return
308 self.dropdownlistbox.Select(toSel)
311 """!Mouse button double click"""
315 """!Left mouse button pressed on column"""
316 col = evt.GetColumn()
320 self.SortListItems( evt.GetColumn(), ascending=self.
_ascending )
331 text = event.GetString()
335 if self.dropdown.IsShown():
342 except ValueError, e:
343 self.statusbar.SetStatusText(str(e))
344 cmd = text.split(
' ')
351 if len(cmd[-1].
split(
'=', 1)) == 1:
353 if cmd[-1][0] ==
'-':
356 pattern = cmd[-1].lstrip(
'-')
363 pattern = cmd[-1].
split(
'=', 1)[1]
376 for numCh, choice
in enumerate(choices):
377 if choice.lower().startswith(pattern):
381 item = self.dropdownlistbox.GetItem(numCh)
383 self.dropdownlistbox.Select(toSel)
387 self.dropdownlistbox.Select(self.dropdownlistbox.GetFirstSelected(),
False)
390 if self.
_module and '=' not in cmd[-1]:
392 if cmd[-1][0] ==
'-':
393 message = _(
"Warning: flag <%(flag)s> not found in '%(module)s'") % \
394 {
'flag' : cmd[-1][1:],
'module' : self._module.name }
396 message = _(
"Warning: option <%(param)s> not found in '%(module)s'") % \
397 {
'param' : cmd[-1],
'module' : self._module.name }
398 self.statusbar.SetStatusText(message)
400 if self.
_module and len(cmd[-1]) == 2
and cmd[-1][-2] ==
'=':
401 optType = self._module.get_param(cmd[-1][:-2])[
'prompt']
402 if optType
in (
'raster',
'vector'):
411 """!Do some work when the user press on the keys: up and down:
412 move the cursor left and right: move the search
415 sel = self.dropdownlistbox.GetFirstSelected()
416 visible = self.dropdown.IsShown()
417 KC = event.GetKeyCode()
419 if KC == wx.WXK_RIGHT:
421 if sel < (self.dropdownlistbox.GetItemCount() - 1):
422 self.dropdownlistbox.Select(sel + 1)
426 elif KC == wx.WXK_UP:
429 self.dropdownlistbox.Select(sel - 1)
439 elif KC == wx.WXK_DOWN:
441 if sel < (self.dropdownlistbox.GetItemCount() - 1):
442 self.dropdownlistbox.Select(sel + 1)
452 if event.GetKeyCode() == wx.WXK_RETURN:
455 if event.GetKeyCode() == wx.WXK_ESCAPE:
462 """!Control changed"""
469 """!Abstract class for interactive wxGUI prompt
471 See subclass GPromptPopUp and GPromptSTC.
477 if self.parent.parent.GetName()
not in (
"LayerManager",
"Modeler"):
484 if self.parent.parent.GetName() ==
'Modeler':
487 self.
moduleDesc = parent.parent.menubar.GetData().GetModules()
506 def _readHistory(self):
507 """!Get list of commands from history file"""
511 fileHistory = codecs.open(os.path.join(env[
'GISDBASE'],
512 env[
'LOCATION_NAME'],
515 encoding =
'utf-8', mode =
'r', errors='replace')
520 for line
in fileHistory.readlines():
521 hist.append(line.replace(
'\n',
''))
528 """!Get description for given command"""
535 """!Get list of available commands"""
546 prefixes = mList.keys()
549 for prefix
in prefixes:
550 for command
in mList[prefix]:
551 name = prefix +
'.' + command
552 if name
not in items:
559 def _getListOfModules(self):
560 """!Get list of modules"""
562 for module
in globalvar.grassCmd:
564 group, name = module.split(
'.',1)
568 if group
not in result:
569 result[group] = list()
570 result[group].append(name)
574 for i
in range(len(name.split(
'.'))-1):
575 group =
'.'.join([group,name.split(
'.',1)[0]])
576 name = name.split(
'.',1)[1]
577 if group
not in result:
578 result[group] = list()
579 result[group].append(name)
582 for group
in result.keys():
587 def _getListOfMaps(self):
588 """!Get list of maps"""
590 result[
'raster'] = grass.list_strings(
'rast')
591 result[
'vector'] = grass.list_strings(
'vect')
595 def _runCmd(self, cmdString):
598 @param cmdString command to run (given as a string)
600 if self.parent.GetName() ==
"ModelerDialog":
601 self.parent.OnOk(
None)
607 if cmdString[:2] ==
'd.' and not self.parent.parent.curr_page:
608 self.parent.parent.NewDisplay(show =
True)
610 self.commands.append(cmdString)
617 cmd = map(DecodeString, cmd)
620 if cmd[0]
in (
'r.mapcalc',
'r3.mapcalc')
and len(cmd) == 1:
621 self.parent.parent.OnMapCalculator(event =
None, cmd = cmd)
623 self.parent.RunCmd(cmd)
626 self.UpdateCmdHistory(cmd)
627 self.OnCmdErase(
None)
628 self.parent.parent.statusbar.SetStatusText(
'')
631 """!Update Layer Manager status bar"""
636 self.parent.parent.statusbar.SetStatusText(
"")
638 self.parent.parent.statusbar.SetStatusText(_(
"Type GRASS command and run by pressing ENTER"))
642 """!Get main widget panel"""
646 """!Get main prompt widget"""
652 @param data data dict
653 @param module True to filter modules, otherwise data
667 """!Get list of launched commands"""
671 """!Clear list of commands"""
675 """!Interactive wxGUI prompt - popup version"""
677 GPrompt.__init__(self, parent)
682 TextCtrlAutoComplete.__init__(self, parent = self.
panel, id = wx.ID_ANY,
684 style = wx.TE_LINEWRAP | wx.TE_PROCESS_ENTER,
685 statusbar = self.parent.parent.statusbar)
687 except NotImplementedError:
690 wx.TextCtrl.__init__(parent = self.
panel, id = wx.ID_ANY,
692 style=wx.TE_LINEWRAP | wx.TE_PROCESS_ENTER,
694 self.searchBy.Enable(
False)
695 self.search.Enable(
False)
697 self.SetFont(wx.Font(10, wx.FONTFAMILY_MODERN, wx.NORMAL, wx.NORMAL, 0,
''))
699 wx.CallAfter(self.SetInsertionPoint, 0)
702 self.Bind(wx.EVT_TEXT_ENTER, self.
OnRunCmd)
706 """!Erase command prompt"""
707 self.input.SetValue(
'')
711 self.
_runCmd(event.GetString())
714 """!Styled wxGUI prompt with autocomplete and calltips"""
715 def __init__(self, parent, id = wx.ID_ANY, margin = False):
716 GPrompt.__init__(self, parent)
717 wx.stc.StyledTextCtrl.__init__(self, self.
panel, id)
722 self.SetWrapMode(
True)
723 self.SetUndoCollection(
True)
728 self.AutoCompSetIgnoreCase(
False)
734 self.SetMarginWidth(1, 0)
735 self.SetMarginWidth(2, 0)
737 self.SetMarginType(0, wx.stc.STC_MARGIN_NUMBER)
738 self.SetMarginWidth(0, 30)
740 self.SetMarginWidth(0, 0)
745 self.SetViewWhiteSpace(
False)
746 self.SetUseTabs(
False)
748 self.SetSelBackground(
True,
"#FFFF00")
749 self.SetUseHorizontalScrollBar(
True)
754 self.Bind(wx.EVT_WINDOW_DESTROY, self.
OnDestroy)
760 """!Copy selected text to clipboard and skip event.
761 The same function is in GMStc class (goutput.py).
767 """!Change text in statusbar
768 if the item selection in the auto-completion list is changed"""
779 desc = self.cmdDesc.get_flag(self.
autoCompList[event.GetIndex()])[
'description']
783 item = self.cmdDesc.get_param(self.
autoCompList[event.GetIndex()])
784 desc = item[
'name'] +
'=' + item[
'type']
785 if not item[
'required']:
786 desc =
'[' + desc +
']'
787 desc +=
': ' + item[
'description']
790 elif self.
toComplete[
'entity'] ==
'params+flags':
792 desc = self.cmdDesc.get_flag(self.
autoCompList[event.GetIndex()].strip(
'-'))[
'description']
794 item = self.cmdDesc.get_param(self.
autoCompList[event.GetIndex()])
795 desc = item[
'name'] +
'=' + item[
'type']
796 if not item[
'required']:
797 desc =
'[' + desc +
']'
798 desc +=
': ' + item[
'description']
804 """!Item selected from the list"""
807 match = difflib.SequenceMatcher(
None, event.GetText(), lastWord)
808 matchTuple = match.find_longest_match(0, len(event.GetText()), 0, len(lastWord))
810 compl = event.GetText()[matchTuple[2]:]
814 for char
in (
'.',
'-',
'='):
815 if text.split(
' ')[-1].find(char) >= 0:
823 self.SetCurrentPos(pos)
825 cmd = text.strip().
split(
' ')[0]
827 if not self.
cmdDesc or cmd != self.cmdDesc.get_name():
828 if cmd
in (
'r.mapcalc',
'v.type'):
829 cmd = cmd +
'_wrapper'
831 if cmd
in (
'r.mapcalc',
'r3.mapcalc')
and \
832 self.parent.parent.GetName() ==
'LayerManager':
833 self.parent.parent.OnMapCalculator(event =
None, cmd = [cmd])
844 """!Update command history
846 @param cmd command given as a list
849 self.cmdbuffer.append(
' '.join(cmd))
857 """!Determines which part of command (flags, parameters) should
858 be completed at current cursor position"""
862 cmd = entry.split()[0].strip()
870 if len(splitted) > 1:
871 if cmd
in globalvar.grassCmd:
872 toComplete[
'cmd'] = cmd
874 words = entry.split(
' ')
875 if any(word.startswith(
'-')
for word
in words):
876 toComplete[
'entity'] =
'params'
878 toComplete[
'entity'] =
'params+flags'
883 if word[0] ==
'=' and word[-1] ==
'@':
884 toComplete[
'entity'] =
'mapsets'
887 paramName = self.
GetWordLeft(withDelimiter =
False, ignoredDelimiter =
'=').strip(
'=')
890 param = self.cmdDesc.get_param(paramName)
891 except (ValueError, AttributeError):
897 toComplete[
'entity'] =
'param values'
898 elif param[
'prompt'] ==
'raster' and param[
'element'] ==
'cell':
899 toComplete[
'entity'] =
'raster map'
900 elif param[
'prompt'] ==
'vector' and param[
'element'] ==
'vector':
901 toComplete[
'entity'] =
'vector map'
903 toComplete[
'entity'] =
'flags'
905 toComplete[
'entity'] =
'params'
909 toComplete[
'entity'] =
'command'
910 toComplete[
'cmd'] = cmd
914 def GetWordLeft(self, withDelimiter = False, ignoredDelimiter = None):
915 """!Get word left from current cursor position. The beginning
916 of the word is given by space or chars: .,-=
918 @param withDelimiter returns the word with the initial delimeter
919 @param ignoredDelimiter finds the word ignoring certain delimeter
924 if ignoredDelimiter
is None:
925 ignoredDelimiter =
''
927 for char
in set(
' .,-=') -
set(ignoredDelimiter):
928 if not withDelimiter:
932 parts.append(delimiter + textLeft.rpartition(char)[2])
933 return min(parts, key=
lambda x: len(x))
936 """!Show sorted auto-completion list if it is not empty"""
938 self.autoCompList.sort()
939 self.AutoCompShow(lenEntered = 0, itemList =
' '.join(self.
autoCompList))
942 """!Key press capture for autocompletion, calltips, and command history
944 @todo event.ControlDown() for manual autocomplete
947 pos = self.GetCurrentPos()
949 if event.GetKeyCode() == 46
and not event.ShiftDown():
952 self.InsertText(pos,
'.')
958 except (KeyError, TypeError):
963 elif event.GetKeyCode() == 45
and not event.ShiftDown():
966 self.InsertText(pos,
'-')
971 for flag
in self.cmdDesc.get_options()[
'flags']:
972 if len(flag[
'name']) == 1:
973 self.autoCompList.append(flag[
'name'])
975 for flag
in self.cmdDesc.get_options()[
'flags']:
976 if len(flag[
'name']) > 1:
977 self.autoCompList.append(flag[
'name'])
981 elif event.GetKeyCode() == 61
and not event.ShiftDown():
983 self.InsertText(pos,
'=')
989 elif self.
toComplete[
'entity'] ==
'vector map':
991 elif self.
toComplete[
'entity'] ==
'param values':
992 param = self.
GetWordLeft(withDelimiter =
False, ignoredDelimiter=
'=').strip(
' =')
993 self.
autoCompList = self.cmdDesc.get_param(param)[
'values']
997 elif event.GetKeyCode() == 50
and event.ShiftDown():
999 self.InsertText(pos,
'@')
1008 elif event.GetKeyCode() == wx.WXK_SPACE
and event.ControlDown():
1016 for command
in globalvar.grassCmd:
1017 if command.find(self.
toComplete[
'cmd']) == 0:
1018 dotNumber = list(self.
toComplete[
'cmd']).count(
'.')
1019 self.autoCompList.append(command.split(
'.',dotNumber)[-1])
1026 for flag
in self.cmdDesc.get_options()[
'flags']:
1027 if len(flag[
'name']) == 1:
1028 self.autoCompList.append(flag[
'name'])
1034 for param
in self.cmdDesc.get_options()[
'params']:
1035 if param[
'name'].find(self.
GetWordLeft(withDelimiter=
False)) == 0:
1036 self.autoCompList.append(param[
'name'])
1044 for param
in self.cmdDesc.get_options()[
'params']:
1045 self.autoCompList.append(param[
'name'])
1046 for flag
in self.cmdDesc.get_options()[
'flags']:
1047 if len(flag[
'name']) == 1:
1048 self.autoCompList.append(
'-' + flag[
'name'])
1050 self.autoCompList.append(
'--' + flag[
'name'])
1057 elif self.
toComplete[
'entity'] ==
'raster map':
1060 elif self.
toComplete[
'entity'] ==
'vector map':
1063 elif self.
toComplete[
'entity'] ==
'param values':
1065 param = self.
GetWordLeft(withDelimiter =
False, ignoredDelimiter=
'=').strip(
' =')
1066 self.
autoCompList = self.cmdDesc.get_param(param)[
'values']
1070 elif event.GetKeyCode() == wx.WXK_TAB:
1074 cmd = entry.split()[0].strip()
1078 if cmd
not in globalvar.grassCmd:
1083 self.CallTipSetBackground(
"#f4f4d1")
1084 self.CallTipSetForeground(
"BLACK")
1085 self.CallTipShow(pos, info[
'usage'] +
'\n\n' + info[
'description'])
1088 elif event.GetKeyCode()
in [wx.WXK_UP, wx.WXK_DOWN]
and \
1089 not self.AutoCompActive():
1097 if event.GetKeyCode() == wx.WXK_UP:
1099 if event.GetKeyCode() == wx.WXK_DOWN:
1114 pos = self.GetCurrentPos()
1115 self.InsertText(pos,txt)
1117 self.parent.parent.statusbar.SetStatusText(
'')
1119 elif event.GetKeyCode()
in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]
and \
1120 self.AutoCompActive() ==
False:
1122 self.
_runCmd(self.GetCurLine()[0].strip())
1124 elif event.GetKeyCode() == wx.WXK_SPACE:
1127 cmd = items[0].strip()
1128 if cmd
in globalvar.grassCmd
and \
1129 cmd !=
'r.mapcalc' and \
1130 (
not self.
cmdDesc or cmd != self.cmdDesc.get_name()):
1142 """!Sets statusbar text, if it's too long, it is cut off"""
1143 maxLen = self.parent.parent.statusbar.GetFieldRect(0).GetWidth()/ 7
1144 if len(text) < maxLen:
1145 self.parent.parent.statusbar.SetStatusText(text)
1147 self.parent.parent.statusbar.SetStatusText(text[:maxLen]+
'...')
1151 """!Returns all text left of the caret"""
1152 pos = self.GetCurrentPos()
1154 entry = self.GetSelectedText()
1155 self.SetCurrentPos(pos)
1160 """!The clipboard contents can be preserved after
1161 the app has exited"""
1162 wx.TheClipboard.Flush()
1166 """!Erase command prompt"""