toolbars.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  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. "histogram": 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. # e.g. ("help", _("Help")) tool short label (triangle/arrow
  85. # at the right side of the toolbar)
  86. return self._getToolbarData(
  87. (
  88. ("help", Icons["help"].label),
  89. Icons["help"],
  90. self.parent.OnHelp
  91. ),
  92. )
  93. """
  94. def __init__(
  95. self, parent, toolSwitcher=None, style=wx.NO_BORDER | wx.TB_HORIZONTAL
  96. ):
  97. self.parent = parent
  98. wx.ToolBar.__init__(self, parent=self.parent, id=wx.ID_ANY, style=style)
  99. self._default = None
  100. self.SetToolBitmapSize(globalvar.toolbarSize)
  101. self.toolSwitcher = toolSwitcher
  102. self.handlers = {}
  103. def InitToolbar(self, toolData):
  104. """Initialize toolbar, add tools to the toolbar"""
  105. for tool in toolData:
  106. self.CreateTool(*tool)
  107. self._data = toolData
  108. def _toolbarData(self):
  109. """Toolbar data (virtual)"""
  110. return None
  111. def CreateTool(self, label, bitmap, kind, shortHelp, longHelp, handler, pos=-1):
  112. """Add tool to the toolbar
  113. :param pos: if -1 add tool, if > 0 insert at given pos
  114. :return: id of tool
  115. """
  116. bmpDisabled = wx.NullBitmap
  117. tool = -1
  118. if isinstance(label, tuple):
  119. internal_label, label = label[0], label[1]
  120. else:
  121. internal_label = label
  122. if label:
  123. tool = vars(self)[internal_label] = NewId()
  124. Debug.msg(
  125. 3, "CreateTool(): tool=%d, label=%s bitmap=%s" % (tool, label, bitmap)
  126. )
  127. if pos < 0:
  128. toolWin = self.AddLabelTool(
  129. tool, label, bitmap, bmpDisabled, kind, shortHelp, longHelp
  130. )
  131. else:
  132. toolWin = self.InsertLabelTool(
  133. pos, tool, label, bitmap, bmpDisabled, kind, shortHelp, longHelp
  134. )
  135. self.handlers[tool] = handler
  136. self.Bind(wx.EVT_TOOL, handler, toolWin)
  137. self.Bind(wx.EVT_TOOL, self.OnTool, toolWin)
  138. else: # separator
  139. self.AddSeparator()
  140. return tool
  141. def EnableLongHelp(self, enable=True):
  142. """Enable/disable long help
  143. :param enable: True for enable otherwise disable
  144. """
  145. for tool in self._data:
  146. if isinstance(tool[0], tuple):
  147. if tool[0][0] == "": # separator
  148. continue
  149. else:
  150. internal_label = tool[0][0]
  151. else:
  152. if tool[0] == "": # separator
  153. continue
  154. else:
  155. internal_label = tool[0]
  156. if enable:
  157. self.SetToolLongHelp(vars(self)[internal_label], tool[4])
  158. else:
  159. self.SetToolLongHelp(vars(self)[internal_label], "")
  160. def OnTool(self, event):
  161. """Tool selected"""
  162. if self.toolSwitcher:
  163. Debug.msg(3, "BaseToolbar.OnTool(): id = %s" % event.GetId())
  164. self.toolSwitcher.ToolChanged(event.GetId())
  165. event.Skip()
  166. def SelectTool(self, id):
  167. self.ToggleTool(id, True)
  168. self.toolSwitcher.ToolChanged(id)
  169. self.handlers[id](event=None)
  170. def SelectDefault(self):
  171. """Select default tool"""
  172. self.SelectTool(self._default)
  173. def FixSize(self, width):
  174. """Fix toolbar width on Windows
  175. .. todo::
  176. Determine why combobox causes problems here
  177. """
  178. if platform.system() == "Windows":
  179. size = self.GetBestSize()
  180. self.SetSize((size[0] + width, size[1]))
  181. def Enable(self, tool, enable=True):
  182. """Enable/Disable defined tool
  183. :param str/tuple tool: name
  184. :param enable: True to enable otherwise disable tool
  185. """
  186. try:
  187. if isinstance(tool, tuple):
  188. id = getattr(self, tool[0])
  189. else:
  190. id = getattr(self, tool)
  191. except AttributeError:
  192. # TODO: test everything that this is not raised
  193. # this error was ignored for a long time
  194. raise AttributeError("Toolbar does not have a tool %s." % tool)
  195. return
  196. self.EnableTool(id, enable)
  197. def EnableAll(self, enable=True):
  198. """Enable/Disable all tools
  199. :param enable: True to enable otherwise disable tool
  200. """
  201. for item in self._toolbarData():
  202. if not item[0]:
  203. continue
  204. self.Enable(item[0], enable)
  205. def _getToolbarData(self, data):
  206. """Define tool"""
  207. retData = list()
  208. for args in data:
  209. retData.append(self._defineTool(*args))
  210. return retData
  211. def _defineTool(
  212. self, name=None, icon=None, handler=None, item=wx.ITEM_NORMAL, pos=-1
  213. ):
  214. """Define tool"""
  215. if name:
  216. return (
  217. name,
  218. icon.GetBitmap(),
  219. item,
  220. icon.GetLabel(),
  221. icon.GetDesc(),
  222. handler,
  223. pos,
  224. )
  225. return ("", "", "", "", "", "") # separator
  226. def _onMenu(self, data):
  227. """Toolbar pop-up menu"""
  228. menu = Menu()
  229. for icon, handler in data:
  230. item = wx.MenuItem(menu, wx.ID_ANY, icon.GetLabel())
  231. item.SetBitmap(icon.GetBitmap(self.parent.iconsize))
  232. menu.AppendItem(item)
  233. self.Bind(wx.EVT_MENU, handler, item)
  234. self.PopupMenu(menu)
  235. menu.Destroy()
  236. def CreateSelectionButton(self, tooltip=_("Select graphics tool")):
  237. """Add button to toolbar for selection of graphics drawing mode.
  238. Button must be custom (not toolbar tool) to set smaller width.
  239. """
  240. arrowPath = os.path.join(IMGDIR, "small_down_arrow.png")
  241. if os.path.isfile(arrowPath) and os.path.getsize(arrowPath):
  242. bitmap = wx.Bitmap(name=arrowPath)
  243. else:
  244. bitmap = wx.ArtProvider.GetBitmap(
  245. id=wx.ART_MISSING_IMAGE, client=wx.ART_TOOLBAR
  246. )
  247. button = BitmapButton(
  248. parent=self,
  249. id=wx.ID_ANY,
  250. size=((-1, self.GetToolSize()[1])),
  251. bitmap=bitmap,
  252. style=wx.NO_BORDER,
  253. )
  254. button.SetToolTip(tooltip)
  255. return button
  256. class ToolSwitcher:
  257. """Class handling switching tools in toolbar and custom toggle buttons."""
  258. def __init__(self):
  259. self._groups = defaultdict(lambda: defaultdict(list))
  260. self._toolsGroups = defaultdict(list)
  261. # emitted when tool is changed
  262. self.toggleToolChanged = Signal("ToolSwitcher.toggleToolChanged")
  263. def AddToolToGroup(self, group, toolbar, tool):
  264. """Adds tool from toolbar to group of exclusive tools.
  265. :param group: name of group (e.g. 'mouseUse')
  266. :param toolbar: instance of toolbar
  267. :param tool: id of a tool from the toolbar
  268. """
  269. self._groups[group][toolbar].append(tool)
  270. self._toolsGroups[tool].append(group)
  271. def AddCustomToolToGroup(self, group, btnId, toggleHandler):
  272. """Adds custom tool from to group of exclusive tools (some toggle button).
  273. :param group: name of group (e.g. 'mouseUse')
  274. :param btnId: id of a tool (typically button)
  275. :param toggleHandler: handler to be called to switch the button
  276. """
  277. self._groups[group]["custom"].append((btnId, toggleHandler))
  278. self._toolsGroups[btnId].append(group)
  279. def RemoveCustomToolFromGroup(self, tool):
  280. """Removes custom tool from group.
  281. :param tool: id of the button
  282. """
  283. if tool not in self._toolsGroups:
  284. return
  285. for group in self._toolsGroups[tool]:
  286. self._groups[group]["custom"] = [
  287. (bid, hdlr)
  288. for (bid, hdlr) in self._groups[group]["custom"]
  289. if bid != tool
  290. ]
  291. def RemoveToolbarFromGroup(self, group, toolbar):
  292. """Removes toolbar from group.
  293. Before toolbar is destroyed, it must be removed from group, too.
  294. Otherwise we can expect some DeadObject errors.
  295. :param group: name of group (e.g. 'mouseUse')
  296. :param toolbar: instance of toolbar
  297. """
  298. for tb in self._groups[group]:
  299. if tb == toolbar:
  300. del self._groups[group][tb]
  301. break
  302. def IsToolInGroup(self, tool, group):
  303. """Checks whether a tool is in a specified group.
  304. :param tool: tool id
  305. :param group: name of group (e.g. 'mouseUse')
  306. """
  307. for group in self._toolsGroups[tool]:
  308. for tb in self._groups[group]:
  309. if tb == "custom":
  310. for bid, handler in self._groups[group][tb]:
  311. if tool == bid:
  312. return True
  313. elif tb.FindById(tool):
  314. return True
  315. return False
  316. def ToolChanged(self, tool):
  317. """When any tool/button is pressed, other tools from group must be unchecked.
  318. :param tool: id of a tool/button
  319. """
  320. for group in self._toolsGroups[tool]:
  321. for tb in self._groups[group]:
  322. if tb == "custom":
  323. for btnId, handler in self._groups[group][tb]:
  324. if btnId != tool:
  325. handler(False)
  326. else:
  327. for tl in self._groups[group][tb]:
  328. if tb.FindById(tl): # check if still exists
  329. if tl != tool:
  330. tb.ToggleTool(tl, False)
  331. else:
  332. tb.ToggleTool(tool, True)
  333. self.toggleToolChanged.emit(id=tool)