toolbars.py 12 KB

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