~ubuntu-branches/ubuntu/vivid/grass/vivid-proposed

« back to all changes in this revision

Viewing changes to gui/wxpython/mapwin/decorations.py

  • Committer: Package Import Robot
  • Author(s): Bas Couwenberg
  • Date: 2015-02-20 23:12:08 UTC
  • mfrom: (8.2.6 experimental)
  • Revision ID: package-import@ubuntu.com-20150220231208-1u6qvqm84v430b10
Tags: 7.0.0-1~exp1
* New upstream release.
* Update python-ctypes-ternary.patch to use if/else instead of and/or.
* Drop check4dev patch, rely on upstream check.
* Add build dependency on libpq-dev to grass-dev for libpq-fe.h.
* Drop patches applied upstream, refresh remaining patches.
* Update symlinks for images switched from jpg to png.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""
 
2
@package mapwin.decorations
 
3
 
 
4
@brief Map display decorations (overlays) - text, barscale and legend
 
5
 
 
6
Classes:
 
7
 - decorations::OverlayController
 
8
 - decorations::BarscaleController
 
9
 - decorations::ArrowController
 
10
 - decorations::LegendController
 
11
 - decorations::TextLayerDialog
 
12
 
 
13
(C) 2006-2014 by the GRASS Development Team
 
14
 
 
15
This program is free software under the GNU General Public License
 
16
(>=v2). Read the file COPYING that comes with GRASS for details.
 
17
 
 
18
@author Anna Kratochvilova <kratochanna gmail.com>
 
19
"""
 
20
 
 
21
import os
 
22
from core.utils import _
 
23
 
 
24
import wx
 
25
from wx.lib.expando import ExpandoTextCtrl, EVT_ETC_LAYOUT_NEEDED
 
26
        
 
27
from grass.pydispatch.signal import Signal
 
28
try:
 
29
    from PIL import Image
 
30
    hasPIL = True
 
31
except ImportError:
 
32
    hasPIL = False
 
33
 
 
34
 
 
35
class OverlayController(object):
 
36
 
 
37
    """Base class for decorations (barscale, legend) controller."""
 
38
 
 
39
    def __init__(self, renderer, giface):
 
40
        self._giface = giface
 
41
        self._renderer = renderer
 
42
        self._overlay = None
 
43
        self._coords = None
 
44
        self._pdcType = 'image'
 
45
        self._propwin = None
 
46
        self._defaultAt = ''
 
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
 
50
        self._dialog = None
 
51
 
 
52
        # signals that overlay or its visibility changed
 
53
        self.overlayChanged = Signal('OverlayController::overlayChanged')
 
54
 
 
55
    def SetCmd(self, cmd):
 
56
        hasAt = False
 
57
        for i in cmd:
 
58
            if i.startswith("at="):
 
59
                hasAt = True
 
60
                # reset coordinates, 'at' values will be used, see GetCoords
 
61
                self._coords = None
 
62
                break
 
63
        if not hasAt:
 
64
            cmd.append(self._defaultAt)
 
65
        self._cmd = cmd
 
66
 
 
67
    def GetCmd(self):
 
68
        return self._cmd
 
69
 
 
70
    cmd = property(fset=SetCmd, fget=GetCmd)
 
71
 
 
72
    def SetCoords(self, coords):
 
73
        self._coords = list(coords)
 
74
 
 
75
    def GetCoords(self):
 
76
        if self._coords is None:  # initial position
 
77
            x, y = self.GetPlacement((self._renderer.width, self._renderer.height))
 
78
            self._coords = [x, y]
 
79
        return self._coords
 
80
 
 
81
    coords = property(fset=SetCoords, fget=GetCoords)
 
82
 
 
83
    def GetPdcType(self):
 
84
        return self._pdcType
 
85
 
 
86
    pdcType = property(fget=GetPdcType)
 
87
 
 
88
    def GetName(self):
 
89
        return self._name
 
90
 
 
91
    name = property(fget=GetName)
 
92
 
 
93
    def GetId(self):
 
94
        return self._id
 
95
 
 
96
    id = property(fget=GetId)
 
97
 
 
98
    def GetPropwin(self):
 
99
        return self._propwin
 
100
 
 
101
    def SetPropwin(self, win):
 
102
        self._propwin = win
 
103
 
 
104
    propwin = property(fget=GetPropwin, fset=SetPropwin)
 
105
 
 
106
    def GetLayer(self):
 
107
        return self._overlay
 
108
 
 
109
    layer = property(fget=GetLayer)
 
110
 
 
111
    def GetDialog(self):
 
112
        return self._dialog
 
113
 
 
114
    def SetDialog(self, win):
 
115
        self._dialog = win
 
116
 
 
117
    dialog = property(fget=GetDialog, fset=SetDialog)
 
118
 
 
119
    def IsShown(self):
 
120
        if self._overlay and self._overlay.IsActive() and self._overlay.IsRendered():
 
121
            return True
 
122
        return False
 
123
 
 
124
    def Show(self, show=True):
 
125
        """Activate or deactivate overlay."""
 
126
        if show:
 
127
            if not self._overlay:
 
128
                self._add()
 
129
            self._overlay.SetActive(True)
 
130
            self._update()
 
131
        else:
 
132
            self.Hide()
 
133
 
 
134
        self.overlayChanged.emit()
 
135
 
 
136
    def Hide(self):
 
137
        if self._overlay:
 
138
            self._overlay.SetActive(False)
 
139
        self.overlayChanged.emit()
 
140
 
 
141
    def GetOptData(self, dcmd, layer, params, propwin):
 
142
        """Called after options are set through module dialog.
 
