menu.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. """
  2. @package gui_core.menu
  3. @brief Menu classes for wxGUI
  4. Classes:
  5. - menu::Menu
  6. - menu::SearchModuleWindow
  7. (C) 2010-2013 by the GRASS Development Team
  8. This program is free software under the GNU General Public License
  9. (>=v2). Read the file COPYING that comes with GRASS for details.
  10. @author Martin Landa <landa.martin gmail.com>
  11. @author Pawel Netzel (menu customization)
  12. @author Milena Nowotarska (menu customization)
  13. @author Robert Szczepanek (menu customization)
  14. @author Vaclav Petras <wenzeslaus gmail.com> (menu customization)
  15. """
  16. import re
  17. import wx
  18. from core import globalvar
  19. from core import utils
  20. from core.gcmd import EncodeString
  21. from gui_core.widgets import SearchModuleWidget
  22. from gui_core.treeview import CTreeView
  23. from gui_core.wrap import Button, StaticText
  24. from gui_core.wrap import Menu as MenuWidget
  25. from icons.icon import MetaIcon
  26. from grass.pydispatch.signal import Signal
  27. class Menu(wx.MenuBar):
  28. def __init__(self, parent, model):
  29. """Creates menubar"""
  30. wx.MenuBar.__init__(self)
  31. self.parent = parent
  32. self.model = model
  33. self.menucmd = dict()
  34. self.bmpsize = (16, 16)
  35. for child in self.model.root.children:
  36. self.Append(self._createMenu(child), child.label)
  37. def _createMenu(self, node):
  38. """Creates menu"""
  39. menu = MenuWidget()
  40. for child in node.children:
  41. if child.children:
  42. label = child.label
  43. subMenu = self._createMenu(child)
  44. menu.AppendMenu(wx.ID_ANY, label, subMenu)
  45. else:
  46. data = child.data.copy()
  47. data.pop('label')
  48. self._createMenuItem(menu, label=child.label, **data)
  49. self.parent.Bind(wx.EVT_MENU_HIGHLIGHT_ALL, self.OnMenuHighlight)
  50. return menu
  51. def _createMenuItem(
  52. self, menu, label, description, handler, command, keywords,
  53. shortcut='', icon='', wxId=wx.ID_ANY, kind=wx.ITEM_NORMAL):
  54. """Creates menu items
  55. There are three menu styles (menu item text styles).
  56. 1 -- label only, 2 -- label and cmd name, 3 -- cmd name only
  57. """
  58. if not label:
  59. menu.AppendSeparator()
  60. return
  61. if command:
  62. helpString = command + ' -- ' + description
  63. else:
  64. helpString = description
  65. if shortcut:
  66. label += '\t' + shortcut
  67. menuItem = wx.MenuItem(menu, wxId, label, helpString, kind)
  68. if icon:
  69. menuItem.SetBitmap(MetaIcon(img=icon).GetBitmap(self.bmpsize))
  70. menu.AppendItem(menuItem)
  71. self.menucmd[menuItem.GetId()] = command
  72. if command:
  73. try:
  74. cmd = utils.split(str(command))
  75. except UnicodeError:
  76. cmd = utils.split(EncodeString((command)))
  77. # disable only grass commands which are not present (e.g.
  78. # r.in.lidar)
  79. if cmd and cmd[0] not in globalvar.grassCmd and \
  80. re.match('[rvdipmgt][3bs]?\.([a-z0-9\.])+', cmd[0]):
  81. menuItem.Enable(False)
  82. rhandler = eval('self.parent.' + handler)
  83. self.parent.Bind(wx.EVT_MENU, rhandler, menuItem)
  84. def GetData(self):
  85. """Get menu data"""
  86. return self.model
  87. def GetCmd(self):
  88. """Get dictionary of commands (key is id)
  89. :return: dictionary of commands
  90. """
  91. return self.menucmd
  92. def OnMenuHighlight(self, event):
  93. """
  94. Default menu help handler
  95. """
  96. # Show how to get menu item info from this event handler
  97. id = event.GetMenuId()
  98. item = self.FindItemById(id)
  99. if item:
  100. text = item.GetText()
  101. help = item.GetHelp()
  102. # but in this case just call Skip so the default is done
  103. event.Skip()
  104. class SearchModuleWindow(wx.Panel):
  105. """Menu tree and search widget for searching modules.
  106. Signal:
  107. showNotification - attribute 'message'
  108. """
  109. def __init__(self, parent, handlerObj, giface, model, id=wx.ID_ANY,
  110. **kwargs):
  111. self.parent = parent
  112. self._handlerObj = handlerObj
  113. self._giface = giface
  114. self.showNotification = Signal('SearchModuleWindow.showNotification')
  115. wx.Panel.__init__(self, parent=parent, id=id, **kwargs)
  116. # tree
  117. self._tree = CTreeView(model=model, parent=self)
  118. self._tree.SetToolTip(
  119. _("Double-click or Ctrl-Enter to run selected module"))
  120. # self._dataBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
  121. # label = " %s " % _("Module tree"))
  122. # search widget
  123. self._search = SearchModuleWidget(parent=self,
  124. model=model,
  125. showChoice=False)
  126. self._search.showSearchResult.connect(
  127. lambda result: self._tree.Select(result))
  128. self._search.showNotification.connect(self.showNotification)
  129. self._helpText = StaticText(
  130. parent=self, id=wx.ID_ANY,
  131. label="Press Enter for next match, Ctrl+Enter to run command")
  132. self._helpText.SetForegroundColour(
  133. wx.SystemSettings.GetColour(
  134. wx.SYS_COLOUR_GRAYTEXT))
  135. # buttons
  136. self._btnRun = Button(self, id=wx.ID_OK, label=_("&Run"))
  137. self._btnRun.SetToolTip(_("Run selected module from the tree"))
  138. self._btnHelp = Button(self, id=wx.ID_ANY, label=_("H&elp"))
  139. self._btnHelp.SetToolTip(
  140. _("Show manual for selected module from the tree"))
  141. self._btnAdvancedSearch = Button(self, id=wx.ID_ANY,
  142. label=_("Adva&nced search..."))
  143. self._btnAdvancedSearch.SetToolTip(
  144. _("Do advanced search using %s module") % 'g.search.module')
  145. # bindings
  146. self._btnRun.Bind(wx.EVT_BUTTON, lambda evt: self.Run())
  147. self._btnHelp.Bind(wx.EVT_BUTTON, lambda evt: self.Help())
  148. self._btnAdvancedSearch.Bind(wx.EVT_BUTTON,
  149. lambda evt: self.AdvancedSearch())
  150. self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
  151. self._tree.selectionChanged.connect(self.OnItemSelected)
  152. self._tree.itemActivated.connect(lambda node: self.Run(node))
  153. self._layout()
  154. self._search.SetFocus()
  155. def _layout(self):
  156. """Do dialog layout"""
  157. sizer = wx.BoxSizer(wx.VERTICAL)
  158. # body
  159. dataSizer = wx.BoxSizer(wx.HORIZONTAL)
  160. dataSizer.Add(self._tree, proportion=1,
  161. flag=wx.EXPAND)
  162. # buttons
  163. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  164. btnSizer.Add(self._btnAdvancedSearch, proportion=0)
  165. btnSizer.AddStretchSpacer()
  166. btnSizer.Add(self._btnHelp, proportion=0)
  167. btnSizer.Add(self._btnRun, proportion=0)
  168. sizer.Add(dataSizer, proportion=1,
  169. flag=wx.EXPAND | wx.ALL, border=5)
  170. sizer.Add(self._search, proportion=0,
  171. flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5)
  172. sizer.Add(btnSizer, proportion=0,
  173. flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5)
  174. sizer.Add(self._helpText,
  175. proportion=0, flag=wx.EXPAND | wx.LEFT, border=5)
  176. sizer.Fit(self)
  177. sizer.SetSizeHints(self)
  178. self.SetSizer(sizer)
  179. self.Fit()
  180. self.SetAutoLayout(True)
  181. self.Layout()
  182. def _GetSelectedNode(self):
  183. selection = self._tree.GetSelected()
  184. if not selection:
  185. return None
  186. return selection[0]
  187. def Run(self, node=None):
  188. """Run selected command.
  189. :param node: a tree node associated with the module or other item
  190. """
  191. if not node:
  192. node = self._GetSelectedNode()
  193. # nothing selected
  194. if not node:
  195. return
  196. data = node.data
  197. # non-leaf nodes
  198. if not data:
  199. return
  200. # extract name of the handler and create a new call
  201. handler = 'self._handlerObj.' + data['handler'].lstrip('self.')
  202. if data['command']:
  203. eval(handler)(event=None, cmd=data['command'].split())
  204. else:
  205. eval(handler)(event=None)
  206. def Help(self, node=None):
  207. """Show documentation for a module"""
  208. if not node:
  209. node = self._GetSelectedNode()
  210. # nothing selected
  211. if not node:
  212. return
  213. data = node.data
  214. # non-leaf nodes
  215. if not data:
  216. return
  217. if not data['command']:
  218. # showing nothing for non-modules
  219. return
  220. # strip parameters from command if present
  221. name = data['command'].split()[0]
  222. self._giface.Help(name)
  223. self.showNotification.emit(
  224. message=_("Documentation for %s is now open in the web browser")
  225. % name)
  226. def AdvancedSearch(self):
  227. """Show advanced search window"""
  228. self._handlerObj.RunMenuCmd(cmd=['g.search.modules'])
  229. def OnKeyUp(self, event):
  230. """Key or key combination pressed"""
  231. if event.ControlDown() and \
  232. event.GetKeyCode() in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER):
  233. self.Run()
  234. def OnItemSelected(self, node):
  235. """Item selected"""
  236. data = node.data
  237. if not data or 'command' not in data:
  238. return
  239. if data['command']:
  240. label = data['command']
  241. if data['description']:
  242. label += ' -- ' + data['description']
  243. else:
  244. label = data['description']
  245. self.showNotification.emit(message=label)