toolbars.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. """
  2. @package gui_core.toolbars
  3. @brief Base classes toolbar widgets
  4. Classes:
  5. - toolbars::BaseToolbar
  6. (C) 2007-2011 by the GRASS Development Team
  7. This program is free software under the GNU General Public License
  8. (>=v2). Read the file COPYING that comes with GRASS for details.
  9. @author Michael Barton
  10. @author Jachym Cepicky
  11. @author Martin Landa <landa.martin gmail.com>
  12. """
  13. import platform
  14. import os
  15. import wx
  16. from core import globalvar
  17. from core.debug import Debug
  18. from icons.icon import MetaIcon
  19. from collections import defaultdict
  20. from core.globalvar import IMGDIR
  21. from gui_core.wrap import ToolBar, Menu, BitmapButton, NewId
  22. from grass.pydispatch.signal import Signal
  23. BaseIcons = {
  24. 'display': MetaIcon(img='show',
  25. label=_('Display map'),
  26. desc=_('Re-render modified map layers only')),
  27. 'render': MetaIcon(img='layer-redraw',
  28. label=_('Render map'),
  29. desc=_('Force re-rendering all map layers')),
  30. 'erase': MetaIcon(img='erase',
  31. label=_('Erase display'),
  32. desc=_('Erase display canvas with given background color')),
  33. 'pointer': MetaIcon(img='pointer',
  34. label=_('Pointer')),
  35. 'zoomIn': MetaIcon(img='zoom-in',
  36. label=_('Zoom in'),
  37. desc=_('Drag or click mouse to zoom')),
  38. 'zoomOut': MetaIcon(img='zoom-out',
  39. label=_('Zoom out'),
  40. desc=_('Drag or click mouse to unzoom')),
  41. 'zoomBack': MetaIcon(img='zoom-last',
  42. label=_('Return to previous zoom')),
  43. 'zoomMenu': MetaIcon(img='zoom-more',
  44. label=_('Various zoom options'),
  45. desc=_('Zoom to default or saved region, save to named region, ...')),
  46. 'zoomExtent': MetaIcon(img='zoom-extent',
  47. label=_('Zoom to selected map layer(s)')),
  48. 'zoomRegion': MetaIcon(img='zoom-region',
  49. label=_('Zoom to computational region extent')),
  50. 'pan': MetaIcon(img='pan',
  51. label=_('Pan'),
  52. desc=_('Drag with mouse to pan')),
  53. 'saveFile': MetaIcon(img='map-export',
  54. label=_('Save display to file')),
  55. 'print': MetaIcon(img='print',
  56. label=_('Print display')),
  57. 'font': MetaIcon(img='font',
  58. label=_('Select font')),
  59. 'help': MetaIcon(img='help',
  60. label=_('Show manual')),
  61. 'quit': MetaIcon(img='quit',
  62. label=_('Quit')),
  63. 'addRast': MetaIcon(img='layer-raster-add',
  64. label=_('Add raster map layer')),
  65. 'addVect': MetaIcon(img='layer-vector-add',
  66. label=_('Add vector map layer')),
  67. 'overlay': MetaIcon(img='overlay-add',
  68. label=_('Add map elements'),
  69. desc=_('Overlay elements like scale and legend onto map')),
  70. 'histogramD': MetaIcon(img='layer-raster-histogram',
  71. label=_('Create histogram with d.histogram')),
  72. 'settings': MetaIcon(img='settings',
  73. label=_("Settings")),
  74. }
  75. class BaseToolbar(ToolBar):
  76. """Abstract toolbar class.
  77. Following code shows how to create new basic toolbar:
  78. class MyToolbar(BaseToolbar):
  79. def __init__(self, parent):
  80. BaseToolbar.__init__(self, parent)
  81. self.InitToolbar(self._toolbarData())
  82. self.Realize()
  83. def _toolbarData(self):
  84. return self._getToolbarData((("help", Icons["help"],
  85. self.parent.OnHelp),
  86. ))
  87. """
  88. def __init__(self, parent, toolSwitcher=None,
  89. style=wx.NO_BORDER | wx.TB_HORIZONTAL):
  90. self.parent = parent
  91. wx.ToolBar.__init__(self, parent=self.parent, id=wx.ID_ANY,
  92. style=style)
  93. self._default = None
  94. self.SetToolBitmapSize(globalvar.toolbarSize)
  95. self.toolSwitcher = toolSwitcher
  96. self.handlers = {}
  97. def InitToolbar(self, toolData):
  98. """Initialize toolbar, add tools to the toolbar
  99. """
  100. for tool in toolData:
  101. self.CreateTool(*tool)
  102. self._data = toolData
  103. def _toolbarData(self):
  104. """Toolbar data (virtual)"""
  105. return None
  106. def CreateTool(self, label, bitmap, kind,
  107. shortHelp, longHelp, handler, pos=-1):
  108. """Add tool to the toolbar
  109. :param pos: if -1 add tool, if > 0 insert at given pos
  110. :return: id of tool
  111. """
  112. bmpDisabled = wx.NullBitmap
  113. tool = -1
  114. if label:
  115. tool = vars(self)[label] = NewId()
  116. Debug.msg(3, "CreateTool(): tool=%d, label=%s bitmap=%s" %
  117. (tool, label, bitmap))
  118. if pos < 0:
  119. toolWin = self.AddLabelTool(tool, label, bitmap,
  120. bmpDisabled, kind,
  121. shortHelp, longHelp)
  122. else:
  123. toolWin = self.InsertLabelTool(pos, tool, label, bitmap,
  124. bmpDisabled, kind,
  125. shortHelp, longHelp)
  126. self.handlers[tool] = handler
  127. self.Bind(wx.EVT_TOOL, handler, toolWin)
  128. self.Bind(wx.EVT_TOOL, self.OnTool, toolWin)
  129. else: # separator
  130. self.AddSeparator()
  131. return tool
  132. def EnableLongHelp(self, enable=True):
  133. """Enable/disable long help
  134. :param enable: True for enable otherwise disable
  135. """
  136. for tool in self._data:
  137. if tool[0] == '': # separator
  138. continue
  139. if enable:
  140. self.SetToolLongHelp(vars(self)[tool[0]], tool[4])
  141. else:
  142. self.SetToolLongHelp(vars(self)[tool[0]], "")
  143. def OnTool(self, event):
  144. """Tool selected
  145. """
  146. if self.toolSwitcher:
  147. Debug.msg(3, "BaseToolbar.OnTool(): id = %s" % event.GetId())
  148. self.toolSwitcher.ToolChanged(event.GetId())
  149. event.Skip()
  150. def SelectTool(self, id):
  151. self.ToggleTool(id, True)
  152. self.toolSwitcher.ToolChanged(id)
  153. self.handlers[id](event=None)
  154. def SelectDefault(self):
  155. """Select default tool"""
  156. self.SelectTool(self._default)
  157. def FixSize(self, width):
  158. """Fix toolbar width on Windows
  159. .. todo::
  160. Determine why combobox causes problems here
  161. """
  162. if platform.system() == 'Windows':
  163. size = self.GetBestSize()
  164. self.SetSize((size[0] + width, size[1]))
  165. def Enable(self, tool, enable=True):
  166. """Enable/Disable defined tool
  167. :param tool: name
  168. :param enable: True to enable otherwise disable tool
  169. """
  170. try:
  171. id = getattr(self, tool)
  172. except AttributeError:
  173. # TODO: test everything that this is not raised
  174. # this error was ignored for a long time
  175. raise AttributeError("Toolbar does not have a tool %s." % tool)
  176. return
  177. self.EnableTool(id, enable)
  178. def EnableAll(self, enable=True):
  179. """Enable/Disable all tools
  180. :param enable: True to enable otherwise disable tool
  181. """
  182. for item in self._toolbarData():
  183. if not item[0]:
  184. continue
  185. self.Enable(item[0], enable)
  186. def _getToolbarData(self, data):
  187. """Define tool
  188. """
  189. retData = list()
  190. for args in data:
  191. retData.append(self._defineTool(*args))
  192. return retData
  193. def _defineTool(self, name=None, icon=None, handler=None,
  194. item=wx.ITEM_NORMAL, pos=-1):
  195. """Define tool
  196. """
  197. if name:
  198. return (name, icon.GetBitmap(),
  199. item, icon.GetLabel(), icon.GetDesc(),
  200. handler, pos)
  201. return ("", "", "", "", "", "") # separator
  202. def _onMenu(self, data):
  203. """Toolbar pop-up menu"""
  204. menu = Menu()
  205. for icon, handler in data:
  206. item = wx.MenuItem(menu, wx.ID_ANY, icon.GetLabel())
  207. item.SetBitmap(icon.GetBitmap(self.parent.iconsize))
  208. menu.AppendItem(item)
  209. self.Bind(wx.EVT_MENU, handler, item)
  210. self.PopupMenu(menu)
  211. menu.Destroy()
  212. def CreateSelectionButton(self, tooltip=_("Select graphics tool")):
  213. """Add button to toolbar for selection of graphics drawing mode.
  214. Button must be custom (not toolbar tool) to set smaller width.
  215. """
  216. arrowPath = os.path.join(IMGDIR, 'small_down_arrow.png')
  217. if os.path.isfile(arrowPath) and os.path.getsize(arrowPath):
  218. bitmap = wx.Bitmap(name=arrowPath)
  219. else:
  220. bitmap = wx.ArtProvider.GetBitmap(
  221. id=wx.ART_MISSING_IMAGE, client=wx.ART_TOOLBAR)
  222. button = BitmapButton(parent=self, id=wx.ID_ANY, size=(
  223. (-1, self.GetToolSize()[1])), bitmap=bitmap, style=wx.NO_BORDER)
  224. button.SetToolTip(tooltip)
  225. return button
  226. class ToolSwitcher:
  227. """Class handling switching tools in toolbar and custom toggle buttons."""
  228. def __init__(self):
  229. self._groups = defaultdict(lambda: defaultdict(list))
  230. self._toolsGroups = defaultdict(list)
  231. # emitted when tool is changed
  232. self.toggleToolChanged = Signal('ToolSwitcher.toggleToolChanged')
  233. def AddToolToGroup(self, group, toolbar, tool):
  234. """Adds tool from toolbar to group of exclusive tools.
  235. :param group: name of group (e.g. 'mouseUse')
  236. :param toolbar: instance of toolbar
  237. :param tool: id of a tool from the toolbar
  238. """
  239. self._groups[group][toolbar].append(tool)
  240. self._toolsGroups[tool].append(group)
  241. def AddCustomToolToGroup(self, group, btnId, toggleHandler):
  242. """Adds custom tool from to group of exclusive tools (some toggle button).
  243. :param group: name of group (e.g. 'mouseUse')
  244. :param btnId: id of a tool (typically button)
  245. :param toggleHandler: handler to be called to switch the button
  246. """
  247. self._groups[group]['custom'].append((btnId, toggleHandler))
  248. self._toolsGroups[btnId].append(group)
  249. def RemoveCustomToolFromGroup(self, tool):
  250. """Removes custom tool from group.
  251. :param tool: id of the button
  252. """
  253. if tool not in self._toolsGroups:
  254. return
  255. for group in self._toolsGroups[tool]:
  256. self._groups[group]['custom'] = \
  257. [(bid, hdlr) for (bid, hdlr)
  258. in self._groups[group]['custom'] if bid != tool]
  259. def RemoveToolbarFromGroup(self, group, toolbar):
  260. """Removes toolbar from group.
  261. Before toolbar is destroyed, it must be removed from group, too.
  262. Otherwise we can expect some DeadObject errors.
  263. :param group: name of group (e.g. 'mouseUse')
  264. :param toolbar: instance of toolbar
  265. """
  266. for tb in self._groups[group]:
  267. if tb == toolbar:
  268. del self._groups[group][tb]
  269. break
  270. def IsToolInGroup(self, tool, group):
  271. """Checks whether a tool is in a specified group.
  272. :param tool: tool id
  273. :param group: name of group (e.g. 'mouseUse')
  274. """
  275. for group in self._toolsGroups[tool]:
  276. for tb in self._groups[group]:
  277. if tb == 'custom':
  278. for bid, handler in self._groups[group][tb]:
  279. if tool == bid:
  280. return True
  281. elif tb.FindById(tool):
  282. return True
  283. return False
  284. def ToolChanged(self, tool):
  285. """When any tool/button is pressed, other tools from group must be unchecked.
  286. :param tool: id of a tool/button
  287. """
  288. for group in self._toolsGroups[tool]:
  289. for tb in self._groups[group]:
  290. if tb == 'custom':
  291. for btnId, handler in self._groups[group][tb]:
  292. if btnId != tool:
  293. handler(False)
  294. else:
  295. for tl in self._groups[group][tb]:
  296. if tb.FindById(tl): # check if still exists
  297. if tl != tool:
  298. tb.ToggleTool(tl, False)
  299. else:
  300. tb.ToggleTool(tool, True)
  301. self.toggleToolChanged.emit(id=tool)