toolbars.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  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. }
  72. class BaseToolbar(ToolBar):
  73. """Abstract toolbar class.
  74. Following code shows how to create new basic toolbar:
  75. class MyToolbar(BaseToolbar):
  76. def __init__(self, parent):
  77. BaseToolbar.__init__(self, parent)
  78. self.InitToolbar(self._toolbarData())
  79. self.Realize()
  80. def _toolbarData(self):
  81. return self._getToolbarData((("help", Icons["help"],
  82. self.parent.OnHelp),
  83. ))
  84. """
  85. def __init__(
  86. self, parent, toolSwitcher=None, style=wx.NO_BORDER | wx.TB_HORIZONTAL
  87. ):
  88. self.parent = parent
  89. wx.ToolBar.__init__(self, parent=self.parent, id=wx.ID_ANY, style=style)
  90. self._default = None
  91. self.SetToolBitmapSize(globalvar.toolbarSize)
  92. self.toolSwitcher = toolSwitcher
  93. self.handlers = {}
  94. def InitToolbar(self, toolData):
  95. """Initialize toolbar, add tools to the toolbar"""
  96. for tool in toolData:
  97. self.CreateTool(*tool)
  98. self._data = toolData
  99. def _toolbarData(self):
  100. """Toolbar data (virtual)"""
  101. return None
  102. def CreateTool(self, label, bitmap, kind, shortHelp, longHelp, handler, pos=-1):
  103. """Add tool to the toolbar
  104. :param pos: if -1 add tool, if > 0 insert at given pos
  105. :return: id of tool
  106. """
  107. bmpDisabled = wx.NullBitmap
  108. tool = -1
  109. if label:
  110. tool = vars(self)[label] = NewId()
  111. Debug.msg(
  112. 3, "CreateTool(): tool=%d, label=%s bitmap=%s" % (tool, label, bitmap)
  113. )
  114. if pos < 0:
  115. toolWin = self.AddLabelTool(
  116. tool, label, bitmap, bmpDisabled, kind, shortHelp, longHelp
  117. )
  118. else:
  119. toolWin = self.InsertLabelTool(
  120. pos, tool, label, bitmap, bmpDisabled, kind, shortHelp, longHelp
  121. )
  122. self.handlers[tool] = handler
  123. self.Bind(wx.EVT_TOOL, handler, toolWin)
  124. self.Bind(wx.EVT_TOOL, self.OnTool, toolWin)
  125. else: # separator
  126. self.AddSeparator()
  127. return tool
  128. def EnableLongHelp(self, enable=True):
  129. """Enable/disable long help
  130. :param enable: True for enable otherwise disable
  131. """
  132. for tool in self._data:
  133. if tool[0] == "": # separator
  134. continue
  135. if enable:
  136. self.SetToolLongHelp(vars(self)[tool[0]], tool[4])
  137. else:
  138. self.SetToolLongHelp(vars(self)[tool[0]], "")
  139. def OnTool(self, event):
  140. """Tool selected"""
  141. if self.toolSwitcher:
  142. Debug.msg(3, "BaseToolbar.OnTool(): id = %s" % event.GetId())
  143. self.toolSwitcher.ToolChanged(event.GetId())
  144. event.Skip()
  145. def SelectTool(self, id):
  146. self.ToggleTool(id, True)
  147. self.toolSwitcher.ToolChanged(id)
  148. self.handlers[id](event=None)
  149. def SelectDefault(self):
  150. """Select default tool"""
  151. self.SelectTool(self._default)
  152. def FixSize(self, width):
  153. """Fix toolbar width on Windows
  154. .. todo::
  155. Determine why combobox causes problems here
  156. """
  157. if platform.system() == "Windows":
  158. size = self.GetBestSize()
  159. self.SetSize((size[0] + width, size[1]))
  160. def Enable(self, tool, enable=True):
  161. """Enable/Disable defined tool
  162. :param tool: name
  163. :param enable: True to enable otherwise disable tool
  164. """
  165. try:
  166. id = getattr(self, tool)
  167. except AttributeError:
  168. # TODO: test everything that this is not raised
  169. # this error was ignored for a long time
  170. raise AttributeError("Toolbar does not have a tool %s." % tool)
  171. return
  172. self.EnableTool(id, enable)
  173. def EnableAll(self, enable=True):
  174. """Enable/Disable all tools
  175. :param enable: True to enable otherwise disable tool
  176. """
  177. for item in self._toolbarData():
  178. if not item[0]:
  179. continue
  180. self.Enable(item[0], enable)
  181. def _getToolbarData(self, data):
  182. """Define tool"""
  183. retData = list()
  184. for args in data:
  185. retData.append(self._defineTool(*args))
  186. return retData
  187. def _defineTool(
  188. self, name=None, icon=None, handler=None, item=wx.ITEM_NORMAL, pos=-1
  189. ):
  190. """Define tool"""
  191. if name:
  192. return (
  193. name,
  194. icon.GetBitmap(),
  195. item,
  196. icon.GetLabel(),
  197. icon.GetDesc(),
  198. handler,
  199. pos,
  200. )
  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. )
  223. button = BitmapButton(
  224. parent=self,
  225. id=wx.ID_ANY,
  226. size=((-1, self.GetToolSize()[1])),
  227. bitmap=bitmap,
  228. style=wx.NO_BORDER,
  229. )
  230. button.SetToolTip(tooltip)
  231. return button
  232. class ToolSwitcher:
  233. """Class handling switching tools in toolbar and custom toggle buttons."""
  234. def __init__(self):
  235. self._groups = defaultdict(lambda: defaultdict(list))
  236. self._toolsGroups = defaultdict(list)
  237. # emitted when tool is changed
  238. self.toggleToolChanged = Signal("ToolSwitcher.toggleToolChanged")
  239. def AddToolToGroup(self, group, toolbar, tool):
  240. """Adds tool from toolbar to group of exclusive tools.
  241. :param group: name of group (e.g. 'mouseUse')
  242. :param toolbar: instance of toolbar
  243. :param tool: id of a tool from the toolbar
  244. """
  245. self._groups[group][toolbar].append(tool)
  246. self._toolsGroups[tool].append(group)
  247. def AddCustomToolToGroup(self, group, btnId, toggleHandler):
  248. """Adds custom tool from to group of exclusive tools (some toggle button).
  249. :param group: name of group (e.g. 'mouseUse')
  250. :param btnId: id of a tool (typically button)
  251. :param toggleHandler: handler to be called to switch the button
  252. """
  253. self._groups[group]["custom"].append((btnId, toggleHandler))
  254. self._toolsGroups[btnId].append(group)
  255. def RemoveCustomToolFromGroup(self, tool):
  256. """Removes custom tool from group.
  257. :param tool: id of the button
  258. """
  259. if tool not in self._toolsGroups:
  260. return
  261. for group in self._toolsGroups[tool]:
  262. self._groups[group]["custom"] = [
  263. (bid, hdlr)
  264. for (bid, hdlr) in self._groups[group]["custom"]
  265. if bid != tool
  266. ]
  267. def RemoveToolbarFromGroup(self, group, toolbar):
  268. """Removes toolbar from group.
  269. Before toolbar is destroyed, it must be removed from group, too.
  270. Otherwise we can expect some DeadObject errors.
  271. :param group: name of group (e.g. 'mouseUse')
  272. :param toolbar: instance of toolbar
  273. """
  274. for tb in self._groups[group]:
  275. if tb == toolbar:
  276. del self._groups[group][tb]
  277. break
  278. def IsToolInGroup(self, tool, group):
  279. """Checks whether a tool is in a specified group.
  280. :param tool: tool id
  281. :param group: name of group (e.g. 'mouseUse')
  282. """
  283. for group in self._toolsGroups[tool]:
  284. for tb in self._groups[group]:
  285. if tb == "custom":
  286. for bid, handler in self._groups[group][tb]:
  287. if tool == bid:
  288. return True
  289. elif tb.FindById(tool):
  290. return True
  291. return False
  292. def ToolChanged(self, tool):
  293. """When any tool/button is pressed, other tools from group must be unchecked.
  294. :param tool: id of a tool/button
  295. """
  296. for group in self._toolsGroups[tool]:
  297. for tb in self._groups[group]:
  298. if tb == "custom":
  299. for btnId, handler in self._groups[group][tb]:
  300. if btnId != tool:
  301. handler(False)
  302. else:
  303. for tl in self._groups[group][tb]:
  304. if tb.FindById(tl): # check if still exists
  305. if tl != tool:
  306. tb.ToggleTool(tl, False)
  307. else:
  308. tb.ToggleTool(tool, True)
  309. self.toggleToolChanged.emit(id=tool)