decorations.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  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-2013 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 core.utils import _
  17. from grass.pydispatch.signal import Signal
  18. class OverlayController(object):
  19. """!Base class for decorations (barscale, legend) controller."""
  20. def __init__(self, renderer, giface):
  21. self._giface = giface
  22. self._renderer = renderer
  23. self._overlay = None
  24. self._coords = None
  25. self._pdcType = 'image'
  26. self._propwin = None
  27. self._defaultAt = ''
  28. self._cmd = None # to be set by user
  29. self._name = None # to be defined by subclass
  30. self._id = None # to be defined by subclass
  31. self._dialog = None
  32. # signals that overlay or its visibility changed
  33. self.overlayChanged = Signal('OverlayController::overlayChanged')
  34. def SetCmd(self, cmd):
  35. hasAt = False
  36. for i in cmd:
  37. if i.startswith("at="):
  38. hasAt = True
  39. break
  40. if not hasAt:
  41. cmd.append(self._defaultAt)
  42. self._cmd = cmd
  43. def GetCmd(self):
  44. return self._cmd
  45. cmd = property(fset=SetCmd, fget=GetCmd)
  46. def SetCoords(self, coords):
  47. self._coords = list(coords)
  48. def GetCoords(self):
  49. if self._coords is None: # initial position
  50. x, y = self.GetPlacement((self._renderer.width, self._renderer.height))
  51. self._coords = [x, y]
  52. return self._coords
  53. coords = property(fset=SetCoords, fget=GetCoords)
  54. def GetPdcType(self):
  55. return self._pdcType
  56. pdcType = property(fget=GetPdcType)
  57. def GetName(self):
  58. return self._name
  59. name = property(fget=GetName)
  60. def GetId(self):
  61. return self._id
  62. id = property(fget=GetId)
  63. def GetPropwin(self):
  64. return self._propwin
  65. def SetPropwin(self, win):
  66. self._propwin = win
  67. propwin = property(fget=GetPropwin, fset=SetPropwin)
  68. def GetLayer(self):
  69. return self._overlay
  70. layer = property(fget=GetLayer)
  71. def GetDialog(self):
  72. return self._dialog
  73. def SetDialog(self, win):
  74. self._dialog = win
  75. dialog = property(fget=GetDialog, fset=SetDialog)
  76. def IsShown(self):
  77. if self._overlay and self._overlay.IsActive():
  78. return True
  79. return False
  80. def Show(self, show=True):
  81. """!Activate or deactivate overlay."""
  82. if show:
  83. if not self._overlay:
  84. self._add()
  85. self._overlay.SetActive(True)
  86. self._update()
  87. else:
  88. self.Hide()
  89. self.overlayChanged.emit()
  90. def Hide(self):
  91. if self._overlay:
  92. self._overlay.SetActive(False)
  93. self.overlayChanged.emit()
  94. def GetOptData(self, dcmd, layer, params, propwin):
  95. """!Called after options are set through module dialog.
  96. @param dcmd resulting command
  97. @param layer not used
  98. @param params module parameters (not used)
  99. @param propwin dialog window
  100. """
  101. if not dcmd:
  102. return
  103. self._cmd = dcmd
  104. self._dialog = propwin
  105. self.Show()
  106. def _add(self):
  107. self._overlay = self._renderer.AddOverlay(id=self._id, ltype=self._name,
  108. command=self.cmd, active=False,
  109. render=False, hidden=True)
  110. # check if successful
  111. def _update(self):
  112. self._renderer.ChangeOverlay(id=self._id, command=self._cmd,
  113. render=False)
  114. def CmdIsValid(self):
  115. """!If command is valid"""
  116. return True
  117. def GetPlacement(self, screensize):
  118. """!Get coordinates where to place overlay in a reasonable way
  119. @param screensize sreen size
  120. """
  121. for param in self._cmd:
  122. if not param.startswith('at'):
  123. continue
  124. x, y = [float(number) for number in param.split('=')[1].split(',')]
  125. x = int((x / 100.) * screensize[0])
  126. y = int((1 - y / 100.) * screensize[1])
  127. return x, y
  128. class BarscaleController(OverlayController):
  129. def __init__(self, renderer, giface):
  130. OverlayController.__init__(self, renderer, giface)
  131. self._id = 1
  132. self._name = 'barscale'
  133. # different from default because the reference point is not in the middle
  134. self._defaultAt = 'at=0,98'
  135. self._cmd = ['d.barscale', self._defaultAt]
  136. class ArrowController(OverlayController):
  137. def __init__(self, renderer, giface):
  138. OverlayController.__init__(self, renderer, giface)
  139. self._id = 2
  140. self._name = 'arrow'
  141. # different from default because the reference point is not in the middle
  142. self._defaultAt = 'at=85.0,25.0'
  143. self._cmd = ['d.northarrow', self._defaultAt]
  144. class LegendController(OverlayController):
  145. def __init__(self, renderer, giface):
  146. OverlayController.__init__(self, renderer, giface)
  147. self._id = 0
  148. self._name = 'legend'
  149. # TODO: synchronize with d.legend?
  150. self._defaultAt = 'at=5,50,2,5'
  151. self._cmd = ['d.legend', self._defaultAt]
  152. def GetPlacement(self, screensize):
  153. for param in self._cmd:
  154. if not param.startswith('at'):
  155. continue
  156. b, t, l, r = [float(number) for number in param.split('=')[1].split(',')] # pylint: disable-msg=W0612
  157. x = int((l / 100.) * screensize[0])
  158. y = int((1 - t / 100.) * screensize[1])
  159. return x, y
  160. def CmdIsValid(self):
  161. for param in self._cmd:
  162. param = param.split('=')
  163. if param[0] == 'map' and len(param) == 2:
  164. return True
  165. return False
  166. def ResizeLegend(self, begin, end, screenSize):
  167. """!Resize legend according to given bbox coordinates."""
  168. w = abs(begin[0] - end[0])
  169. h = abs(begin[1] - end[1])
  170. if begin[0] < end[0]:
  171. x = begin[0]
  172. else:
  173. x = end[0]
  174. if begin[1] < end[1]:
  175. y = begin[1]
  176. else:
  177. y = end[1]
  178. at = [(screenSize[1] - (y + h)) / float(screenSize[1]) * 100,
  179. (screenSize[1] - y) / float(screenSize[1]) * 100,
  180. x / float(screenSize[0]) * 100,
  181. (x + w) / float(screenSize[0]) * 100]
  182. atStr = "at=%d,%d,%d,%d" % (at[0], at[1], at[2], at[3])
  183. for i, subcmd in enumerate(self._cmd):
  184. if subcmd.startswith('at='):
  185. self._cmd[i] = atStr
  186. break
  187. self._coords = None
  188. self.Show()
  189. def StartResizing(self):
  190. """!Tool in toolbar or button itself were pressed"""
  191. # prepare for resizing
  192. window = self._giface.GetMapWindow()
  193. window.SetNamedCursor('cross')
  194. window.mouse['use'] = None
  195. window.mouse['box'] = 'box'
  196. window.pen = wx.Pen(colour='Black', width=2, style=wx.SHORT_DASH)
  197. window.mouseLeftUp.connect(self._finishResizing)
  198. def _finishResizing(self):
  199. window = self._giface.GetMapWindow()
  200. window.mouseLeftUp.disconnect(self._finishResizing)
  201. screenSize = window.GetClientSizeTuple()
  202. self.ResizeLegend(window.mouse["begin"], window.mouse["end"], screenSize)
  203. self._giface.GetMapDisplay().GetMapToolbar().SelectDefault()
  204. # redraw
  205. self.overlayChanged.emit()
  206. class TextLayerDialog(wx.Dialog):
  207. """
  208. Controls setting options and displaying/hiding map overlay decorations
  209. """
  210. def __init__(self, parent, ovlId, title, name='text',
  211. pos=wx.DefaultPosition, size=wx.DefaultSize,
  212. style=wx.DEFAULT_DIALOG_STYLE):
  213. wx.Dialog.__init__(self, parent, wx.ID_ANY, title, pos, size, style)
  214. from wx.lib.expando import ExpandoTextCtrl, EVT_ETC_LAYOUT_NEEDED
  215. self.ovlId = ovlId
  216. self.parent = parent
  217. if self.ovlId in self.parent.MapWindow.textdict.keys():
  218. self.currText = self.parent.MapWindow.textdict[self.ovlId]['text']
  219. self.currFont = self.parent.MapWindow.textdict[self.ovlId]['font']
  220. self.currClr = self.parent.MapWindow.textdict[self.ovlId]['color']
  221. self.currRot = self.parent.MapWindow.textdict[self.ovlId]['rotation']
  222. self.currCoords = self.parent.MapWindow.textdict[self.ovlId]['coords']
  223. self.currBB = self.parent.MapWindow.textdict[self.ovlId]['bbox']
  224. else:
  225. self.currClr = wx.BLACK
  226. self.currText = ''
  227. self.currFont = self.GetFont()
  228. self.currRot = 0.0
  229. self.currCoords = [10, 10]
  230. self.currBB = wx.Rect()
  231. self.sizer = wx.BoxSizer(wx.VERTICAL)
  232. box = wx.GridBagSizer(vgap=5, hgap=5)
  233. # show/hide
  234. self.chkbox = wx.CheckBox(parent=self, id=wx.ID_ANY,
  235. label=_('Show text object'))
  236. if self.parent.Map.GetOverlay(self.ovlId) is None:
  237. self.chkbox.SetValue(True)
  238. else:
  239. self.chkbox.SetValue(self.parent.MapWindow.overlays[self.ovlId]['layer'].IsActive())
  240. box.Add(item=self.chkbox, span=(1, 2),
  241. flag=wx.ALIGN_LEFT | wx.ALL, border=5,
  242. pos=(0, 0))
  243. # text entry
  244. label = wx.StaticText(parent=self, id=wx.ID_ANY, label=_("Enter text:"))
  245. box.Add(item=label,
  246. flag=wx.ALIGN_CENTER_VERTICAL,
  247. pos=(1, 0))
  248. self.textentry = ExpandoTextCtrl(
  249. parent=self, id=wx.ID_ANY, value="", size=(300, -1))
  250. self.textentry.SetFont(self.currFont)
  251. self.textentry.SetForegroundColour(self.currClr)
  252. self.textentry.SetValue(self.currText)
  253. # get rid of unneeded scrollbar when text box first opened
  254. self.textentry.SetClientSize((300, -1))
  255. box.Add(item=self.textentry,
  256. pos=(1, 1))
  257. # rotation
  258. label = wx.StaticText(parent=self, id=wx.ID_ANY, label=_("Rotation:"))
  259. box.Add(item=label,
  260. flag=wx.ALIGN_CENTER_VERTICAL,
  261. pos=(2, 0))
  262. self.rotation = wx.SpinCtrl(parent=self, id=wx.ID_ANY, value="", pos=(30, 50),
  263. size=(75, -1), style=wx.SP_ARROW_KEYS)
  264. self.rotation.SetRange(-360, 360)
  265. self.rotation.SetValue(int(self.currRot))
  266. box.Add(item=self.rotation,
  267. flag=wx.ALIGN_RIGHT,
  268. pos=(2, 1))
  269. # font
  270. fontbtn = wx.Button(parent=self, id=wx.ID_ANY, label=_("Set font"))
  271. box.Add(item=fontbtn,
  272. flag=wx.ALIGN_RIGHT,
  273. pos=(3, 1))
  274. self.sizer.Add(item=box, proportion=1,
  275. flag=wx.ALL, border=10)
  276. # note
  277. box = wx.BoxSizer(wx.HORIZONTAL)
  278. label = wx.StaticText(parent=self, id=wx.ID_ANY,
  279. label=_("Drag text with mouse in pointer mode "
  280. "to position.\nDouble-click to change options"))
  281. box.Add(item=label, proportion=0,
  282. flag=wx.ALIGN_CENTRE | wx.ALL, border=5)
  283. self.sizer.Add(item=box, proportion=0,
  284. flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER | wx.ALL, border=5)
  285. line = wx.StaticLine(parent=self, id=wx.ID_ANY,
  286. size=(20, -1), style=wx.LI_HORIZONTAL)
  287. self.sizer.Add(item=line, proportion=0,
  288. flag=wx.EXPAND | wx.ALIGN_CENTRE | wx.ALL, border=5)
  289. btnsizer = wx.StdDialogButtonSizer()
  290. btn = wx.Button(parent=self, id=wx.ID_OK)
  291. btn.SetDefault()
  292. btnsizer.AddButton(btn)
  293. btn = wx.Button(parent=self, id=wx.ID_CANCEL)
  294. btnsizer.AddButton(btn)
  295. btnsizer.Realize()
  296. self.sizer.Add(item=btnsizer, proportion=0,
  297. flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)
  298. self.SetSizer(self.sizer)
  299. self.sizer.Fit(self)
  300. # bindings
  301. self.Bind(EVT_ETC_LAYOUT_NEEDED, self.OnRefit, self.textentry)
  302. self.Bind(wx.EVT_BUTTON, self.OnSelectFont, fontbtn)
  303. self.Bind(wx.EVT_TEXT, self.OnText, self.textentry)
  304. self.Bind(wx.EVT_SPINCTRL, self.OnRotation, self.rotation)
  305. def OnRefit(self, event):
  306. """!Resize text entry to match text"""
  307. self.sizer.Fit(self)
  308. def OnText(self, event):
  309. """!Change text string"""
  310. self.currText = event.GetString()
  311. def OnRotation(self, event):
  312. """!Change rotation"""
  313. self.currRot = event.GetInt()
  314. event.Skip()
  315. def OnSelectFont(self, event):
  316. """!Change font"""
  317. data = wx.FontData()
  318. data.EnableEffects(True)
  319. data.SetColour(self.currClr) # set colour
  320. data.SetInitialFont(self.currFont)
  321. dlg = wx.FontDialog(self, data)
  322. if dlg.ShowModal() == wx.ID_OK:
  323. data = dlg.GetFontData()
  324. self.currFont = data.GetChosenFont()
  325. self.currClr = data.GetColour()
  326. self.textentry.SetFont(self.currFont)
  327. self.textentry.SetForegroundColour(self.currClr)
  328. self.Layout()
  329. dlg.Destroy()
  330. def GetValues(self):
  331. """!Get text properties"""
  332. return {'text': self.currText,
  333. 'font': self.currFont,
  334. 'color': self.currClr,
  335. 'rotation': self.currRot,
  336. 'coords': self.currCoords,
  337. 'active': self.chkbox.IsChecked()}