143
 
 
144
        :param dcmd: resulting command
 
145
        :param layer: not used
 
146
        :param params: module parameters (not used)
 
147
        :param propwin: dialog window
 
148
        """
 
149
        if not dcmd:
 
150
            return
 
151
 
 
152
        self._cmd = dcmd
 
153
        self._dialog = propwin
 
154
 
 
155
        self.Show()
 
156
 
 
157
    def _add(self):
 
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
 
162
 
 
163
    def _update(self):
 
164
        self._renderer.ChangeOverlay(id=self._id, command=self._cmd,
 
165
                                     render=False)
 
166
 
 
167
    def CmdIsValid(self):
 
168
        """If command is valid"""
 
169
        return True
 
170
 
 
171
    def GetPlacement(self, screensize):
 
172
        """Get coordinates where to place overlay in a reasonable way
 
173
 
 
174
        :param screensize: screen size
 
175
        """
 
176
        if not hasPIL:
 
177
            self._giface.WriteWarning(_("Please install Python Imaging Library (PIL)\n"
 
178
                                        "for better control of legend and other decorations."))
 
179
            return 0, 0
 
180
        for param in self._cmd:
 
181
            if not param.startswith('at'):
 
182
                continue
 
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])
 
186
 
 
187
            return x, y
 
188
 
 
189
 
 
190
class BarscaleController(OverlayController):
 
191
 
 
192
    def __init__(self, renderer, giface):
 
193
        OverlayController.__init__(self, renderer, giface)
 
194
        self._id = 1
 
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]
 
199
 
 
200
 
 
201
class ArrowController(OverlayController):
 
202
 
 
203
    def __init__(self, renderer, giface):
 
204
        OverlayController.__init__(self, renderer, giface)
 
205
        self._id = 2
 
206
        self._name = 'arrow'
 
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]
 
210
 
 
211
 
 
212
class LegendController(OverlayController):
 
213
 
 
214
    def __init__(self, renderer, giface):
 
215
        OverlayController.__init__(self, renderer, giface)
 
216
        self._id = 0
 
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]
 
221
 
 
222
    def GetPlacement(self, screensize):
 
223
        if not hasPIL:
 
224
            self._giface.WriteWarning(_("Please install Python Imaging Library (PIL)\n"
 
225
                                        "for better control of legend and other decorations."))
 
226
            return 0, 0
 
227
        for param in self._cmd:
 
228
            if not param.startswith('at'):
 
229
                continue
 
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])
 
233
 
 
234
            return x, y
 
235
 
 
236
    def CmdIsValid(self):
 
237
        inputs = 0
 
238
        for param in self._cmd[1:]:
 
239
            param = param.split('=')
 
240
            if len(param) == 1:
 
241
                inputs += 1
 
242
            else:
 
243
                if param[0] == 'raster' and len(param) == 2:
 
244
                    inputs += 1
 
245
                elif param[0] == 'raster_3d' and len(param) == 2:
 
246
                    inputs += 1
 
247
        if inputs == 1:
 
248
            return True
 
249
        return False
 
250
 
 
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]:
 
256
            x = begin[0]
 
257
        else:
 
258
            x = end[0]
 
259
        if begin[1] < end[1]:
 
260
            y = begin[1]
 
261
        else:
 
262
            y = end[1]
 
263
 
 
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])
 
269
 
 
270
        for i, subcmd in enumerate(self._cmd):
 
271
            if subcmd.startswith('at='):
 
272
                self._cmd[i] = atStr
 
273
                break
 
274
 
 
275
        self._coords = None
 
276
        self.Show()
 
277
 
 
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)
 
287
 
 
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()
 
294
        # redraw
 
295
        self.overlayChanged.emit()
 
296
 
 
297
 
 
298
class TextLayerDialog(wx.Dialog):
 
299
    """!Controls setting options and displaying/hiding map overlay decorations
 
