decorations.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  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::LegendController
  8. - decorations::DecorationDialog
  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 GetLayerNameFromCmd, _
  17. from gui_core.forms import GUI
  18. class OverlayController(object):
  19. """!Base class for decorations (barscale, legend) controller."""
  20. def __init__(self, renderer):
  21. self._renderer = renderer
  22. self._overlay = None
  23. self._coords = [0, 0]
  24. self._pdcType = 'image'
  25. self._propwin = None
  26. self._defaultAt = ''
  27. self._cmd = None # to be set by user
  28. self._name = None # to be defined by subclass
  29. self._id = None # to be defined by subclass
  30. def SetCmd(self, cmd):
  31. hasAt = False
  32. for i in cmd:
  33. if i.startswith("at="):
  34. hasAt = True
  35. break
  36. if not hasAt:
  37. cmd.append(self._defaultAt)
  38. self._cmd = cmd
  39. def GetCmd(self):
  40. return self._cmd
  41. cmd = property(fset=SetCmd, fget=GetCmd)
  42. def SetCoords(self, coords):
  43. self._coords = list(coords)
  44. def GetCoords(self):
  45. return self._coords
  46. coords = property(fset=SetCoords, fget=GetCoords)
  47. def GetPdcType(self):
  48. return self._pdcType
  49. pdcType = property(fget=GetPdcType)
  50. def GetName(self):
  51. return self._name
  52. name = property(fget=GetName)
  53. def GetId(self):
  54. return self._id
  55. id = property(fget=GetId)
  56. def GetPropwin(self):
  57. return self._propwin
  58. def SetPropwin(self, win):
  59. self._propwin = win
  60. propwin = property(fget=GetPropwin, fset=SetPropwin)
  61. def GetLayer(self):
  62. return self._overlay
  63. layer = property(fget=GetLayer)
  64. def IsShown(self):
  65. if self._overlay and self._overlay.IsActive():
  66. return True
  67. return False
  68. def Show(self, show=True):
  69. """!Activate or deactivate overlay."""
  70. if show:
  71. if not self._overlay:
  72. self._add()
  73. self._overlay.SetActive(True)
  74. self._update()
  75. else:
  76. self.Hide()
  77. def Hide(self):
  78. if self._overlay:
  79. self._overlay.SetActive(False)
  80. def _add(self):
  81. self._overlay = self._renderer.AddOverlay(id=self._id, ltype=self._name,
  82. command=self.cmd, active=False,
  83. render=False, hidden=True)
  84. # check if successful
  85. def _update(self):
  86. self._renderer.ChangeOverlay(id=self._id, command=self._cmd,
  87. render=False)
  88. class BarscaleController(OverlayController):
  89. def __init__(self, renderer):
  90. OverlayController.__init__(self, renderer)
  91. self._id = 0
  92. self._name = 'barscale'
  93. self._defaultAt = 'at=0,95'
  94. self._cmd = ['d.barscale', self._defaultAt]
  95. class LegendController(OverlayController):
  96. def __init__(self, renderer):
  97. OverlayController.__init__(self, renderer)
  98. self._id = 1
  99. self._name = 'legend'
  100. # TODO: synchronize with d.legend?
  101. self._defaultAt = 'at=5,50,2,5'
  102. self._cmd = ['d.legend', self._defaultAt]
  103. def ResizeLegend(self, begin, end, screenSize):
  104. """!Resize legend according to given bbox coordinates."""
  105. w = abs(begin[0] - end[0])
  106. h = abs(begin[1] - end[1])
  107. if begin[0] < end[0]:
  108. x = begin[0]
  109. else:
  110. x = end[0]
  111. if begin[1] < end[1]:
  112. y = begin[1]
  113. else:
  114. y = end[1]
  115. at = [(screenSize[1] - (y + h)) / float(screenSize[1]) * 100,
  116. (screenSize[1] - y) / float(screenSize[1]) * 100,
  117. x / float(screenSize[0]) * 100,
  118. (x + w) / float(screenSize[0]) * 100]
  119. atStr = "at=%d,%d,%d,%d" % (at[0], at[1], at[2], at[3])
  120. for i, subcmd in enumerate(self._cmd):
  121. if subcmd.startswith('at='):
  122. self._cmd[i] = atStr
  123. break
  124. self._coords = [0, 0]
  125. self.Show()
  126. DECOR_DIALOG_LEGEND = 0
  127. DECOR_DIALOG_BARSCALE = 1
  128. class DecorationDialog(wx.Dialog):
  129. """!Controls setting options and displaying/hiding map overlay
  130. decorations
  131. """
  132. def __init__(self, parent, title, giface, overlayController,
  133. ddstyle, **kwargs):
  134. wx.Dialog.__init__(self, parent, wx.ID_ANY, title, **kwargs)
  135. self.parent = parent # MapFrame
  136. self._overlay = overlayController
  137. self._ddstyle = ddstyle
  138. self._giface = giface
  139. self._oldMouseUse = None
  140. self._oldCursor = None
  141. sizer = wx.BoxSizer(wx.VERTICAL)
  142. box = wx.BoxSizer(wx.HORIZONTAL)
  143. self.chkbox = wx.CheckBox(parent=self, id=wx.ID_ANY)
  144. self.chkbox.SetValue(True)
  145. if self._ddstyle == DECOR_DIALOG_LEGEND:
  146. self.chkbox.SetLabel("Show legend")
  147. else:
  148. self.chkbox.SetLabel("Show scale and North arrow")
  149. box.Add(item=self.chkbox, proportion=0,
  150. flag=wx.ALIGN_CENTRE | wx.ALL, border=5)
  151. sizer.Add(item=box, proportion=0,
  152. flag=wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
  153. box = wx.BoxSizer(wx.HORIZONTAL)
  154. optnbtn = wx.Button(parent=self, id=wx.ID_ANY, label=_("Set options"))
  155. box.Add(item=optnbtn, proportion=0, flag=wx.ALIGN_CENTRE | wx.ALL, border=5)
  156. sizer.Add(item=box, proportion=0,
  157. flag=wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
  158. if self._ddstyle == DECOR_DIALOG_LEGEND:
  159. box = wx.BoxSizer(wx.HORIZONTAL)
  160. self.resizeBtn = wx.ToggleButton(
  161. parent=self, id=wx.ID_ANY, label=_("Set size and position"))
  162. self.resizeBtn.SetToolTipString(_("Click and drag on the map display to set legend "
  163. "size and position and then press OK"))
  164. self.resizeBtn.Disable()
  165. self.resizeBtn.Bind(wx.EVT_TOGGLEBUTTON, self.OnResize)
  166. box.Add(item=self.resizeBtn, proportion=0,
  167. flag=wx.ALIGN_CENTRE | wx.ALL, border=5)
  168. sizer.Add(item=box, proportion=0,
  169. flag=wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
  170. box = wx.BoxSizer(wx.HORIZONTAL)
  171. if self._ddstyle == DECOR_DIALOG_LEGEND:
  172. labelText = _("Drag legend object with mouse in pointer mode to position.\n"
  173. "Double-click to change options.\n"
  174. "Define raster map name for legend in properties dialog.")
  175. else:
  176. labelText = _("Drag scale object with mouse in pointer mode to position.\n"
  177. "Double-click to change options.")
  178. label = wx.StaticText(parent=self, id=wx.ID_ANY,
  179. label=labelText)
  180. box.Add(item=label, proportion=0,
  181. flag=wx.ALIGN_CENTRE | wx.ALL, border=5)
  182. sizer.Add(item=box, proportion=0,
  183. flag=wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
  184. line = wx.StaticLine(
  185. parent=self, id=wx.ID_ANY, size=(20, -1), style = wx.LI_HORIZONTAL)
  186. sizer.Add(item=line, proportion=0,
  187. flag=wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
  188. # buttons
  189. btnsizer = wx.StdDialogButtonSizer()
  190. self.btnOK = wx.Button(parent=self, id=wx.ID_OK)
  191. self.btnOK.SetDefault()
  192. self.btnOK.Enable(self._ddstyle != DECOR_DIALOG_LEGEND)
  193. btnsizer.AddButton(self.btnOK)
  194. btnCancel = wx.Button(parent=self, id=wx.ID_CANCEL)
  195. btnsizer.AddButton(btnCancel)
  196. btnsizer.Realize()
  197. sizer.Add(item=btnsizer, proportion=0,
  198. flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
  199. #
  200. # bindings
  201. #
  202. optnbtn.Bind(wx.EVT_BUTTON, self.OnOptions)
  203. btnCancel.Bind(wx.EVT_BUTTON, lambda evt: self.CloseDialog())
  204. self.btnOK.Bind(wx.EVT_BUTTON, self.OnOK)
  205. self.SetSizer(sizer)
  206. sizer.Fit(self)
  207. mapName, found = GetLayerNameFromCmd(self._overlay.cmd)
  208. if found:
  209. # enable 'OK' and 'Resize' button
  210. self.btnOK.Enable()
  211. # set title
  212. self.SetTitle(_('Legend of raster map <%s>') %
  213. mapName)
  214. def OnOptions(self, event):
  215. """!Sets option for decoration map overlays
  216. """
  217. if self._overlay.propwin is None:
  218. # build properties dialog
  219. GUI(parent=self.parent).ParseCommand(cmd=self._overlay.cmd,
  220. completed=(self.GetOptData, self._overlay.name, ''))
  221. else:
  222. if self._overlay.propwin.IsShown():
  223. self._overlay.propwin.SetFocus()
  224. else:
  225. self._overlay.propwin.Show()
  226. def OnResize(self, event):
  227. window = self._giface.GetMapWindow()
  228. if event.GetInt():
  229. self._oldMouseUse = window.mouse['use']
  230. self._oldCursor = window.GetNamedCursor()
  231. window.SetNamedCursor('cross')
  232. window.mouse['use'] = None
  233. window.mouse['box'] = 'box'
  234. window.pen = wx.Pen(colour='Black', width=2, style=wx.SHORT_DASH)
  235. window.mouseLeftUp.connect(self._resizeLegend)
  236. else:
  237. self.Restore()
  238. self.DisconnectResizing()
  239. def Restore(self):
  240. """!Restore conditions before resizing"""
  241. window = self._giface.GetMapWindow()
  242. if self._oldCursor:
  243. window.SetNamedCursor(self._oldCursor)
  244. if self._oldMouseUse:
  245. window.mouse['use'] = self._oldMouseUse
  246. def DisconnectResizing(self):
  247. self._giface.GetMapWindow().mouseLeftUp.disconnect(self._resizeLegend)
  248. def _resizeLegend(self, x, y):
  249. """!Update legend after drawing new legend size (moved from BufferedWindow)"""
  250. self.resizeBtn.SetValue(False)
  251. window = self._giface.GetMapWindow()
  252. self.DisconnectResizing()
  253. self.Restore()
  254. # resize legend
  255. screenSize = window.GetClientSizeTuple()
  256. self._overlay.ResizeLegend(window.mouse["begin"], window.mouse["end"], screenSize)
  257. # redraw
  258. self._giface.updateMap.emit()
  259. def CloseDialog(self):
  260. """!Hide dialog"""
  261. if self._ddstyle == DECOR_DIALOG_LEGEND and self.resizeBtn.GetValue():
  262. self.Restore()
  263. self.resizeBtn.SetValue(False)
  264. self.DisconnectResizing()
  265. self.Hide()
  266. def OnOK(self, event):
  267. """!Button 'OK' pressed"""
  268. # enable or disable overlay
  269. self._overlay.Show(self.chkbox.IsChecked())
  270. # update map
  271. if self.parent.IsPaneShown('3d'):
  272. self.parent.MapWindow.UpdateOverlays()
  273. self._giface.updateMap.emit()
  274. # hide dialog
  275. self.CloseDialog()
  276. def GetOptData(self, dcmd, layer, params, propwin):
  277. """!Process decoration layer data"""
  278. if dcmd:
  279. self._overlay.cmd = dcmd
  280. self._overlay.propwin = propwin
  281. if params:
  282. self.btnOK.Enable()
  283. if self._ddstyle == DECOR_DIALOG_LEGEND and not self.parent.IsPaneShown('3d'):
  284. self.resizeBtn.Enable()
  285. def Show(self, show=True):
  286. if show:
  287. self.resizeBtn.Enable(not self.parent.IsPaneShown('3d'))
  288. wx.Dialog.Show(self, show)
  289. class TextLayerDialog(wx.Dialog):
  290. """
  291. Controls setting options and displaying/hiding map overlay decorations
  292. """
  293. def __init__(self, parent, ovlId, title, name='text',
  294. pos=wx.DefaultPosition, size=wx.DefaultSize,
  295. style=wx.DEFAULT_DIALOG_STYLE):
  296. wx.Dialog.__init__(self, parent, wx.ID_ANY, title, pos, size, style)
  297. from wx.lib.expando import ExpandoTextCtrl, EVT_ETC_LAYOUT_NEEDED
  298. self.ovlId = ovlId
  299. self.parent = parent
  300. if self.ovlId in self.parent.MapWindow.textdict.keys():
  301. self.currText = self.parent.MapWindow.textdict[self.ovlId]['text']
  302. self.currFont = self.parent.MapWindow.textdict[self.ovlId]['font']
  303. self.currClr = self.parent.MapWindow.textdict[self.ovlId]['color']
  304. self.currRot = self.parent.MapWindow.textdict[self.ovlId]['rotation']
  305. self.currCoords = self.parent.MapWindow.textdict[self.ovlId]['coords']
  306. self.currBB = self.parent.MapWindow.textdict[self.ovlId]['bbox']
  307. else:
  308. self.currClr = wx.BLACK
  309. self.currText = ''
  310. self.currFont = self.GetFont()
  311. self.currRot = 0.0
  312. self.currCoords = [10, 10]
  313. self.currBB = wx.Rect()
  314. self.sizer = wx.BoxSizer(wx.VERTICAL)
  315. box = wx.GridBagSizer(vgap=5, hgap=5)
  316. # show/hide
  317. self.chkbox = wx.CheckBox(parent=self, id=wx.ID_ANY,
  318. label=_('Show text object'))
  319. if self.parent.Map.GetOverlay(self.ovlId) is None:
  320. self.chkbox.SetValue(True)
  321. else:
  322. self.chkbox.SetValue(self.parent.MapWindow.overlays[self.ovlId]['layer'].IsActive())
  323. box.Add(item=self.chkbox, span=(1, 2),
  324. flag=wx.ALIGN_LEFT | wx.ALL, border=5,
  325. pos=(0, 0))
  326. # text entry
  327. label = wx.StaticText(parent=self, id=wx.ID_ANY, label=_("Enter text:"))
  328. box.Add(item=label,
  329. flag=wx.ALIGN_CENTER_VERTICAL,
  330. pos=(1, 0))
  331. self.textentry = ExpandoTextCtrl(
  332. parent=self, id=wx.ID_ANY, value="", size=(300, -1))
  333. self.textentry.SetFont(self.currFont)
  334. self.textentry.SetForegroundColour(self.currClr)
  335. self.textentry.SetValue(self.currText)
  336. # get rid of unneeded scrollbar when text box first opened
  337. self.textentry.SetClientSize((300, -1))
  338. box.Add(item=self.textentry,
  339. pos=(1, 1))
  340. # rotation
  341. label = wx.StaticText(parent=self, id=wx.ID_ANY, label=_("Rotation:"))
  342. box.Add(item=label,
  343. flag=wx.ALIGN_CENTER_VERTICAL,
  344. pos=(2, 0))
  345. self.rotation = wx.SpinCtrl(parent=self, id=wx.ID_ANY, value="", pos=(30, 50),
  346. size = (75, -1), style = wx.SP_ARROW_KEYS)
  347. self.rotation.SetRange(-360, 360)
  348. self.rotation.SetValue(int(self.currRot))
  349. box.Add(item=self.rotation,
  350. flag=wx.ALIGN_RIGHT,
  351. pos=(2, 1))
  352. # font
  353. fontbtn = wx.Button(parent=self, id=wx.ID_ANY, label=_("Set font"))
  354. box.Add(item=fontbtn,
  355. flag=wx.ALIGN_RIGHT,
  356. pos=(3, 1))
  357. self.sizer.Add(item=box, proportion=1,
  358. flag=wx.ALL, border=10)
  359. # note
  360. box = wx.BoxSizer(wx.HORIZONTAL)
  361. label = wx.StaticText(parent=self, id=wx.ID_ANY,
  362. label=_("Drag text with mouse in pointer mode "
  363. "to position.\nDouble-click to change options"))
  364. box.Add(item=label, proportion=0,
  365. flag=wx.ALIGN_CENTRE | wx.ALL, border=5)
  366. self.sizer.Add(item=box, proportion=0,
  367. flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER | wx.ALL, border=5)
  368. line = wx.StaticLine(parent=self, id=wx.ID_ANY,
  369. size=(20, -1), style = wx.LI_HORIZONTAL)
  370. self.sizer.Add(item=line, proportion=0,
  371. flag=wx.EXPAND | wx.ALIGN_CENTRE | wx.ALL, border=5)
  372. btnsizer = wx.StdDialogButtonSizer()
  373. btn = wx.Button(parent=self, id=wx.ID_OK)
  374. btn.SetDefault()
  375. btnsizer.AddButton(btn)
  376. btn = wx.Button(parent=self, id=wx.ID_CANCEL)
  377. btnsizer.AddButton(btn)
  378. btnsizer.Realize()
  379. self.sizer.Add(item=btnsizer, proportion=0,
  380. flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)
  381. self.SetSizer(self.sizer)
  382. self.sizer.Fit(self)
  383. # bindings
  384. self.Bind(EVT_ETC_LAYOUT_NEEDED, self.OnRefit, self.textentry)
  385. self.Bind(wx.EVT_BUTTON, self.OnSelectFont, fontbtn)
  386. self.Bind(wx.EVT_TEXT, self.OnText, self.textentry)
  387. self.Bind(wx.EVT_SPINCTRL, self.OnRotation, self.rotation)
  388. def OnRefit(self, event):
  389. """!Resize text entry to match text"""
  390. self.sizer.Fit(self)
  391. def OnText(self, event):
  392. """!Change text string"""
  393. self.currText = event.GetString()
  394. def OnRotation(self, event):
  395. """!Change rotation"""
  396. self.currRot = event.GetInt()
  397. event.Skip()
  398. def OnSelectFont(self, event):
  399. """!Change font"""
  400. data = wx.FontData()
  401. data.EnableEffects(True)
  402. data.SetColour(self.currClr) # set colour
  403. data.SetInitialFont(self.currFont)
  404. dlg = wx.FontDialog(self, data)
  405. if dlg.ShowModal() == wx.ID_OK:
  406. data = dlg.GetFontData()
  407. self.currFont = data.GetChosenFont()
  408. self.currClr = data.GetColour()
  409. self.textentry.SetFont(self.currFont)
  410. self.textentry.SetForegroundColour(self.currClr)
  411. self.Layout()
  412. dlg.Destroy()
  413. def GetValues(self):
  414. """!Get text properties"""
  415. return {'text': self.currText,
  416. 'font': self.currFont,
  417. 'color': self.currClr,
  418. 'rotation': self.currRot,
  419. 'coords': self.currCoords,
  420. 'active': self.chkbox.IsChecked()}