decorations.py 18 KB

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