toolbars.py 12 KB

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