toolbars.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  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(
  72. img="monitor-settings", label=_("Map Display Settings")
  73. ),
  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__(
  89. self, parent, toolSwitcher=None, style=wx.NO_BORDER | wx.TB_HORIZONTAL
  90. ):
  91. self.parent = parent
  92. wx.ToolBar.__init__(self, parent=self.parent, id=wx.ID_ANY, 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. 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, shortHelp, longHelp, handler, pos=-1):
  106. """Add tool to the toolbar
  107. :param pos: if -1 add tool, if > 0 insert at given pos
  108. :return: id of tool
  109. """
  110. bmpDisabled = wx.NullBitmap
  111. tool = -1
  112. if label:
  113. tool = vars(self)[label] = NewId()
  114. Debug.msg(
  115. 3, "CreateTool(): tool=%d, label=%s bitmap=%s" % (tool, label, bitmap)
  116. )
  117. if pos < 0:
  118. toolWin = self.AddLabelTool(
  119. tool, label, bitmap, bmpDisabled, kind, shortHelp, longHelp
  120. )
  121. else:
  122. toolWin = self.InsertLabelTool(
  123. pos, tool, label, bitmap, bmpDisabled, kind, shortHelp, longHelp
  124. )
  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. if self.toolSwitcher:
  145. Debug.msg(3, "BaseToolbar.OnTool(): id = %s" % event.GetId())
  146. self.toolSwitcher.ToolChanged(event.GetId())
  147. event.Skip()
  148. def SelectTool(self, id):
  149. self.ToggleTool(id, True)
  150. self.toolSwitcher.ToolChanged(id)
  151. self.handlers[id](event=None)
  152. def SelectDefault(self):
  153. """Select default tool"""
  154. self.SelectTool(self._default)
  155. def FixSize(self, width):
  156. """Fix toolbar width on Windows
  157. .. todo::
  158. 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. retData = list()
  187. for args in data:
  188. retData.append(self._defineTool(*args))
  189. return retData
  190. def _defineTool(
  191. self, name=None, icon=None, handler=None, item=wx.ITEM_NORMAL, pos=-1
  192. ):
  193. """Define tool"""
  194. if name:
  195. return (
  196. name,
  197. icon.GetBitmap(),
  198. item,
  199. icon.GetLabel(),
  200. icon.GetDesc(),
  201. handler,
  202. pos,
  203. )
  204. return ("", "", "", "", "", "") # separator
  205. def _onMenu(self, data):
  206. """Toolbar pop-up menu"""
  207. menu = Menu()
  208. for icon, handler in data:
  209. item = wx.MenuItem(menu, wx.ID_ANY, icon.GetLabel())
  210. item.SetBitmap(icon.GetBitmap(self.parent.iconsize))
  211. menu.AppendItem(item)
  212. self.Bind(wx.EVT_MENU, handler, item)
  213. self.PopupMenu(menu)
  214. menu.Destroy()
  215. def CreateSelectionButton(self, tooltip=_("Select graphics tool")):
  216. """Add button to toolbar for selection of graphics drawing mode.
  217. Button must be custom (not toolbar tool) to set smaller width.
  218. """
  219. arrowPath = os.path.join(IMGDIR, "small_down_arrow.png")
  220. if os.path.isfile(arrowPath) and os.path.getsize(arrowPath):
  221. bitmap = wx.Bitmap(name=arrowPath)
  222. else:
  223. bitmap = wx.ArtProvider.GetBitmap(
  224. id=wx.ART_MISSING_IMAGE, client=wx.ART_TOOLBAR
  225. )
  226. button = BitmapButton(
  227. parent=self,
  228. id=wx.ID_ANY,
  229. size=((-1, self.GetToolSize()[1])),
  230. bitmap=bitmap,
  231. style=wx.NO_BORDER,
  232. )
  233. button.SetToolTip(tooltip)
  234. return button
  235. class ToolSwitcher:
  236. """Class handling switching tools in toolbar and custom toggle buttons."""
  237. def __init__(self):
  238. self._groups = defaultdict(lambda: defaultdict(list))
  239. self._toolsGroups = defaultdict(list)
  240. # emitted when tool is changed
  241. self.toggleToolChanged = Signal("ToolSwitcher.toggleToolChanged")
  242. def AddToolToGroup(self, group, toolbar, tool):
  243. """Adds tool from toolbar to group of exclusive tools.
  244. :param group: name of group (e.g. 'mouseUse')
  245. :param toolbar: instance of toolbar
  246. :param tool: id of a tool from the toolbar
  247. """
  248. self._groups[group][toolbar].append(tool)
  249. self._toolsGroups[tool].append(group)
  250. def AddCustomToolToGroup(self, group, btnId, toggleHandler):
  251. """Adds custom tool from to group of exclusive tools (some toggle button).
  252. :param group: name of group (e.g. 'mouseUse')
  253. :param btnId: id of a tool (typically button)
  254. :param toggleHandler: handler to be called to switch the button
  255. """
  256. self._groups[group]["custom"].append((btnId, toggleHandler))
  257. self._toolsGroups[btnId].append(group)
  258. def RemoveCustomToolFromGroup(self, tool):
  259. """Removes custom tool from group.
  260. :param tool: id of the button
  261. """
  262. if tool not in self._toolsGroups:
  263. return
  264. for group in self._toolsGroups[tool]:
  265. self._groups[group]["custom"] = [
  266. (bid, hdlr)
  267. for (bid, hdlr) in self._groups[group]["custom"]
  268. if bid != tool
  269. ]
  270. def RemoveToolbarFromGroup(self, group, toolbar):
  271. """Removes toolbar from group.
  272. Before toolbar is destroyed, it must be removed from group, too.
  273. Otherwise we can expect some DeadObject errors.
  274. :param group: name of group (e.g. 'mouseUse')
  275. :param toolbar: instance of toolbar
  276. """
  277. for tb in self._groups[group]:
  278. if tb == toolbar:
  279. del self._groups[group][tb]
  280. break
  281. def IsToolInGroup(self, tool, group):
  282. """Checks whether a tool is in a specified group.
  283. :param tool: tool id
  284. :param group: name of group (e.g. 'mouseUse')
  285. """
  286. for group in self._toolsGroups[tool]:
  287. for tb in self._groups[group]:
  288. if tb == "custom":
  289. for bid, handler in self._groups[group][tb]:
  290. if tool == bid:
  291. return True
  292. elif tb.FindById(tool):
  293. return True
  294. return False
  295. def ToolChanged(self, tool):
  296. """When any tool/button is pressed, other tools from group must be unchecked.
  297. :param tool: id of a tool/button
  298. """
  299. for group in self._toolsGroups[tool]:
  300. for tb in self._groups[group]:
  301. if tb == "custom":
  302. for btnId, handler in self._groups[group][tb]:
  303. if btnId != tool:
  304. handler(False)
  305. else:
  306. for tl in self._groups[group][tb]:
  307. if tb.FindById(tl): # check if still exists
  308. if tl != tool:
  309. tb.ToggleTool(tl, False)
  310. else:
  311. tb.ToggleTool(tool, True)
  312. self.toggleToolChanged.emit(id=tool)