decorations.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. """
  2. @package mapwin.decorations
  3. @brief Map display decorations (overlays) - text, barscale and legend
  4. Classes:
  5. - decorations::OverlayController
  6. - decorations::BarscaleController
  7. - decorations::ArrowController
  8. - decorations::LegendController
  9. - decorations::TextLayerDialog
  10. (C) 2006-2014 by the GRASS Development Team
  11. This program is free software under the GNU General Public License
  12. (>=v2). Read the file COPYING that comes with GRASS for details.
  13. @author Anna Kratochvilova <kratochanna gmail.com>
  14. """
  15. import wx
  16. from grass.pydispatch.signal import Signal
  17. try:
  18. from PIL import Image # noqa: F401
  19. hasPIL = True
  20. except ImportError:
  21. hasPIL = False
  22. from gui_core.wrap import NewId
  23. class OverlayController(object):
  24. """Base class for decorations (barscale, legend) controller."""
  25. def __init__(self, renderer, giface):
  26. self._giface = giface
  27. self._renderer = renderer
  28. self._overlay = None
  29. self._coords = None
  30. self._pdcType = "image"
  31. self._propwin = None
  32. self._defaultAt = ""
  33. self._cmd = None # to be set by user
  34. self._name = None # to be defined by subclass
  35. self._removeLabel = None # to be defined by subclass
  36. self._activateLabel = None # to be defined by subclass
  37. self._id = NewId()
  38. self._dialog = None
  39. # signals that overlay or its visibility changed
  40. self.overlayChanged = Signal("OverlayController::overlayChanged")
  41. def SetCmd(self, cmd):
  42. hasAt = False
  43. for i in cmd:
  44. if i.startswith("at="):
  45. hasAt = True
  46. # reset coordinates, 'at' values will be used, see GetCoords
  47. self._coords = None
  48. break
  49. if not hasAt:
  50. cmd.append(self._defaultAt)
  51. self._cmd = cmd
  52. def GetCmd(self):
  53. return self._cmd
  54. cmd = property(fset=SetCmd, fget=GetCmd)
  55. def SetCoords(self, coords):
  56. self._coords = list(coords)
  57. def GetCoords(self):
  58. if self._coords is None: # initial position
  59. x, y = self.GetPlacement((self._renderer.width, self._renderer.height))
  60. self._coords = [x, y]
  61. return self._coords
  62. coords = property(fset=SetCoords, fget=GetCoords)
  63. def GetPdcType(self):
  64. return self._pdcType
  65. pdcType = property(fget=GetPdcType)
  66. def GetName(self):
  67. return self._name
  68. name = property(fget=GetName)
  69. def GetRemoveLabel(self):
  70. return self._removeLabel
  71. removeLabel = property(fget=GetRemoveLabel)
  72. def GetActivateLabel(self):
  73. return self._activateLabel
  74. activateLabel = property(fget=GetActivateLabel)
  75. def GetId(self):
  76. return self._id
  77. id = property(fget=GetId)
  78. def GetPropwin(self):
  79. return self._propwin
  80. def SetPropwin(self, win):
  81. self._propwin = win
  82. propwin = property(fget=GetPropwin, fset=SetPropwin)
  83. def GetLayer(self):
  84. return self._overlay
  85. layer = property(fget=GetLayer)
  86. def GetDialog(self):
  87. return self._dialog
  88. def SetDialog(self, win):
  89. self._dialog = win
  90. dialog = property(fget=GetDialog, fset=SetDialog)
  91. def IsShown(self):
  92. if self._overlay and self._overlay.IsActive() and self._overlay.IsRendered():
  93. return True
  94. return False
  95. def Show(self, show=True):
  96. """Activate or deactivate overlay."""
  97. if show:
  98. if not self._overlay:
  99. self._add()
  100. self._overlay.SetActive(True)
  101. self._update()
  102. else:
  103. self.Hide()
  104. self.overlayChanged.emit()
  105. def Hide(self):
  106. if self._overlay:
  107. self._overlay.SetActive(False)
  108. self.overlayChanged.emit()
  109. def Remove(self):
  110. if self._dialog:
  111. self._dialog.Destroy()
  112. self._renderer.DeleteOverlay(self._overlay)
  113. self.overlayChanged.emit()
  114. def _add(self):
  115. self._overlay = self._renderer.AddOverlay(
  116. id=self._id,
  117. ltype=self._name,
  118. command=self.cmd,
  119. active=False,
  120. render=True,
  121. hidden=True,
  122. )
  123. # check if successful
  124. def _update(self):
  125. self._renderer.ChangeOverlay(id=self._id, command=self._cmd)
  126. def CmdIsValid(self):
  127. """If command is valid"""
  128. return True
  129. def GetPlacement(self, screensize):
  130. """Get coordinates where to place overlay in a reasonable way
  131. :param screensize: screen size
  132. """
  133. if not hasPIL:
  134. self._giface.WriteWarning(
  135. _(
  136. "Please install Python Imaging Library (PIL)\n"
  137. "for better control of legend and other decorations."
  138. )
  139. )
  140. return 0, 0
  141. for param in self._cmd:
  142. if not param.startswith("at"):
  143. continue
  144. x, y = [float(number) for number in param.split("=")[1].split(",")]
  145. x = int((x / 100.0) * screensize[0])
  146. y = int((1 - y / 100.0) * screensize[1])
  147. return x, y
  148. class DtextController(OverlayController):
  149. def __init__(self, renderer, giface):
  150. OverlayController.__init__(self, renderer, giface)
  151. self._name = "text"
  152. self._removeLabel = _("Remove text")
  153. self._activateLabel = _("Text properties")
  154. self._defaultAt = "at=50,50"
  155. self._cmd = ["d.text", self._defaultAt]
  156. def CmdIsValid(self):
  157. inputs = 0
  158. for param in self._cmd[1:]:
  159. param = param.split("=")
  160. if len(param) == 1:
  161. inputs += 1
  162. else:
  163. if param[0] == "text" and len(param) == 2:
  164. inputs += 1
  165. if inputs >= 1:
  166. return True
  167. return False
  168. class BarscaleController(OverlayController):
  169. def __init__(self, renderer, giface):
  170. OverlayController.__init__(self, renderer, giface)
  171. self._name = "barscale"
  172. self._removeLabel = _("Remove scale bar")
  173. self._activateLabel = _("Scale bar properties")
  174. # different from default because the reference point is not in the
  175. # middle
  176. self._defaultAt = "at=0,98"
  177. self._cmd = ["d.barscale", self._defaultAt]
  178. class ArrowController(OverlayController):
  179. def __init__(self, renderer, giface):
  180. OverlayController.__init__(self, renderer, giface)
  181. self._name = "arrow"
  182. self._removeLabel = _("Remove north arrow")
  183. self._activateLabel = _("North arrow properties")
  184. # different from default because the reference point is not in the
  185. # middle
  186. self._defaultAt = "at=85.0,25.0"
  187. self._cmd = ["d.northarrow", self._defaultAt]
  188. class LegendVectController(OverlayController):
  189. def __init__(self, renderer, giface):
  190. OverlayController.__init__(self, renderer, giface)
  191. self._name = "vectleg"
  192. self._removeLabel = _("Remove legend")
  193. self._activateLabel = _("Vector legend properties")
  194. # different from default because the reference point is not in the
  195. # middle
  196. self._defaultAt = "at=20.0,80.0"
  197. self._cmd = ["d.legend.vect", self._defaultAt]
  198. class LegendController(OverlayController):
  199. def __init__(self, renderer, giface):
  200. OverlayController.__init__(self, renderer, giface)
  201. self._name = "legend"
  202. self._removeLabel = _("Remove legend")
  203. self._activateLabel = _("Raster legend properties")
  204. # default is in the center to avoid trimmed legend on the edge
  205. self._defaultAt = "at=5,50,47,50"
  206. self._actualAt = self._defaultAt
  207. self._cmd = ["d.legend", self._defaultAt]
  208. def SetCmd(self, cmd):
  209. """Overridden method
  210. Required for setting default or actual raster legend position.
  211. """
  212. hasAt = False
  213. for i in cmd:
  214. if i.startswith("at="):
  215. hasAt = True
  216. # reset coordinates, 'at' values will be used, see GetCoords
  217. self._coords = None
  218. break
  219. if not hasAt:
  220. if self._actualAt != self._defaultAt:
  221. cmd.append(self._actualAt)
  222. else:
  223. cmd.append(self._defaultAt)
  224. self._cmd = cmd
  225. cmd = property(fset=SetCmd, fget=OverlayController.GetCmd)
  226. def GetPlacement(self, screensize):
  227. if not hasPIL:
  228. self._giface.WriteWarning(
  229. _(
  230. "Please install Python Imaging Library (PIL)\n"
  231. "for better control of legend and other decorations."
  232. )
  233. )
  234. return 0, 0
  235. for param in self._cmd:
  236. if not param.startswith("at"):
  237. continue
  238. # if the at= is the default, we will move the legend from the center to bottom left
  239. if param == self._defaultAt:
  240. b, t, l, r = 5, 50, 7, 10
  241. else:
  242. b, t, l, r = [
  243. float(number) for number in param.split("=")[1].split(",")
  244. ] # pylint: disable-msg=W0612
  245. x = int((l / 100.0) * screensize[0])
  246. y = int((1 - t / 100.0) * screensize[1])
  247. return x, y
  248. def CmdIsValid(self):
  249. inputs = 0
  250. for param in self._cmd[1:]:
  251. param = param.split("=")
  252. if len(param) == 1:
  253. inputs += 1
  254. else:
  255. if param[0] == "raster" and len(param) == 2:
  256. inputs += 1
  257. elif param[0] == "raster_3d" and len(param) == 2:
  258. inputs += 1
  259. if inputs == 1:
  260. return True
  261. return False
  262. def ResizeLegend(self, begin, end, screenSize):
  263. """Resize legend according to given bbox coordinates."""
  264. w = abs(begin[0] - end[0])
  265. h = abs(begin[1] - end[1])
  266. if begin[0] < end[0]:
  267. x = begin[0]
  268. else:
  269. x = end[0]
  270. if begin[1] < end[1]:
  271. y = begin[1]
  272. else:
  273. y = end[1]
  274. at = [
  275. (screenSize[1] - (y + h)) / float(screenSize[1]) * 100,
  276. (screenSize[1] - y) / float(screenSize[1]) * 100,
  277. x / float(screenSize[0]) * 100,
  278. (x + w) / float(screenSize[0]) * 100,
  279. ]
  280. atStr = "at=%d,%d,%d,%d" % (at[0], at[1], at[2], at[3])
  281. for i, subcmd in enumerate(self._cmd):
  282. if subcmd.startswith("at="):
  283. self._cmd[i] = atStr
  284. break
  285. self._coords = None
  286. self._actualAt = atStr
  287. self.Show()
  288. def StartResizing(self):
  289. """Tool in toolbar or button itself were pressed"""
  290. # prepare for resizing
  291. window = self._giface.GetMapWindow()
  292. window.SetNamedCursor("cross")
  293. window.mouse["use"] = None
  294. window.mouse["box"] = "box"
  295. window.pen = wx.Pen(colour="Black", width=2, style=wx.SHORT_DASH)
  296. window.mouseLeftUp.connect(self._finishResizing)
  297. def _finishResizing(self):
  298. window = self._giface.GetMapWindow()
  299. window.mouseLeftUp.disconnect(self._finishResizing)
  300. screenSize = window.GetClientSize()
  301. self.ResizeLegend(window.mouse["begin"], window.mouse["end"], screenSize)
  302. self._giface.GetMapDisplay().GetMapToolbar().SelectDefault()
  303. # redraw
  304. self.overlayChanged.emit()