4 @brief wxGUI command prompt
13 (C) 2009-2011 by the GRASS Development Team
14 This program is free software under the GNU General Public
15 License (>=v2). Read the file COPYING that comes with GRASS
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)
31 import wx.lib.mixins.listctrl
as listmix
43 """!PopUp window used by GPromptPopUp"""
44 def __init__(self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition,
45 size = wx.DefaultSize, style = 0):
46 wx.ListCtrl.__init__(self, parent, id, pos, size, style)
47 listmix.ListCtrlAutoWidthMixin.__init__(self)
50 """!Auto complete text area used by GPromptPopUp"""
51 def __init__ (self, parent, statusbar,
52 id = wx.ID_ANY, choices = [], **kwargs):
53 """!Constructor works just like wx.TextCtrl except you can pass in a
54 list of choices. You can also change the choice list at any time
55 by calling setChoices.
57 Inspired by http://wiki.wxpython.org/TextCtrlAutoComplete
62 kwargs[
'style'] = wx.TE_PROCESS_ENTER | kwargs[
'style']
64 kwargs[
'style'] = wx.TE_PROCESS_ENTER
66 wx.ComboBox.__init__(self, parent, id, **kwargs)
73 self.
_screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
82 except NotImplementedError:
84 raise NotImplementedError
88 style = wx.LC_REPORT | wx.LC_SINGLE_SEL | \
89 wx.LC_SORT_ASCENDING | wx.LC_NO_HEADER,
92 listmix.ColumnSorterMixin.__init__(self, 1)
97 for type
in (
'raster',
'vector'):
98 self.
_choicesMap[type] = grass.list_strings(type = type[:4])
102 self.SetMinSize(self.GetSize())
107 self.Bind(wx.EVT_TEXT_ENTER, self.OnRunCmd)
108 self.Bind(wx.EVT_KEY_DOWN , self.
OnKeyDown)
113 self.dropdownlistbox.Bind(wx.EVT_LEFT_DOWN, self.
OnListClick)
114 self.dropdownlistbox.Bind(wx.EVT_LEFT_DCLICK, self.
OnListDClick)
115 self.dropdownlistbox.Bind(wx.EVT_LIST_COL_CLICK, self.
OnListColClick)
119 def _updateDataList(self, choices):
120 """!Update data list"""
122 if self.dropdownlistbox.GetColumnCount() != 0:
123 self.dropdownlistbox.DeleteAllColumns()
124 self.dropdownlistbox.DeleteAllItems()
127 for numVal, data
in enumerate(choices):
131 self.SetColumnCount(numVal)
133 def _setListSize(self):
137 for choice
in choices:
138 longest =
max(len(choice), longest)
140 itemcount =
min(len( choices ), 7) + 2
141 charheight = self.dropdownlistbox.GetCharHeight()
142 charwidth = self.dropdownlistbox.GetCharWidth()
143 self.
popupsize = wx.Size(charwidth*longest, charheight*itemcount)
144 self.dropdownlistbox.SetSize(self.
popupsize)
145 self.dropdown.SetClientSize(self.
popupsize)
147 def _showDropDown(self, show = True):
148 """!Either display the drop down list (show = True) or hide it
152 size = self.dropdown.GetSize()
153 width, height = self.GetSizeTuple()
154 x, y = self.ClientToScreenXY(0, height)
155 if size.GetWidth() != width:
157 self.dropdown.SetSize(size)
158 self.dropdownlistbox.SetSize(self.dropdown.GetClientSize())
160 self.dropdown.SetPosition(wx.Point(x, y))
162 self.dropdown.SetPosition(wx.Point(x, y - height - size.GetHeight()))
164 self.dropdown.Show(show)
166 def _listItemVisible(self):
167 """!Moves the selected item to the top of the list ensuring it is
170 toSel = self.dropdownlistbox.GetFirstSelected()
173 self.dropdownlistbox.EnsureVisible(toSel)
175 def _setModule(self, name):
176 """!Set module's choices (flags, parameters)"""
180 self.
_module = gtask.parse_interface(name)
185 self.
_choicesMap[
'flag'] = self._module.get_list_flags()
188 desc = self._module.get_flag(item)[
'label']
190 desc = self._module.get_flag(item)[
'description']
192 self.
_choicesMap[
'flag'][idx] =
'%s (%s)' % (item, desc)
195 self.
_choicesMap[
'param'] = self._module.get_list_params()
198 desc = self._module.get_param(item)[
'label']
200 desc = self._module.get_param(item)[
'description']
202 self.
_choicesMap[
'param'][idx] =
'%s (%s)' % (item, desc)
204 def _setValueFromSelected(self):
205 """!Sets the wx.TextCtrl value from the selected wx.ListCtrl item.
206 Will do nothing if no item is selected in the wx.ListCtrl.
208 sel = self.dropdownlistbox.GetFirstSelected()
216 itemtext = self.dropdownlistbox.GetItem(sel, col).GetText()
218 cmd = utils.split(str(self.GetValue()))
222 itemtext = itemtext.split(
' ')[0]
223 self.SetValue(
' '.join(cmd) +
' ' + itemtext +
'=')
224 optType = self._module.get_param(itemtext)[
'prompt']
225 if optType
in (
'raster',
'vector'):
229 itemtext = itemtext.split(
' ')[0]
230 if len(itemtext) > 1:
234 self.SetValue(
' '.join(cmd[:-1]) +
' ' + prefix + itemtext)
236 self.SetValue(
' '.join(cmd[:-1]) +
' ' + cmd[-1].
split(
'=', 1)[0] +
'=' + itemtext)
239 self.SetValue(itemtext +
' ')
248 self.SetInsertionPointEnd()
253 """!Method required by listmix.ColumnSorterMixin"""
257 """!Sets the choices available in the popup wx.ListBox.
258 The items will be sorted case insensitively.
260 @param choices list of choices
261 @param type type of choices (module, param, flag, raster, vector)
266 self.dropdownlistbox.SetWindowStyleFlag(wx.LC_REPORT | wx.LC_SINGLE_SEL |
267 wx.LC_SORT_ASCENDING | wx.LC_NO_HEADER)
268 if not isinstance(choices, list):
269 self.
_choices = [ x
for x
in choices ]
276 self.dropdownlistbox.InsertColumn(0,
"")
277 for num, colVal
in enumerate(self.
_choices):
278 index = self.dropdownlistbox.InsertImageStringItem(sys.maxint, colVal, -1)
279 self.dropdownlistbox.SetStringItem(index, 0, colVal)
280 self.dropdownlistbox.SetItemData(index, num)
288 """Left mouse button pressed"""
289 sel = self.dropdownlistbox.GetFirstSelected()
290 if not self.dropdown.IsShown():
292 self.dropdownlistbox.Select(sel)
294 self.dropdownlistbox.Select(0)
301 """!Command selected from history"""
302 self.
_historyItem = event.GetSelection() - len(self.GetItems())
306 """!Left mouse button pressed"""
307 toSel, flag = self.dropdownlistbox.HitTest( evt.GetPosition() )
309 if toSel == -1:
return
310 self.dropdownlistbox.Select(toSel)
313 """!Mouse button double click"""
317 """!Left mouse button pressed on column"""
318 col = evt.GetColumn()
322 self.SortListItems( evt.GetColumn(), ascending=self.
_ascending )
333 text = event.GetString()
337 if self.dropdown.IsShown():
343 cmd = utils.split(str(text))
344 except ValueError, e:
345 self.statusbar.SetStatusText(str(e))
346 cmd = text.split(
' ')
353 if len(cmd[-1].
split(
'=', 1)) == 1:
355 if cmd[-1][0] ==
'-':
358 pattern = cmd[-1].lstrip(
'-')
365 pattern = cmd[-1].
split(
'=', 1)[1]
378 for numCh, choice
in enumerate(choices):
379 if choice.lower().startswith(pattern):
383 item = self.dropdownlistbox.GetItem(numCh)
385 self.dropdownlistbox.Select(toSel)
389 self.dropdownlistbox.Select(self.dropdownlistbox.GetFirstSelected(),
False)
392 if self.
_module and '=' not in cmd[-1]:
394 if cmd[-1][0] ==
'-':
395 message = _(
"Warning: flag <%(flag)s> not found in '%(module)s'") % \
396 {
'flag' : cmd[-1][1:],
'module' : self._module.name }
398 message = _(
"Warning: option <%(param)s> not found in '%(module)s'") % \
399 {
'param' : cmd[-1],
'module' : self._module.name }
400 self.statusbar.SetStatusText(message)
402 if self.
_module and len(cmd[-1]) == 2
and cmd[-1][-2] ==
'=':
403 optType = self._module.get_param(cmd[-1][:-2])[
'prompt']
404 if optType
in (
'raster',
'vector'):
413 """!Do some work when the user press on the keys: up and down:
414 move the cursor left and right: move the search
417 sel = self.dropdownlistbox.GetFirstSelected()
418 visible = self.dropdown.IsShown()
419 KC = event.GetKeyCode()
421 if KC == wx.WXK_RIGHT:
423 if sel < (self.dropdownlistbox.GetItemCount() - 1):
424 self.dropdownlistbox.Select(sel + 1)
428 elif KC == wx.WXK_UP:
431 self.dropdownlistbox.Select(sel - 1)
441 elif KC == wx.WXK_DOWN:
443 if sel < (self.dropdownlistbox.GetItemCount() - 1):
444 self.dropdownlistbox.Select(sel + 1)
454 if event.GetKeyCode() == wx.WXK_RETURN:
457 if event.GetKeyCode() == wx.WXK_ESCAPE:
464 """!Control changed"""
471 """!Abstract class for interactive wxGUI prompt
473 See subclass GPromptPopUp and GPromptSTC.
479 if self.parent.parent.GetName()
not in (
"LayerManager",
"Modeler"):
486 if self.parent.parent.GetName() ==
'Modeler':
489 self.
moduleDesc = parent.parent.menubar.GetData().GetModules()
504 def _readHistory(self):
505 """!Get list of commands from history file"""
509 fileHistory = codecs.open(os.path.join(env[
'GISDBASE'],
510 env[
'LOCATION_NAME'],
513 encoding =
'utf-8', mode =
'r', errors='replace')
518 for line
in fileHistory.readlines():
519 hist.append(line.replace(
'\n',
''))
526 """!Get description for given command"""
533 """!Get list of available commands"""
544 prefixes = mList.keys()
547 for prefix
in prefixes:
548 for command
in mList[prefix]:
549 name = prefix +
'.' + command
550 if name
not in items:
557 def _getListOfModules(self):
558 """!Get list of modules"""
560 for module
in globalvar.grassCmd[
'all']:
562 group, name = module.split(
'.',1)
566 if group
not in result:
567 result[group] = list()
568 result[group].append(name)
572 for i
in range(len(name.split(
'.'))-1):
573 group =
'.'.join([group,name.split(
'.',1)[0]])
574 name = name.split(
'.',1)[1]
575 if group
not in result:
576 result[group] = list()
577 result[group].append(name)
580 for group
in result.keys():
585 def _getListOfMaps(self):
586 """!Get list of maps"""
588 result[
'raster'] = grass.list_strings(
'rast')
589 result[
'vector'] = grass.list_strings(
'vect')
595 cmdString = event.GetString()
600 if cmdString[:2] ==
'd.' and not self.parent.curr_page:
601 self.parent.NewDisplay(show=
True)
603 cmd = utils.split(cmdString)
605 self.parent.RunCmd(cmd, switchPage =
True)
607 self.parent.RunCmd(cmd, switchPage =
False)
612 """!Update Layer Manager status bar"""
617 self.parent.parent.statusbar.SetStatusText(
"")
619 self.parent.parent.statusbar.SetStatusText(_(
"Type GRASS command and run by pressing ENTER"))
623 """!Get main widget panel"""
627 """!Get main prompt widget"""
633 @param data data dict
634 @param module True to filter modules, otherwise data
648 """!Interactive wxGUI prompt - popup version"""
650 GPrompt.__init__(self, parent)
655 TextCtrlAutoComplete.__init__(self, parent = self.
panel, id = wx.ID_ANY,
657 style = wx.TE_LINEWRAP | wx.TE_PROCESS_ENTER,
658 statusbar = self.parent.parent.statusbar)
660 except NotImplementedError:
663 wx.TextCtrl.__init__(parent = self.
panel, id = wx.ID_ANY,
665 style=wx.TE_LINEWRAP | wx.TE_PROCESS_ENTER,
667 self.searchBy.Enable(
False)
668 self.search.Enable(
False)
670 self.SetFont(wx.Font(10, wx.FONTFAMILY_MODERN, wx.NORMAL, wx.NORMAL, 0,
''))
672 wx.CallAfter(self.SetInsertionPoint, 0)
675 self.Bind(wx.EVT_TEXT_ENTER, self.
OnRunCmd)
679 """!Erase command prompt"""
680 self.input.SetValue(
'')
683 """!Styled wxGUI prompt with autocomplete and calltips"""
684 def __init__(self, parent, id = wx.ID_ANY, margin = False):
685 GPrompt.__init__(self, parent)
686 wx.stc.StyledTextCtrl.__init__(self, self.
panel, id)
691 self.SetWrapMode(
True)
692 self.SetUndoCollection(
True)
697 self.AutoCompSetIgnoreCase(
False)
703 self.SetMarginWidth(1, 0)
704 self.SetMarginWidth(2, 0)
706 self.SetMarginType(0, wx.stc.STC_MARGIN_NUMBER)
707 self.SetMarginWidth(0, 30)
709 self.SetMarginWidth(0, 0)
714 self.SetViewWhiteSpace(
False)
715 self.SetUseTabs(
False)
717 self.SetSelBackground(
True,
"#FFFF00")
718 self.SetUseHorizontalScrollBar(
True)
723 self.Bind(wx.EVT_WINDOW_DESTROY, self.
OnDestroy)
729 """!Copy selected text to clipboard and skip event.
730 The same function is in GMStc class (goutput.py).
736 """!Change text in statusbar
737 if the item selection in the auto-completion list is changed"""
748 desc = self.cmdDesc.get_flag(self.
autoCompList[event.GetIndex()])[
'description']
752 item = self.cmdDesc.get_param(self.
autoCompList[event.GetIndex()])
753 desc = item[
'name'] +
'=' + item[
'type']
754 if not item[
'required']:
755 desc =
'[' + desc +
']'
756 desc +=
': ' + item[
'description']
759 elif self.
toComplete[
'entity'] ==
'params+flags':
761 desc = self.cmdDesc.get_flag(self.
autoCompList[event.GetIndex()].strip(
'-'))[
'description']
763 item = self.cmdDesc.get_param(self.
autoCompList[event.GetIndex()])
764 desc = item[
'name'] +
'=' + item[
'type']
765 if not item[
'required']:
766 desc =
'[' + desc +
']'
767 desc +=
': ' + item[
'description']
773 """!Item selected from the list"""
776 match = difflib.SequenceMatcher(
None, event.GetText(), lastWord)
777 matchTuple = match.find_longest_match(0, len(event.GetText()), 0, len(lastWord))
779 compl = event.GetText()[matchTuple[2]:]
783 for char
in (
'.',
'-',
'='):
784 if text.split(
' ')[-1].find(char) >= 0:
792 self.SetCurrentPos(pos)
794 cmd = text.strip().
split(
' ')[0]
796 if not self.
cmdDesc or cmd != self.cmdDesc.get_name():
797 if cmd
in (
'r.mapcalc',
'r3.mapcalc'):
798 self.parent.parent.OnMapCalculator(event =
None, cmd = [cmd])
803 if sys.platform ==
'win32':
804 if cmd
in globalvar.grassCmd[
'script']:
805 cmd += globalvar.EXT_SCT
812 """!Update command history
814 @param cmd command given as a list
817 self.cmdbuffer.append(
' '.join(cmd))
825 """!Determines which part of command (flags, parameters) should
826 be completed at current cursor position"""
830 cmd = entry.split()[0].strip()
834 if len(entry.split(
' ')) > 1:
835 if cmd
in globalvar.grassCmd[
'all']:
836 toComplete[
'cmd'] = cmd
838 words = entry.split(
' ')
839 if any(word.startswith(
'-')
for word
in words):
840 toComplete[
'entity'] =
'params'
843 toComplete[
'entity'] =
'params+flags'
851 paramName = self.
GetWordLeft(withDelimiter =
False, ignoredDelimiter =
'=').strip(
'=')
854 param = self.cmdDesc.get_param(paramName)
855 except (ValueError, AttributeError):
861 toComplete[
'entity'] =
'param values'
863 elif param[
'prompt'] ==
'raster' and param[
'element'] ==
'cell':
864 toComplete[
'entity'] =
'raster map'
866 elif param[
'prompt'] ==
'vector' and param[
'element'] ==
'vector':
867 toComplete[
'entity'] =
'vector map'
870 toComplete[
'entity'] =
'flags'
873 toComplete[
'entity'] =
'params'
879 toComplete[
'entity'] =
'command'
880 toComplete[
'cmd'] = cmd
883 def GetWordLeft(self, withDelimiter = False, ignoredDelimiter = None):
884 """!Get word left from current cursor position. The beginning
885 of the word is given by space or chars: .,-=
887 @param withDelimiter returns the word with the initial delimeter
888 @param ignoredDelimiter finds the word ignoring certain delimeter
893 if ignoredDelimiter
is None:
894 ignoredDelimiter =
''
896 for char
in set(
' .,-=') -
set(ignoredDelimiter):
897 if not withDelimiter:
901 parts.append(delimiter + textLeft.rpartition(char)[2])
902 return min(parts, key=
lambda x: len(x))
905 """!Show sorted auto-completion list if it is not empty"""
907 self.autoCompList.sort()
908 self.AutoCompShow(lenEntered = 0, itemList =
' '.join(self.
autoCompList))
911 """!Key press capture for autocompletion, calltips, and command history
913 @todo event.ControlDown() for manual autocomplete
916 pos = self.GetCurrentPos()
918 if event.GetKeyCode() == 46
and not event.ShiftDown():
921 self.InsertText(pos,
'.')
927 except (KeyError, TypeError):
932 elif event.GetKeyCode() == 45
and not event.ShiftDown():
935 self.InsertText(pos,
'-')
940 for flag
in self.cmdDesc.get_options()[
'flags']:
941 if len(flag[
'name']) == 1:
942 self.autoCompList.append(flag[
'name'])
944 for flag
in self.cmdDesc.get_options()[
'flags']:
945 if len(flag[
'name']) > 1:
946 self.autoCompList.append(flag[
'name'])
950 elif event.GetKeyCode() == 61
and not event.ShiftDown():
952 self.InsertText(pos,
'=')
958 elif self.
toComplete[
'entity'] ==
'vector map':
960 elif self.
toComplete[
'entity'] ==
'param values':
961 param = self.
GetWordLeft(withDelimiter =
False, ignoredDelimiter=
'=').strip(
' =')
962 self.
autoCompList = self.cmdDesc.get_param(param)[
'values']
966 elif event.GetKeyCode() == wx.WXK_SPACE
and event.ControlDown():
974 for command
in globalvar.grassCmd[
'all']:
976 dotNumber = list(self.
toComplete[
'cmd']).count(
'.')
977 self.autoCompList.append(command.split(
'.',dotNumber)[-1])
984 for flag
in self.cmdDesc.get_options()[
'flags']:
985 if len(flag[
'name']) == 1:
986 self.autoCompList.append(flag[
'name'])
992 for param
in self.cmdDesc.get_options()[
'params']:
993 if param[
'name'].find(self.
GetWordLeft(withDelimiter=
False)) == 0:
994 self.autoCompList.append(param[
'name'])
1002 for param
in self.cmdDesc.get_options()[
'params']:
1003 self.autoCompList.append(param[
'name'])
1004 for flag
in self.cmdDesc.get_options()[
'flags']:
1005 if len(flag[
'name']) == 1:
1006 self.autoCompList.append(
'-' + flag[
'name'])
1008 self.autoCompList.append(
'--' + flag[
'name'])
1015 elif self.
toComplete[
'entity'] ==
'raster map':
1018 elif self.
toComplete[
'entity'] ==
'vector map':
1021 elif self.
toComplete[
'entity'] ==
'param values':
1023 param = self.
GetWordLeft(withDelimiter =
False, ignoredDelimiter=
'=').strip(
' =')
1024 self.
autoCompList = self.cmdDesc.get_param(param)[
'values']
1028 elif event.GetKeyCode() == wx.WXK_TAB:
1032 cmd = entry.split()[0].strip()
1036 if cmd
not in globalvar.grassCmd[
'all']:
1039 if sys.platform ==
'win32':
1040 if cmd
in globalvar.grassCmd[
'script']:
1041 cmd += globalvar.EXT_SCT
1043 info = gtask.command_info(cmd)
1045 self.CallTipSetBackground(
"#f4f4d1")
1046 self.CallTipSetForeground(
"BLACK")
1047 self.CallTipShow(pos, info[
'usage'] +
'\n\n' + info[
'description'])
1050 elif event.GetKeyCode()
in [wx.WXK_UP, wx.WXK_DOWN]
and \
1051 not self.AutoCompActive():
1059 if event.GetKeyCode() == wx.WXK_UP:
1061 if event.GetKeyCode() == wx.WXK_DOWN:
1076 pos = self.GetCurrentPos()
1077 self.InsertText(pos,txt)
1079 self.parent.parent.statusbar.SetStatusText(
'')
1081 elif event.GetKeyCode() == wx.WXK_RETURN
and \
1082 self.AutoCompActive() ==
False:
1085 if self.parent.GetName() ==
"ModelerDialog":
1086 self.parent.OnOk(
None)
1090 line = self.GetCurLine()[0].strip()
1096 cmd = utils.split(str(line))
1097 except UnicodeError:
1098 cmd = utils.split(utils.EncodeString((line)))
1099 cmd =
map(utils.DecodeString, cmd)
1102 if cmd[0]
in (
'r.mapcalc',
'r3.mapcalc')
and len(cmd) == 1:
1103 self.parent.parent.OnMapCalculator(event =
None, cmd = cmd)
1105 self.parent.RunCmd(cmd)
1110 self.parent.parent.statusbar.SetStatusText(
'')
1112 elif event.GetKeyCode() == wx.WXK_SPACE:
1115 cmd = items[0].strip()
1116 if cmd
in globalvar.grassCmd[
'all']
and \
1117 cmd !=
'r.mapcalc' and \
1118 (
not self.
cmdDesc or cmd != self.cmdDesc.get_name()):
1119 if sys.platform ==
'win32':
1120 if cmd
in globalvar.grassCmd[
'script']:
1121 cmd += globalvar.EXT_SCT
1123 self.
cmdDesc = gtask.parse_interface(cmd)
1132 """!Sets statusbar text, if it's too long, it is cut off"""
1133 maxLen = self.parent.parent.statusbar.GetFieldRect(0).GetWidth()/ 7
1134 if len(text) < maxLen:
1135 self.parent.parent.statusbar.SetStatusText(text)
1137 self.parent.parent.statusbar.SetStatusText(text[:maxLen]+
'...')
1141 """!Returns all text left of the caret"""
1142 pos = self.GetCurrentPos()
1144 entry = self.GetSelectedText()
1145 self.SetCurrentPos(pos)
1150 """!The clipboard contents can be preserved after
1151 the app has exited"""
1152 wx.TheClipboard.Flush()
1156 """!Erase command prompt"""