300
    """
 
301
    def __init__(self, parent, ovlId, title, name='text', size=wx.DefaultSize,
 
302
                 style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER):
 
303
 
 
304
        wx.Dialog.__init__(self, parent=parent, id=wx.ID_ANY, title=title, style=style, size=size)
 
305
 
 
306
        self.ovlId = ovlId
 
307
        self.parent = parent
 
308
 
 
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']
 
316
        else:
 
317
            self.currClr = wx.BLACK
 
318
            self.currText = ''
 
319
            self.currFont = self.GetFont()
 
320
            self.currRot = 0.0
 
321
            self.currCoords = [10, 10]
 
322
            self.currBB = wx.Rect()
 
323
 
 
324
        self.sizer = wx.BoxSizer(wx.VERTICAL)
 
325
        box = wx.GridBagSizer(vgap=5, hgap=5)
 
326
 
 
327
        # show/hide
 
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)
 
332
        else:
 
333
            self.chkbox.SetValue(self.parent.MapWindow.overlays[self.ovlId]['layer'].IsActive())
 
334
        box.Add(item=self.chkbox, span=(1, 2),
 
335
                pos=(0, 0))
 
336
 
 
337
        # text entry
 
338
        box.Add(item=wx.StaticText(parent=self, id=wx.ID_ANY, label=_("Text:")),
 
339
                flag=wx.ALIGN_CENTER_VERTICAL,
 
340
                pos=(1, 0))
 
341
 
 
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))
 
348
 
 
349
        box.Add(item=self.textentry,
 
350
                flag=wx.EXPAND,
 
351
                pos=(1, 1))
 
352
 
 
353
        # rotation
 
354
        box.Add(item=wx.StaticText(parent=self, id=wx.ID_ANY, label=_("Rotation:")),
 
355
                flag=wx.ALIGN_CENTER_VERTICAL,
 
356
                pos=(2, 0))
 
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,
 
362
                flag=wx.ALIGN_RIGHT,
 
363
                pos=(2, 1))
 
364
 
 
365
        # font
 
366
        box.Add(item=wx.StaticText(parent=self, id=wx.ID_ANY, label=_("Font:")),
 
367
                flag=wx.ALIGN_CENTER_VERTICAL,
 
368
                pos=(3, 0))
 
369
        fontbtn = wx.Button(parent=self, id=wx.ID_ANY, label=_("Set font"))
 
370
        box.Add(item=fontbtn,
 
371
                flag=wx.ALIGN_RIGHT,
 
372
                pos=(3, 1))
 
373
 
 
374
        box.AddGrowableCol(1)
 
375
        box.AddGrowableRow(1)
 
376
        self.sizer.Add(item=box, proportion=1,
 
377
                       flag=wx.ALL | wx.EXPAND, border=10)
 
378
 
 
379
        # note
 
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)
 
388
 
 
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)
 
393
 
 
394
        btnsizer = wx.StdDialogButtonSizer()
 
395
 
 
396
        btn = wx.Button(parent=self, id=wx.ID_OK)
 
397
        btn.SetDefault()
 
398
        btnsizer.AddButton(btn)
 
399
 
 
400
        btn = wx.Button(parent=self, id=wx.ID_CANCEL)
 
401
        btnsizer.AddButton(btn)
 
402
        btnsizer.Realize()
 
403
 
 
404
        self.sizer.Add(item=btnsizer, proportion=0,
 
405
                       flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)
 
406
 
 
407
        self.SetSizer(self.sizer)
 
408
        self.sizer.Fit(self)
 
409
 
 
410
        # bindings
 
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)
 
415
 
 
416
        self.SetMinSize((400, 230))
 
417
        
 
418
    def OnRefit(self, event):
 
419
        """Resize text entry to match text"""
 
420
        self.sizer.Fit(self)
 
421
 
 
422
    def OnText(self, event):
 
423
        """Change text string"""
 
424
        self.currText = event.GetString()
 
425
 
 
426
    def OnRotation(self, event):
 
427
        """Change rotation"""
 
428
        self.currRot = event.GetInt()
 
429
 
 
430
        event.Skip()
 
431
 
 
432
    def OnSelectFont(self, event):
 
433
        """Change font"""
 
434
        data = wx.FontData()
 
435
        data.EnableEffects(True)
 
436
        data.SetColour(self.currClr)         # set colour
 
437
        data.SetInitialFont(self.currFont)
 
438
 
 
439
        dlg = wx.FontDialog(self, data)
 
440
 
 
441
        if dlg.ShowModal() == wx.ID_OK:
 
442
            data = dlg.GetFontData()
 
443
            self.currFont = data.GetChosenFont()
 
444
            self.currClr = data.GetColour()
 
445
 
 
446
            self.textentry.SetFont(self.currFont)
 
447
            self.textentry.SetForegroundColour(self.currClr)
 
448
 
 
449
            self.Layout()
 
450
 
 
451
        dlg.Destroy()
 
452
 
 
453
    def GetValues(self):
 
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()}