toolbars.py 12 KB

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