1""" 2@package mapwin.decorations 3 4@brief Map display decorations (overlays) - text, barscale and legend 5 6Classes: 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 15This 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 21import os 22 23import wx 24 25from grass.pydispatch.signal import Signal 26try: 27 from PIL import Image 28 hasPIL = True 29except ImportError: 30 hasPIL = False 31from gui_core.wrap import NewId 32 33 34class OverlayController(object): 35 36 """Base class for decorations (barscale, legend) controller.""" 37 38 def __init__(self, renderer, giface): 39 self._giface = giface 40 self._renderer = renderer 41 self._overlay = None 42 self._coords = None 43 self._pdcType = 'image' 44 self._propwin = None 45 self._defaultAt = '' 46 self._cmd = None # to be set by user 47 self._name = None # to be defined by subclass 48 self._removeLabel = None # to be defined by subclass 49 self._id = NewId() 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( 78 (self._renderer.width, self._renderer.height)) 79 self._coords = [x, y] 80 return self._coords 81 82 coords = property(fset=SetCoords, fget=GetCoords) 83 84 def GetPdcType(self): 85 return self._pdcType 86 87 pdcType = property(fget=GetPdcType) 88 89 def GetName(self): 90 return self._name 91 92 name = property(fget=GetName) 93 94 def GetRemoveLabel(self): 95 return self._removeLabel 96 97 removeLabel = property(fget=GetRemoveLabel) 98 99 def GetId(self): 100 return self._id 101 102 id = property(fget=GetId) 103 104 def GetPropwin(self): 105 return self._propwin 106 107 def SetPropwin(self, win): 108 self._propwin = win 109 110 propwin = property(fget=GetPropwin, fset=SetPropwin) 111 112 def GetLayer(self): 113 return self._overlay 114 115 layer = property(fget=GetLayer) 116 117 def GetDialog(self): 118 return self._dialog 119 120 def SetDialog(self, win): 121 self._dialog = win 122 123 dialog = property(fget=GetDialog, fset=SetDialog) 124 125 def IsShown(self): 126 if self._overlay and self._overlay.IsActive() and self._overlay.IsRendered(): 127 return True 128 return False 129 130 def Show(self, show=True): 131 """Activate or deactivate overlay.""" 132 if show: 133 if not self._overlay: 134 self._add() 135 self._overlay.SetActive(True) 136 self._update() 137 else: 138 self.Hide() 139 140 self.overlayChanged.emit() 141 142 def Hide(self): 143 if self._overlay: 144 self._overlay.SetActive(False) 145 self.overlayChanged.emit() 146 147 def Remove(self): 148 if self._dialog: 149 self._dialog.Destroy() 150 self._renderer.DeleteOverlay(self._overlay) 151 self.overlayChanged.emit() 152 153 def _add(self): 154 self._overlay = self._renderer.AddOverlay( 155 id=self._id, 156 ltype=self._name, 157 command=self.cmd, 158 active=False, 159 render=True, 160 hidden=True) 161 # check if successful 162 163 def _update(self): 164 self._renderer.ChangeOverlay(id=self._id, command=self._cmd) 165 166 def CmdIsValid(self): 167 """If command is valid""" 168 return True 169 170 def GetPlacement(self, screensize): 171 """Get coordinates where to place overlay in a reasonable way 172 173 :param screensize: screen size 174 """ 175 if not hasPIL: 176 self._giface.WriteWarning( 177 _( 178 "Please install Python Imaging Library (PIL)\n" 179 "for better control of legend and other decorations.")) 180 return 0, 0 181 182 for param in self._cmd: 183 if not param.startswith('at'): 184 continue 185 x, y = [float(number) for number in param.split('=')[1].split(',')] 186 x = int((x / 100.) * screensize[0]) 187 y = int((1 - y / 100.) * screensize[1]) 188 189 return x, y 190 191 192class DtextController(OverlayController): 193 194 def __init__(self, renderer, giface): 195 OverlayController.__init__(self, renderer, giface) 196 self._name = 'text' 197 self._removeLabel = _("Remove text") 198 self._defaultAt = 'at=50,50' 199 self._cmd = ['d.text', self._defaultAt] 200 201 def CmdIsValid(self): 202 inputs = 0 203 for param in self._cmd[1:]: 204 param = param.split('=') 205 if len(param) == 1: 206 inputs += 1 207 else: 208 if param[0] == 'text' and len(param) == 2: 209 inputs += 1 210 if inputs >= 1: 211 return True 212 return False 213 214 215class BarscaleController(OverlayController): 216 217 def __init__(self, renderer, giface): 218 OverlayController.__init__(self, renderer, giface) 219 self._name = 'barscale' 220 self._removeLabel = _("Remove scale bar") 221 # different from default because the reference point is not in the 222 # middle 223 self._defaultAt = 'at=0,98' 224 self._cmd = ['d.barscale', self._defaultAt] 225 226 227class ArrowController(OverlayController): 228 229 def __init__(self, renderer, giface): 230 OverlayController.__init__(self, renderer, giface) 231 self._name = 'arrow' 232 self._removeLabel = _("Remove north arrow") 233 # different from default because the reference point is not in the 234 # middle 235 self._defaultAt = 'at=85.0,25.0' 236 self._cmd = ['d.northarrow', self._defaultAt] 237 238 239class LegendVectController(OverlayController): 240 241 def __init__(self, renderer, giface): 242 OverlayController.__init__(self, renderer, giface) 243 self._name = 'vectleg' 244 self._removeLabel = _("Remove vector legend") 245 # different from default because the reference point is not in the 246 # middle 247 self._defaultAt = 'at=20.0,80.0' 248 self._cmd = ['d.legend.vect', self._defaultAt] 249 250 251class LegendController(OverlayController): 252 253 def __init__(self, renderer, giface): 254 OverlayController.__init__(self, renderer, giface) 255 self._name = 'legend' 256 self._removeLabel = _("Remove legend") 257 # default is in the center to avoid trimmed legend on the edge 258 self._defaultAt = 'at=5,50,47,50' 259 self._actualAt = self._defaultAt 260 self._cmd = ['d.legend', self._defaultAt] 261 262 def SetCmd(self, cmd): 263 """Overriden method 264 265 Required for setting default or actual raster legend position. 266 """ 267 hasAt = False 268 for i in cmd: 269 if i.startswith("at="): 270 hasAt = True 271 # reset coordinates, 'at' values will be used, see GetCoords 272 self._coords = None 273 break 274 if not hasAt: 275 if self._actualAt != self._defaultAt: 276 cmd.append(self._actualAt) 277 else: 278 cmd.append(self._defaultAt) 279 self._cmd = cmd 280 281 cmd = property(fset=SetCmd, fget=OverlayController.GetCmd) 282 283 def GetPlacement(self, screensize): 284 if not hasPIL: 285 self._giface.WriteWarning( 286 _( 287 "Please install Python Imaging Library (PIL)\n" 288 "for better control of legend and other decorations.")) 289 return 0, 0 290 for param in self._cmd: 291 if not param.startswith('at'): 292 continue 293 # if the at= is the default, we will move the legend from the center to bottom left 294 if param == self._defaultAt: 295 b, t, l, r = 5, 50, 7, 10 296 else: 297 b, t, l, r = [float(number) for number in param.split( 298 '=')[1].split(',')] # pylint: disable-msg=W0612 299 x = int((l / 100.) * screensize[0]) 300 y = int((1 - t / 100.) * screensize[1]) 301 302 return x, y 303 304 def CmdIsValid(self): 305 inputs = 0 306 for param in self._cmd[1:]: 307 param = param.split('=') 308 if len(param) == 1: 309 inputs += 1 310 else: 311 if param[0] == 'raster' and len(param) == 2: 312 inputs += 1 313 elif param[0] == 'raster_3d' and len(param) == 2: 314 inputs += 1 315 if inputs == 1: 316 return True 317 return False 318 319 def ResizeLegend(self, begin, end, screenSize): 320 """Resize legend according to given bbox coordinates.""" 321 w = abs(begin[0] - end[0]) 322 h = abs(begin[1] - end[1]) 323 if begin[0] < end[0]: 324 x = begin[0] 325 else: 326 x = end[0] 327 if begin[1] < end[1]: 328 y = begin[1] 329 else: 330 y = end[1] 331 332 at = [(screenSize[1] - (y + h)) / float(screenSize[1]) * 100, 333 (screenSize[1] - y) / float(screenSize[1]) * 100, 334 x / float(screenSize[0]) * 100, 335 (x + w) / float(screenSize[0]) * 100] 336 atStr = "at=%d,%d,%d,%d" % (at[0], at[1], at[2], at[3]) 337 338 for i, subcmd in enumerate(self._cmd): 339 if subcmd.startswith('at='): 340 self._cmd[i] = atStr 341 break 342 343 self._coords = None 344 self._actualAt = atStr 345 self.Show() 346 347 def StartResizing(self): 348 """Tool in toolbar or button itself were pressed""" 349 # prepare for resizing 350 window = self._giface.GetMapWindow() 351 window.SetNamedCursor('cross') 352 window.mouse['use'] = None 353 window.mouse['box'] = 'box' 354 window.pen = wx.Pen(colour='Black', width=2, style=wx.SHORT_DASH) 355 window.mouseLeftUp.connect(self._finishResizing) 356 357 def _finishResizing(self): 358 window = self._giface.GetMapWindow() 359 window.mouseLeftUp.disconnect(self._finishResizing) 360 screenSize = window.GetClientSize() 361 self.ResizeLegend( 362 window.mouse["begin"], 363 window.mouse["end"], 364 screenSize) 365 self._giface.GetMapDisplay().GetMapToolbar().SelectDefault() 366 # redraw 367 self.overlayChanged.emit() 368