prompt.py 15 KB

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