prompt.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. """
  2. @package prompt.py
  3. @brief GRASS prompt
  4. Classes:
  5. - GPrompt
  6. - PromptListCtrl
  7. - TextCtrlAutoComplete
  8. (C) 2009 by the GRASS Development Team
  9. This program is free software under the GNU General Public
  10. License (>=v2). Read the file COPYING that comes with GRASS
  11. for details.
  12. @author Martin Landa <landa.martin gmail.com>
  13. """
  14. import sys
  15. import shlex
  16. import wx
  17. import wx.lib.mixins.listctrl as listmix
  18. import globalvar
  19. import utils
  20. import menuform
  21. from grass.script import core as grass
  22. class GPrompt:
  23. """Interactive GRASS prompt"""
  24. def __init__(self, parent):
  25. self.parent = parent
  26. self.panel, self.input = self.__create()
  27. def __create(self):
  28. """Create widget"""
  29. cmdprompt = wx.Panel(self.parent)
  30. label = wx.Button(parent = cmdprompt, id = wx.ID_ANY,
  31. label = _("Cmd >"), size = (-1, 25))
  32. label.SetToolTipString(_("Click for erasing command prompt"))
  33. cmdinput = TextCtrlAutoComplete(parent = cmdprompt, id = wx.ID_ANY,
  34. value = "",
  35. style = wx.TE_LINEWRAP | wx.TE_PROCESS_ENTER,
  36. size = (-1, 25))
  37. cmdinput.SetFont(wx.Font(10, wx.FONTFAMILY_MODERN, wx.NORMAL, wx.NORMAL, 0, ''))
  38. wx.CallAfter(cmdinput.SetInsertionPoint, 0)
  39. label.Bind(wx.EVT_BUTTON, self.OnCmdErase)
  40. cmdinput.Bind(wx.EVT_TEXT_ENTER, self.OnRunCmd)
  41. cmdinput.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
  42. # layout
  43. sizer = wx.BoxSizer(wx.HORIZONTAL)
  44. sizer.Add(item = label, proportion = 0,
  45. flag = wx.EXPAND | wx.LEFT | wx.RIGHT |
  46. wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER,
  47. border = 3)
  48. sizer.Add(item = cmdinput, proportion = 1,
  49. flag = wx.EXPAND | wx.ALL,
  50. border = 1)
  51. cmdprompt.SetSizer(sizer)
  52. sizer.Fit(cmdprompt)
  53. cmdprompt.Layout()
  54. return cmdprompt, cmdinput
  55. def GetPanel(self):
  56. """Get main widget panel"""
  57. return self.panel
  58. def GetInput(self):
  59. """Get main prompt widget"""
  60. return self.input
  61. def OnCmdErase(self, event):
  62. """Erase command prompt"""
  63. self.input.SetValue('')
  64. def OnRunCmd(self, event):
  65. """Run command"""
  66. cmdString = event.GetString()
  67. if self.parent.GetName() != "LayerManager":
  68. return
  69. if cmdString[:2] == 'd.' and not self.parent.curr_page:
  70. self.parent.NewDisplay(show=True)
  71. cmd = shlex.split(str(cmdString))
  72. if len(cmd) > 1:
  73. self.parent.goutput.RunCmd(cmd, switchPage = True)
  74. else:
  75. self.parent.goutput.RunCmd(cmd, switchPage = False)
  76. self.OnUpdateStatusBar(None)
  77. def OnUpdateStatusBar(self, event):
  78. """Update Layer Manager status bar"""
  79. if self.parent.GetName() != "LayerManager":
  80. return
  81. if event is None:
  82. self.parent.statusbar.SetStatusText("")
  83. else:
  84. self.parent.statusbar.SetStatusText(_("Type GRASS command and run by pressing ENTER"))
  85. event.Skip()
  86. class PromptListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
  87. def __init__(self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition,
  88. size = wx.DefaultSize, style = 0):
  89. wx.ListCtrl.__init__(self, parent, id, pos, size, style)
  90. listmix.ListCtrlAutoWidthMixin.__init__(self)
  91. class TextCtrlAutoComplete(wx.TextCtrl, listmix.ColumnSorterMixin):
  92. def __init__ (self, parent, id = wx.ID_ANY, choices = [], **kwargs):
  93. """
  94. Constructor works just like wx.TextCtrl except you can pass in a
  95. list of choices. You can also change the choice list at any time
  96. by calling setChoices.
  97. Inspired by http://wiki.wxpython.org/TextCtrlAutoComplete
  98. """
  99. if kwargs.has_key('style'):
  100. kwargs['style'] = wx.TE_PROCESS_ENTER | kwargs['style']
  101. else:
  102. kwargs['style'] = wx.TE_PROCESS_ENTER
  103. wx.TextCtrl.__init__(self, parent, id, **kwargs)
  104. # some variables
  105. self._choices = choices
  106. self._hideOnNoMatch = True
  107. self._module = None # currently selected module
  108. self._choiceType = None # type of choice (module, params, flags, raster, vector ...)
  109. self._screenheight = wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y)
  110. # sort variable needed by listmix
  111. self.itemDataMap = dict()
  112. # widgets
  113. self.dropdown = wx.PopupWindow(self)
  114. # create the list and bind the events
  115. self.dropdownlistbox = PromptListCtrl(parent = self.dropdown,
  116. style = wx.LC_REPORT | wx.LC_SINGLE_SEL | \
  117. wx.LC_SORT_ASCENDING | wx.LC_NO_HEADER,
  118. pos = wx.Point(0, 0))
  119. listmix.ColumnSorterMixin.__init__(self, 1)
  120. # set choices (list of GRASS modules)
  121. self._choicesCmd = globalvar.grassCmd['all']
  122. self._choicesMap = dict()
  123. for type in ('raster', 'vector'):
  124. self._choicesMap[type] = grass.list_strings(type = type[:4])
  125. # first search for GRASS module
  126. self.SetChoices(self._choicesCmd)
  127. # bindings...
  128. self.Bind(wx.EVT_KILL_FOCUS, self.OnControlChanged, self)
  129. self.Bind(wx.EVT_TEXT, self.OnEnteredText, self)
  130. self.Bind(wx.EVT_KEY_DOWN , self.OnKeyDown, self)
  131. # if need drop down on left click
  132. self.dropdown.Bind(wx.EVT_LISTBOX , self.OnListItemSelected, self.dropdownlistbox)
  133. self.dropdownlistbox.Bind(wx.EVT_LEFT_DOWN, self.OnListClick)
  134. self.dropdownlistbox.Bind(wx.EVT_LEFT_DCLICK, self.OnListDClick)
  135. self.dropdownlistbox.Bind(wx.EVT_LIST_COL_CLICK, self.OnListColClick)
  136. def _updateDataList(self, choices):
  137. """Update data list"""
  138. # delete, if need, all the previous data
  139. if self.dropdownlistbox.GetColumnCount() != 0:
  140. self.dropdownlistbox.DeleteAllColumns()
  141. self.dropdownlistbox.DeleteAllItems()
  142. # and update the dict
  143. if choices:
  144. for numVal, data in enumerate(choices):
  145. self.itemDataMap[numVal] = data
  146. else:
  147. numVal = 0
  148. self.SetColumnCount(numVal)
  149. def _setListSize(self):
  150. """Set list size"""
  151. choices = self._choices
  152. longest = 0
  153. for choice in choices:
  154. longest = max(len(choice), longest)
  155. longest += 3
  156. itemcount = min(len( choices ), 7) + 2
  157. charheight = self.dropdownlistbox.GetCharHeight()
  158. charwidth = self.dropdownlistbox.GetCharWidth()
  159. self.popupsize = wx.Size(charwidth*longest, charheight*itemcount)
  160. self.dropdownlistbox.SetSize(self.popupsize)
  161. self.dropdown.SetClientSize(self.popupsize)
  162. def _showDropDown(self, show = True):
  163. """
  164. Eit`her display the drop down list (show = True) or hide it
  165. (show = False).
  166. """
  167. if show:
  168. size = self.dropdown.GetSize()
  169. width, height = self.GetSizeTuple()
  170. x, y = self.ClientToScreenXY(0, height)
  171. if size.GetWidth() != width:
  172. size.SetWidth(width)
  173. self.dropdown.SetSize(size)
  174. self.dropdownlistbox.SetSize(self.dropdown.GetClientSize())
  175. if (y + size.GetHeight()) < self._screenheight:
  176. self.dropdown.SetPosition(wx.Point(x, y))
  177. else:
  178. self.dropdown.SetPosition(wx.Point(x, y - height - size.GetHeight()))
  179. self.dropdown.Show(show)
  180. def _listItemVisible(self):
  181. """
  182. Moves the selected item to the top of the list ensuring it is
  183. always visible.
  184. """
  185. toSel = self.dropdownlistbox.GetFirstSelected()
  186. if toSel == -1:
  187. return
  188. self.dropdownlistbox.EnsureVisible(toSel)
  189. def _setValueFromSelected(self):
  190. """
  191. Sets the wx.TextCtrl value from the selected wx.ListCtrl item.
  192. Will do nothing if no item is selected in the wx.ListCtrl.
  193. """
  194. sel = self.dropdownlistbox.GetFirstSelected()
  195. if sel > -1:
  196. if self._colFetch != -1:
  197. col = self._colFetch
  198. else:
  199. col = self._colSearch
  200. itemtext = self.dropdownlistbox.GetItem(sel, col).GetText()
  201. cmd = shlex.split(str(self.GetValue()))
  202. if len(cmd) > 1:
  203. # -> append text (skip last item)
  204. if self._choiceType == 'param':
  205. self.SetValue(' '.join(cmd[:-1]) + ' ' + itemtext + '=')
  206. optType = self._module.get_param(itemtext)['prompt']
  207. if optType in ('raster', 'vector'):
  208. # -> raster/vector map
  209. self.SetChoices(self._choicesMap[optType], optType)
  210. elif self._choiceType == 'flag':
  211. if len(itemtext) > 1:
  212. prefix = '--'
  213. else:
  214. prefix = '-'
  215. self.SetValue(' '.join(cmd[:-1]) + ' ' + prefix + itemtext)
  216. elif self._choiceType in ('raster', 'vector'):
  217. self.SetValue(' '.join(cmd[:-1]) + ' ' + cmd[-1].split('=', 1)[0] + '=' + itemtext)
  218. else:
  219. # -> reset text
  220. self.SetValue(itemtext + ' ')
  221. self.SetInsertionPointEnd()
  222. self._showDropDown(False)
  223. def GetListCtrl(self):
  224. """Method required by listmix.ColumnSorterMixin"""
  225. return self.dropdownlistbox
  226. def SetChoices(self, choices, type = 'module'):
  227. """
  228. Sets the choices available in the popup wx.ListBox.
  229. The items will be sorted case insensitively.
  230. """
  231. self._choices = choices
  232. self._choiceType = type
  233. self.dropdownlistbox.SetWindowStyleFlag(wx.LC_REPORT | wx.LC_SINGLE_SEL | \
  234. wx.LC_SORT_ASCENDING | wx.LC_NO_HEADER)
  235. if not isinstance(choices, list):
  236. self._choices = [ x for x in choices ]
  237. if self._choiceType not in ('raster', 'vector'):
  238. # do not sort raster/vector maps
  239. utils.ListSortLower(self._choices)
  240. self._updateDataList(self._choices)
  241. self.dropdownlistbox.InsertColumn(0, "")
  242. for num, colVal in enumerate(self._choices):
  243. index = self.dropdownlistbox.InsertImageStringItem(sys.maxint, colVal, -1)
  244. self.dropdownlistbox.SetStringItem(index, 0, colVal)
  245. self.dropdownlistbox.SetItemData(index, num)
  246. self._setListSize()
  247. # there is only one choice for both search and fetch if setting a single column:
  248. self._colSearch = 0
  249. self._colFetch = -1
  250. def OnListClick(self, evt):
  251. """Left mouse button pressed"""
  252. toSel, flag = self.dropdownlistbox.HitTest( evt.GetPosition() )
  253. #no values on poition, return
  254. if toSel == -1: return
  255. self.dropdownlistbox.Select(toSel)
  256. def OnListDClick(self, evt):
  257. """Mouse button double click"""
  258. self._setValueFromSelected()
  259. def OnListColClick(self, evt):
  260. """Left mouse button pressed on column"""
  261. col = evt.GetColumn()
  262. # reverse the sort
  263. if col == self._colSearch:
  264. self._ascending = not self._ascending
  265. self.SortListItems( evt.GetColumn(), ascending=self._ascending )
  266. self._colSearch = evt.GetColumn()
  267. evt.Skip()
  268. def OnListItemSelected(self, event):
  269. """Item selected"""
  270. self._setValueFromSelected()
  271. event.Skip()
  272. def OnEnteredText(self, event):
  273. """Text entered"""
  274. text = event.GetString()
  275. if not text:
  276. # control is empty; hide dropdown if shown:
  277. if self.dropdown.IsShown():
  278. self._showDropDown(False)
  279. event.Skip()
  280. return
  281. cmd = shlex.split(str(text))
  282. pattern = str(text)
  283. if len(cmd) > 1:
  284. # search for module's options
  285. if not self._module:
  286. self._module = menuform.GUI().ParseInterface(cmd = cmd)
  287. if len(cmd[-1].split('=', 1)) == 1:
  288. # new option
  289. if cmd[-1][0] == '-':
  290. # -> flags
  291. self.SetChoices(self._module.get_list_flags(), type = 'flag')
  292. pattern = cmd[-1].lstrip('-')
  293. else:
  294. # -> options
  295. self.SetChoices(self._module.get_list_params(), type = 'param')
  296. pattern = cmd[-1]
  297. else:
  298. # value
  299. pattern = cmd[-1].split('=', 1)[1]
  300. else:
  301. # search for GRASS modules
  302. if self._module:
  303. # -> switch back to GRASS modules list
  304. self.SetChoices(self._choicesCmd)
  305. self._module = None
  306. self._choiceType = None
  307. found = False
  308. choices = self._choices
  309. for numCh, choice in enumerate(choices):
  310. if choice.lower().startswith(pattern):
  311. found = True
  312. if found:
  313. self._showDropDown(True)
  314. item = self.dropdownlistbox.GetItem(numCh)
  315. toSel = item.GetId()
  316. self.dropdownlistbox.Select(toSel)
  317. break
  318. if not found:
  319. self.dropdownlistbox.Select(self.dropdownlistbox.GetFirstSelected(), False)
  320. if self._hideOnNoMatch:
  321. self._showDropDown(False)
  322. self._listItemVisible()
  323. event.Skip()
  324. def OnKeyDown (self, event):
  325. """
  326. Do some work when the user press on the keys: up and down:
  327. move the cursor left and right: move the search
  328. """
  329. skip = True
  330. sel = self.dropdownlistbox.GetFirstSelected()
  331. visible = self.dropdown.IsShown()
  332. KC = event.GetKeyCode()
  333. if KC == wx.WXK_DOWN:
  334. if sel < (self.dropdownlistbox.GetItemCount() - 1):
  335. self.dropdownlistbox.Select(sel + 1)
  336. self._listItemVisible()
  337. self._showDropDown()
  338. skip = False
  339. elif KC == wx.WXK_UP:
  340. if sel > 0:
  341. self.dropdownlistbox.Select(sel - 1)
  342. self._listItemVisible()
  343. self._showDropDown ()
  344. skip = False
  345. if visible:
  346. if event.GetKeyCode() == wx.WXK_RETURN:
  347. self._setValueFromSelected()
  348. skip = False
  349. if event.GetKeyCode() == wx.WXK_ESCAPE:
  350. self._showDropDown(False)
  351. skip = False
  352. if skip:
  353. event.Skip()
  354. def OnControlChanged(self, event):
  355. """Control changed"""
  356. if self.IsShown():
  357. self._showDropDown(False)
  358. event.Skip()