decorations.py 11 KB

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