2
@package mapwin.decorations
4
@brief Map display decorations (overlays) - text, barscale and legend
7
- decorations::OverlayController
8
- decorations::BarscaleController
9
- decorations::ArrowController
10
- decorations::LegendController
11
- decorations::TextLayerDialog
13
(C) 2006-2014 by the GRASS Development Team
15
This program is free software under the GNU General Public License
16
(>=v2). Read the file COPYING that comes with GRASS for details.
18
@author Anna Kratochvilova <kratochanna gmail.com>
22
from core.utils import _
25
from wx.lib.expando import ExpandoTextCtrl, EVT_ETC_LAYOUT_NEEDED
27
from grass.pydispatch.signal import Signal
35
class OverlayController(object):
37
"""Base class for decorations (barscale, legend) controller."""
39
def __init__(self, renderer, giface):
41
self._renderer = renderer
44
self._pdcType = 'image'
47
self._cmd = None # to be set by user
48
self._name = None # to be defined by subclass
49
self._id = None # to be defined by subclass
52
# signals that overlay or its visibility changed
53
self.overlayChanged = Signal('OverlayController::overlayChanged')
55
def SetCmd(self, cmd):
58
if i.startswith("at="):
60
# reset coordinates, 'at' values will be used, see GetCoords
64
cmd.append(self._defaultAt)
70
cmd = property(fset=SetCmd, fget=GetCmd)
72
def SetCoords(self, coords):
73
self._coords = list(coords)
76
if self._coords is None: # initial position
77
x, y = self.GetPlacement((self._renderer.width, self._renderer.height))
81
coords = property(fset=SetCoords, fget=GetCoords)
86
pdcType = property(fget=GetPdcType)
91
name = property(fget=GetName)
96
id = property(fget=GetId)
101
def SetPropwin(self, win):
104
propwin = property(fget=GetPropwin, fset=SetPropwin)
109
layer = property(fget=GetLayer)
114
def SetDialog(self, win):
117
dialog = property(fget=GetDialog, fset=SetDialog)
120
if self._overlay and self._overlay.IsActive() and self._overlay.IsRendered():
124
def Show(self, show=True):
125
"""Activate or deactivate overlay."""
127
if not self._overlay:
129
self._overlay.SetActive(True)
134
self.overlayChanged.emit()
138
self._overlay.SetActive(False)
139
self.overlayChanged.emit()
141
def GetOptData(self, dcmd, layer, params, propwin):
142
"""Called after options are set through module dialog.
144
:param dcmd: resulting command
145
:param layer: not used
146
:param params: module parameters (not used)
147
:param propwin: dialog window
153
self._dialog = propwin
158
self._overlay = self._renderer.AddOverlay(id=self._id, ltype=self._name,
159
command=self.cmd, active=False,
160
render=False, hidden=True)
161
# check if successful
164
self._renderer.ChangeOverlay(id=self._id, command=self._cmd,
167
def CmdIsValid(self):
168
"""If command is valid"""
171
def GetPlacement(self, screensize):
172
"""Get coordinates where to place overlay in a reasonable way
174
:param screensize: screen size
177
self._giface.WriteWarning(_("Please install Python Imaging Library (PIL)\n"
178
"for better control of legend and other decorations."))
180
for param in self._cmd:
181
if not param.startswith('at'):
183
x, y = [float(number) for number in param.split('=')[1].split(',')]
184
x = int((x / 100.) * screensize[0])
185
y = int((1 - y / 100.) * screensize[1])
190
class BarscaleController(OverlayController):
192
def __init__(self, renderer, giface):
193
OverlayController.__init__(self, renderer, giface)
195
self._name = 'barscale'
196
# different from default because the reference point is not in the middle
197
self._defaultAt = 'at=0,98'
198
self._cmd = ['d.barscale', self._defaultAt]
201
class ArrowController(OverlayController):
203
def __init__(self, renderer, giface):
204
OverlayController.__init__(self, renderer, giface)
207
# different from default because the reference point is not in the middle
208
self._defaultAt = 'at=85.0,25.0'
209
self._cmd = ['d.northarrow', self._defaultAt]
212
class LegendController(OverlayController):
214
def __init__(self, renderer, giface):
215
OverlayController.__init__(self, renderer, giface)
217
self._name = 'legend'
218
# TODO: synchronize with d.legend?
219
self._defaultAt = 'at=5,50,7,10'
220
self._cmd = ['d.legend', self._defaultAt]
222
def GetPlacement(self, screensize):
224
self._giface.WriteWarning(_("Please install Python Imaging Library (PIL)\n"
225
"for better control of legend and other decorations."))
227
for param in self._cmd:
228
if not param.startswith('at'):
230
b, t, l, r = [float(number) for number in param.split('=')[1].split(',')] # pylint: disable-msg=W0612
231
x = int((l / 100.) * screensize[0])
232
y = int((1 - t / 100.) * screensize[1])
236
def CmdIsValid(self):
238
for param in self._cmd[1:]:
239
param = param.split('=')
243
if param[0] == 'raster' and len(param) == 2:
245
elif param[0] == 'raster_3d' and len(param) == 2:
251
def ResizeLegend(self, begin, end, screenSize):
252
"""Resize legend according to given bbox coordinates."""
253
w = abs(begin[0] - end[0])
254
h = abs(begin[1] - end[1])
255
if begin[0] < end[0]:
259
if begin[1] < end[1]:
264
at = [(screenSize[1] - (y + h)) / float(screenSize[1]) * 100,
265
(screenSize[1] - y) / float(screenSize[1]) * 100,
266
x / float(screenSize[0]) * 100,
267
(x + w) / float(screenSize[0]) * 100]
268
atStr = "at=%d,%d,%d,%d" % (at[0], at[1], at[2], at[3])
270
for i, subcmd in enumerate(self._cmd):
271
if subcmd.startswith('at='):
278
def StartResizing(self):
279
"""Tool in toolbar or button itself were pressed"""
280
# prepare for resizing
281
window = self._giface.GetMapWindow()
282
window.SetNamedCursor('cross')
283
window.mouse['use'] = None
284
window.mouse['box'] = 'box'
285
window.pen = wx.Pen(colour='Black', width=2, style=wx.SHORT_DASH)
286
window.mouseLeftUp.connect(self._finishResizing)
288
def _finishResizing(self):
289
window = self._giface.GetMapWindow()
290
window.mouseLeftUp.disconnect(self._finishResizing)
291
screenSize = window.GetClientSizeTuple()
292
self.ResizeLegend(window.mouse["begin"], window.mouse["end"], screenSize)
293
self._giface.GetMapDisplay().GetMapToolbar().SelectDefault()
295
self.overlayChanged.emit()
298
class TextLayerDialog(wx.Dialog):
299
"""!Controls setting options and displaying/hiding map overlay decorations
301
def __init__(self, parent, ovlId, title, name='text', size=wx.DefaultSize,
302
style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER):
304
wx.Dialog.__init__(self, parent=parent, id=wx.ID_ANY, title=title, style=style, size=size)
309
if self.ovlId in self.parent.MapWindow.textdict.keys():
310
self.currText = self.parent.MapWindow.textdict[self.ovlId]['text']
311
self.currFont = self.parent.MapWindow.textdict[self.ovlId]['font']
312
self.currClr = self.parent.MapWindow.textdict[self.ovlId]['color']
313
self.currRot = self.parent.MapWindow.textdict[self.ovlId]['rotation']
314
self.currCoords = self.parent.MapWindow.textdict[self.ovlId]['coords']
315
self.currBB = self.parent.MapWindow.textdict[self.ovlId]['bbox']
317
self.currClr = wx.BLACK
319
self.currFont = self.GetFont()
321
self.currCoords = [10, 10]
322
self.currBB = wx.Rect()
324
self.sizer = wx.BoxSizer(wx.VERTICAL)
325
box = wx.GridBagSizer(vgap=5, hgap=5)
328
self.chkbox = wx.CheckBox(parent=self, id=wx.ID_ANY,
329
label=_('Show text object'))
330
if self.parent.Map.GetOverlay(self.ovlId) is None:
331
self.chkbox.SetValue(True)
333
self.chkbox.SetValue(self.parent.MapWindow.overlays[self.ovlId]['layer'].IsActive())
334
box.Add(item=self.chkbox, span=(1, 2),
338
box.Add(item=wx.StaticText(parent=self, id=wx.ID_ANY, label=_("Text:")),
339
flag=wx.ALIGN_CENTER_VERTICAL,
342
self.textentry = ExpandoTextCtrl(parent=self, id=wx.ID_ANY, value="", size=(300, -1))
343
self.textentry.SetFont(self.currFont)
344
self.textentry.SetForegroundColour(self.currClr)
345
self.textentry.SetValue(self.currText)
346
# get rid of unneeded scrollbar when text box first opened
347
self.textentry.SetClientSize((300, -1))
349
box.Add(item=self.textentry,
354
box.Add(item=wx.StaticText(parent=self, id=wx.ID_ANY, label=_("Rotation:")),
355
flag=wx.ALIGN_CENTER_VERTICAL,
357
self.rotation = wx.SpinCtrl(parent=self, id=wx.ID_ANY, value="", pos=(30, 50),
358
size=(75, -1), style=wx.SP_ARROW_KEYS)
359
self.rotation.SetRange(-360, 360)
360
self.rotation.SetValue(int(self.currRot))
361
box.Add(item=self.rotation,
366
box.Add(item=wx.StaticText(parent=self, id=wx.ID_ANY, label=_("Font:")),
367
flag=wx.ALIGN_CENTER_VERTICAL,
369
fontbtn = wx.Button(parent=self, id=wx.ID_ANY, label=_("Set font"))
370
box.Add(item=fontbtn,
374
box.AddGrowableCol(1)
375
box.AddGrowableRow(1)
376
self.sizer.Add(item=box, proportion=1,
377
flag=wx.ALL | wx.EXPAND, border=10)
380
box = wx.BoxSizer(wx.HORIZONTAL)
381
label = wx.StaticText(parent=self, id=wx.ID_ANY,
382
label=_("Drag text with mouse in pointer mode "
383
"to position.\nDouble-click to change options"))
384
box.Add(item=label, proportion=0,
385
flag=wx.ALIGN_CENTRE | wx.ALL, border=5)
386
self.sizer.Add(item=box, proportion=0,
387
flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER | wx.ALL, border=5)
389
line = wx.StaticLine(parent=self, id=wx.ID_ANY,
390
size=(20, -1), style=wx.LI_HORIZONTAL)
391
self.sizer.Add(item=line, proportion=0,
392
flag=wx.EXPAND | wx.ALIGN_CENTRE | wx.ALL, border=5)
394
btnsizer = wx.StdDialogButtonSizer()
396
btn = wx.Button(parent=self, id=wx.ID_OK)
398
btnsizer.AddButton(btn)
400
btn = wx.Button(parent=self, id=wx.ID_CANCEL)
401
btnsizer.AddButton(btn)
404
self.sizer.Add(item=btnsizer, proportion=0,
405
flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)
407
self.SetSizer(self.sizer)
411
self.Bind(EVT_ETC_LAYOUT_NEEDED, self.OnRefit, self.textentry)
412
self.Bind(wx.EVT_BUTTON, self.OnSelectFont, fontbtn)
413
self.Bind(wx.EVT_TEXT, self.OnText, self.textentry)
414
self.Bind(wx.EVT_SPINCTRL, self.OnRotation, self.rotation)
416
self.SetMinSize((400, 230))
418
def OnRefit(self, event):
419
"""Resize text entry to match text"""
422
def OnText(self, event):
423
"""Change text string"""
424
self.currText = event.GetString()
426
def OnRotation(self, event):
427
"""Change rotation"""
428
self.currRot = event.GetInt()
432
def OnSelectFont(self, event):
435
data.EnableEffects(True)
436
data.SetColour(self.currClr) # set colour
437
data.SetInitialFont(self.currFont)
439
dlg = wx.FontDialog(self, data)
441
if dlg.ShowModal() == wx.ID_OK:
442
data = dlg.GetFontData()
443
self.currFont = data.GetChosenFont()
444
self.currClr = data.GetColour()
446
self.textentry.SetFont(self.currFont)
447
self.textentry.SetForegroundColour(self.currClr)
454
"""Get text properties"""
455
return {'text': self.currText,
456
'font': self.currFont,
457
'color': self.currClr,
458
'rotation': self.currRot,
459
'coords': self.currCoords,
460
'active': self.chkbox.IsChecked()}