[GRASS-SVN] r69199 - in grass/branches/releasebranch_7_2: . display display/d.legend.vect display/d.mon display/d.vect gui/images gui/images/symbols gui/images/symbols/legend gui/wxpython/core gui/wxpython/gui_core gui/wxpython/lmgr gui/wxpython/mapdisp gui/wxpython/mapwin gui/wxpython/nviz include lib/symbol lib/symbol/symbol lib/symbol/symbol/legend

svn_grass at osgeo.org svn_grass at osgeo.org
Sun Aug 21 15:54:25 PDT 2016


Author: annakrat
Date: 2016-08-21 15:54:25 -0700 (Sun, 21 Aug 2016)
New Revision: 69199

Added:
   grass/branches/releasebranch_7_2/display/d.legend.vect/
   grass/branches/releasebranch_7_2/display/d.vect/legend.c
   grass/branches/releasebranch_7_2/gui/images/symbols/legend/
   grass/branches/releasebranch_7_2/gui/images/symbols/legend/area.png
   grass/branches/releasebranch_7_2/gui/images/symbols/legend/area_curved.png
   grass/branches/releasebranch_7_2/gui/images/symbols/legend/line.png
   grass/branches/releasebranch_7_2/gui/images/symbols/legend/line_crooked.png
   grass/branches/releasebranch_7_2/lib/symbol/symbol/legend/
   grass/branches/releasebranch_7_2/lib/symbol/symbol/legend/area
   grass/branches/releasebranch_7_2/lib/symbol/symbol/legend/area_curved
   grass/branches/releasebranch_7_2/lib/symbol/symbol/legend/line
   grass/branches/releasebranch_7_2/lib/symbol/symbol/legend/line_crooked
Modified:
   grass/branches/releasebranch_7_2/
   grass/branches/releasebranch_7_2/display/Makefile
   grass/branches/releasebranch_7_2/display/d.legend.vect/Makefile
   grass/branches/releasebranch_7_2/display/d.legend.vect/d.legend.vect.html
   grass/branches/releasebranch_7_2/display/d.legend.vect/draw.c
   grass/branches/releasebranch_7_2/display/d.legend.vect/local_proto.h
   grass/branches/releasebranch_7_2/display/d.legend.vect/main.c
   grass/branches/releasebranch_7_2/display/d.mon/render_cmd.py
   grass/branches/releasebranch_7_2/display/d.mon/start.c
   grass/branches/releasebranch_7_2/display/d.vect/local_proto.h
   grass/branches/releasebranch_7_2/display/d.vect/main.c
   grass/branches/releasebranch_7_2/gui/images/Makefile
   grass/branches/releasebranch_7_2/gui/wxpython/core/giface.py
   grass/branches/releasebranch_7_2/gui/wxpython/core/render.py
   grass/branches/releasebranch_7_2/gui/wxpython/core/utils.py
   grass/branches/releasebranch_7_2/gui/wxpython/core/workspace.py
   grass/branches/releasebranch_7_2/gui/wxpython/gui_core/forms.py
   grass/branches/releasebranch_7_2/gui/wxpython/gui_core/widgets.py
   grass/branches/releasebranch_7_2/gui/wxpython/lmgr/frame.py
   grass/branches/releasebranch_7_2/gui/wxpython/mapdisp/frame.py
   grass/branches/releasebranch_7_2/gui/wxpython/mapdisp/main.py
   grass/branches/releasebranch_7_2/gui/wxpython/mapdisp/toolbars.py
   grass/branches/releasebranch_7_2/gui/wxpython/mapwin/base.py
   grass/branches/releasebranch_7_2/gui/wxpython/mapwin/buffered.py
   grass/branches/releasebranch_7_2/gui/wxpython/mapwin/decorations.py
   grass/branches/releasebranch_7_2/gui/wxpython/nviz/mapwindow.py
   grass/branches/releasebranch_7_2/gui/wxpython/nviz/wxnviz.py
   grass/branches/releasebranch_7_2/include/symbol.h
   grass/branches/releasebranch_7_2/lib/symbol/Makefile
   grass/branches/releasebranch_7_2/lib/symbol/read.c
Log:
wxGUI/vector legend: major backport of changes in overlays and Adam Laza's GSoC work related to vector legend


Property changes on: grass/branches/releasebranch_7_2
___________________________________________________________________
Added: svn:mergeinfo
   + /grass/trunk:68502,68507,68529,68570,68612,68671,68699-68702,68711,68753-68754,68758-68760,68947,68950,68953,68960,69000,69003,69005-69007,69036,69052,69069,69075,69100,69107,69116,69153,69169,69173,69191

Modified: grass/branches/releasebranch_7_2/display/Makefile
===================================================================
--- grass/branches/releasebranch_7_2/display/Makefile	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/display/Makefile	2016-08-21 22:54:25 UTC (rev 69199)
@@ -18,6 +18,7 @@
 	d.info \
 	d.labels \
 	d.legend \
+	d.legend.vect \
 	d.linegraph \
 	d.mon \
 	d.northarrow \


Property changes on: grass/branches/releasebranch_7_2/display/d.legend.vect/Makefile
___________________________________________________________________
Added: svn:mime-type
   + text/x-makefile
Added: svn:eol-style
   + native

Modified: grass/branches/releasebranch_7_2/display/d.legend.vect/d.legend.vect.html
===================================================================
--- grass/trunk/display/d.legend.vect/d.legend.vect.html	2016-08-21 19:05:45 UTC (rev 69191)
+++ grass/branches/releasebranch_7_2/display/d.legend.vect/d.legend.vect.html	2016-08-21 22:54:25 UTC (rev 69199)
@@ -95,4 +95,4 @@
 Mentors: Anna Petrasova, Vaclav Petras, Martin Landa
 
 <p>
-<i>Last changed: $Date: 2015-08-11 23:06:03 +0200 (Út, 11 srp 2015) $</i>
+<i>Last changed: $Date$</i>


Property changes on: grass/branches/releasebranch_7_2/display/d.legend.vect/d.legend.vect.html
___________________________________________________________________
Added: svn:mime-type
   + text/html
Added: svn:keywords
   + Author Date Id
Added: svn:eol-style
   + native


Property changes on: grass/branches/releasebranch_7_2/display/d.legend.vect/draw.c
___________________________________________________________________
Added: svn:mime-type
   + text/x-csrc
Added: svn:eol-style
   + native


Property changes on: grass/branches/releasebranch_7_2/display/d.legend.vect/local_proto.h
___________________________________________________________________
Added: svn:mime-type
   + text/x-chdr
Added: svn:eol-style
   + native


Property changes on: grass/branches/releasebranch_7_2/display/d.legend.vect/main.c
___________________________________________________________________
Added: svn:mime-type
   + text/x-csrc
Added: svn:eol-style
   + native

Modified: grass/branches/releasebranch_7_2/display/d.mon/render_cmd.py
===================================================================
--- grass/branches/releasebranch_7_2/display/d.mon/render_cmd.py	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/display/d.mon/render_cmd.py	2016-08-21 22:54:25 UTC (rev 69199)
@@ -8,7 +8,7 @@
 
 # read environment variables from file
 def read_env_file(env_file):
-    width = height = None
+    width = height = legfile = None
     fd = open(env_file, 'r')
     if fd is None:
         grass.fatal("Unable to open file '{}'".format(env_file))
@@ -22,16 +22,19 @@
             width = int(v)
         if height is None and k == 'GRASS_RENDER_HEIGHT':
             height = int(v)
+        if legfile is None and k == 'GRASS_LEGEND_FILE':
+            legfile = v
     fd.close()
 
     if width is None or height is None:
         grass.fatal("Unknown monitor size")
 
-    return width, height
+    return width, height, legfile
 
 # run display command
 def render(cmd, mapfile):
     env = os.environ.copy()
+
     if mapfile:
         env['GRASS_RENDER_FILE'] = mapfile
     try:
@@ -101,10 +104,10 @@
     path = os.path.dirname(os.path.abspath(__file__))
     mon = os.path.split(path)[-1]
 
-    width, height = read_env_file(os.path.join(path, 'env'))
+    width, height, legfile = read_env_file(os.path.join(path, 'env'))
     if mon.startswith('wx'):
         mapfile = tempfile.NamedTemporaryFile(dir=path).name
-        if cmd[0] in ('d.barscale', 'd.legend', 'd.northarrow'):
+        if cmd[0] in ('d.barscale', 'd.legend', 'd.northarrow', 'd.legend.vect'):
             mapfile += '.png'
         else:
             mapfile += '.ppm'
@@ -114,5 +117,8 @@
 
     render(cmd, mapfile)
     update_cmd_file(os.path.join(path, 'cmd'), cmd, mapfile)
+    if cmd[0] == 'd.erase' and os.path.exists(legfile):
+        os.remove(legfile)
 
+
     sys.exit(0)

Modified: grass/branches/releasebranch_7_2/display/d.mon/start.c
===================================================================
--- grass/branches/releasebranch_7_2/display/d.mon/start.c	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/display/d.mon/start.c	2016-08-21 22:54:25 UTC (rev 69199)
@@ -133,7 +133,7 @@
 	      int truecolor, int x_only, int update)
 {
     char *mon_path;
-    char *out_file, *env_file, *cmd_file;
+    char *out_file, *env_file, *cmd_file, *leg_file;
     char  buf[1024];
     char file_path[GPATH_MAX], render_cmd_path[GPATH_MAX];
     int  fd;
@@ -158,6 +158,8 @@
     env_file = G_store(file_path);
     G_file_name(file_path, mon_path, "cmd", G_mapset());
     cmd_file = G_store(file_path);
+    G_file_name(file_path, mon_path, "leg", G_mapset());
+    leg_file = G_store(file_path);
 
     /* create py file (renderer) */
     sprintf(render_cmd_path, "%s/etc/d.mon/render_cmd.py", getenv("GISBASE"));
@@ -199,6 +201,10 @@
     write(fd, buf, strlen(buf));
     sprintf(buf, "GRASS_RENDER_HEIGHT=%d\n", height);
     write(fd, buf, strlen(buf));
+    sprintf(buf, "GRASS_LEGEND_FILE=%s\n", leg_file);
+    write(fd, buf, strlen(buf));
+
+
     if (bgcolor) {
 	if (strcmp(bgcolor, "none") == 0)
 	    sprintf(buf, "GRASS_RENDER_TRANSPARENT=TRUE\n");

Added: grass/branches/releasebranch_7_2/display/d.vect/legend.c
===================================================================
--- grass/branches/releasebranch_7_2/display/d.vect/legend.c	                        (rev 0)
+++ grass/branches/releasebranch_7_2/display/d.vect/legend.c	2016-08-21 22:54:25 UTC (rev 69199)
@@ -0,0 +1,78 @@
+#include <grass/gis.h>
+#include <grass/vector.h>
+#include "local_proto.h"
+
+void write_into_legfile(struct Map_info *Map, int type, const char *leglab, const char *name_map, const char *icon,
+                       const char *size, const char *color, const char *fcolor, const char *width, const char *icon_area,
+                       const char *icon_line, const char *size_column)
+{
+    int nfeatures;
+    FILE *fd;
+    char *leg_file;
+    char map[GNAME_MAX];
+    char *ptr;
+    strcpy(map, name_map);
+    strtok_r(map, "@", &ptr);
+
+    if (size_column)
+        size = "-1";
+
+    /* Write into legend file */
+    leg_file = getenv("GRASS_LEGEND_FILE");
+    if (leg_file) {
+        fd = fopen(leg_file, "a");
+
+        /* Point */
+        if (type & GV_POINT){
+            nfeatures = Vect_get_num_primitives(Map, GV_POINT);
+            if (nfeatures > 0) {
+                if (leglab)
+                    fprintf(fd, "%s|", leglab);
+                else
+                    fprintf(fd, "%s|", map);
+                fprintf(fd, "%s|%s|%s|%s|%s", icon, size, color, fcolor, width);
+                fprintf(fd, "|%s|%d\n", "point", nfeatures);
+            }
+        }
+
+        /* Line */
+        if (type & GV_LINE){
+            nfeatures = Vect_get_num_primitives(Map, GV_LINE);
+            if (nfeatures > 0){
+                if (leglab)
+                    fprintf(fd, "%s|", leglab);
+                else
+                    fprintf(fd, "%s|", map);
+                fprintf(fd, "%s|%s|%s|%s|%s", icon_line, size, color, fcolor, width);
+                fprintf(fd, "|%s|%d\n", "line", nfeatures);
+            }
+        }
+
+        /* Area */
+        if (type & GV_AREA){
+            nfeatures = Vect_get_num_primitives(Map, GV_BOUNDARY);
+            if (nfeatures > 0) {
+                if (leglab)
+                    fprintf(fd, "%s|", leglab);
+                else
+                    fprintf(fd, "%s|", map);
+                fprintf(fd, "%s|%s|%s|%s|%s", icon_area, size, color, fcolor, width);
+                fprintf(fd, "|%s|%d\n", "area", nfeatures);
+            }
+        }
+        /* Centroid */
+        if (type & GV_CENTROID){
+            nfeatures = Vect_get_num_primitives(Map, GV_CENTROID);
+            if (nfeatures > 0) {
+                if (leglab)
+                    fprintf(fd, "%s|", leglab);
+                else
+                    fprintf(fd, "%s|", map);
+                fprintf(fd, "%s|%s|%s|%s|%s", icon, size, color, fcolor, width);
+                fprintf(fd, "|%s|%d\n", "centroid", nfeatures);
+            }
+        }
+
+        fclose(fd);
+    }
+}


Property changes on: grass/branches/releasebranch_7_2/display/d.vect/legend.c
___________________________________________________________________
Added: svn:mime-type
   + text/x-csrc
Added: svn:eol-style
   + native

Modified: grass/branches/releasebranch_7_2/display/d.vect/local_proto.h
===================================================================
--- grass/branches/releasebranch_7_2/display/d.vect/local_proto.h	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/display/d.vect/local_proto.h	2016-08-21 22:54:25 UTC (rev 69199)
@@ -65,3 +65,8 @@
 
 /* zcoor.c */
 int display_zcoor(struct Map_info *, int, LATTR *);
+
+/* legend.c */
+void write_into_legfile(struct Map_info *, int, const char *, const char *,
+			const char *, const char *, const char *, const char *,
+			const char *, const char *, const char *, const char *);

Modified: grass/branches/releasebranch_7_2/display/d.vect/main.c
===================================================================
--- grass/branches/releasebranch_7_2/display/d.vect/main.c	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/display/d.vect/main.c	2016-08-21 22:54:25 UTC (rev 69199)
@@ -4,6 +4,7 @@
  * MODULE:       d.vect
  * AUTHOR(S):    CERL, Radim Blazek, others
  *               Updated to GRASS7 by Martin Landa <landa.martin gmail.com>
+ *               Support for vector legend by Adam Laza <ad.laza32 gmail.com >
  * PURPOSE:      Display the vector map in map display
  * COPYRIGHT:    (C) 2004-2014 by the GRASS Development Team
  *
@@ -52,7 +53,9 @@
     struct Option *lsize_opt, *font_opt, *enc_opt, *xref_opt, *yref_opt;
     struct Option *attrcol_opt, *maxreg_opt, *minreg_opt;
     struct Option *width_opt, *wcolumn_opt, *wscale_opt;
-    struct Flag *id_flag, *cats_acolors_flag, *sqrt_flag;
+    struct Option *leglab_opt;
+    struct Option *icon_line_opt, *icon_area_opt;
+    struct Flag *id_flag, *cats_acolors_flag, *sqrt_flag, *legend_flag;
     char *desc;
     
     struct cat_list *Clist;
@@ -189,6 +192,32 @@
     rotcolumn_opt->description =
 	_("Measured in degrees CCW from east");
 
+    icon_area_opt = G_define_option();
+    icon_area_opt->key = "icon_area";
+    icon_area_opt->type = TYPE_STRING;
+    icon_area_opt->required = NO;
+    icon_area_opt->multiple = NO;
+    icon_area_opt->guisection = _("Legend");
+    icon_area_opt->answer = "legend/area";
+    icon_area_opt->options = icon_files();
+    icon_area_opt->description = _("Area/boundary symbol for legend");
+
+    icon_line_opt = G_define_option();
+    icon_line_opt->key = "icon_line";
+    icon_line_opt->type = TYPE_STRING;
+    icon_line_opt->required = NO;
+    icon_line_opt->multiple = NO;
+    icon_line_opt->guisection = _("Legend");
+    icon_line_opt->answer = "legend/line";
+    icon_line_opt->options = icon_files();
+    icon_line_opt->description = _("Line symbol for legend");
+
+    leglab_opt = G_define_option();
+    leglab_opt->key = "legend_label";
+    leglab_opt->type = TYPE_STRING;
+    leglab_opt->guisection = _("Legend");
+    leglab_opt->description = _("Label to display after symbol in vector legend");
+
     /* Labels */
     lfield_opt = G_define_standard_option(G_OPT_V_FIELD);
     lfield_opt->key = "label_layer";
@@ -295,6 +324,11 @@
 	  "instead of circle radius");
     sqrt_flag->guisection = _("Symbols");
 
+    legend_flag = G_define_flag();
+    legend_flag->key = 'l';
+    legend_flag->label = _("Do not add this layer to vector legend");
+    legend_flag->guisection = _("Legend");
+
     /* Check command line */
     if (G_parser(argc, argv))
 	exit(EXIT_FAILURE);
@@ -424,6 +458,14 @@
 		stat += display_dir(&Map, type, Clist, chcat, size);
 	}
 
+	if (!legend_flag->answer) {
+		write_into_legfile(&Map, type, leglab_opt->answer, map_name,
+			   icon_opt->answer, size_opt->answer, 
+			   color_opt->answer, fcolor_opt->answer, 
+			   width_opt->answer, icon_area_opt->answer,
+			   icon_line_opt->answer, sizecolumn_opt->answer);
+	}
+
 	/* reset line width: Do we need to get line width from display
 	 * driver (not implemented)?  It will help restore previous line
 	 * width (not just 0) determined by another module (e.g.,

Modified: grass/branches/releasebranch_7_2/gui/images/Makefile
===================================================================
--- grass/branches/releasebranch_7_2/gui/images/Makefile	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/gui/images/Makefile	2016-08-21 22:54:25 UTC (rev 69199)
@@ -9,7 +9,7 @@
 IMGDST := $(patsubst %,$(DSTDIR)/%,$(IMGSRC))
 
 # symbols
-CATEGORIES = basic demo extra geology n_arrows
+CATEGORIES = basic demo extra geology legend n_arrows
 SYMSRC := $(foreach dir,$(CATEGORIES),$(wildcard symbols/$(dir)/*.png))
 SYMDST := $(patsubst symbols/%,$(DSTDIR)/symbols/%,$(SYMSRC))
 

Added: grass/branches/releasebranch_7_2/gui/images/symbols/legend/area.png
===================================================================
(Binary files differ)


Property changes on: grass/branches/releasebranch_7_2/gui/images/symbols/legend/area.png
___________________________________________________________________
Added: svn:mime-type
   + image/png

Added: grass/branches/releasebranch_7_2/gui/images/symbols/legend/area_curved.png
===================================================================
(Binary files differ)


Property changes on: grass/branches/releasebranch_7_2/gui/images/symbols/legend/area_curved.png
___________________________________________________________________
Added: svn:mime-type
   + image/png

Added: grass/branches/releasebranch_7_2/gui/images/symbols/legend/line.png
===================================================================
(Binary files differ)


Property changes on: grass/branches/releasebranch_7_2/gui/images/symbols/legend/line.png
___________________________________________________________________
Added: svn:mime-type
   + image/png

Added: grass/branches/releasebranch_7_2/gui/images/symbols/legend/line_crooked.png
===================================================================
(Binary files differ)


Property changes on: grass/branches/releasebranch_7_2/gui/images/symbols/legend/line_crooked.png
___________________________________________________________________
Added: svn:mime-type
   + image/png

Modified: grass/branches/releasebranch_7_2/gui/wxpython/core/giface.py
===================================================================
--- grass/branches/releasebranch_7_2/gui/wxpython/core/giface.py	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/gui/wxpython/core/giface.py	2016-08-21 22:54:25 UTC (rev 69199)
@@ -285,7 +285,7 @@
         os.environ["GRASS_MESSAGE_FORMAT"] = orig
 
     def GetLayerList(self):
-        raise NotImplementedError()
+        return []
 
     def GetLayerTree(self):
         return None

Modified: grass/branches/releasebranch_7_2/gui/wxpython/core/render.py
===================================================================
--- grass/branches/releasebranch_7_2/gui/wxpython/core/render.py	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/gui/wxpython/core/render.py	2016-08-21 22:54:25 UTC (rev 69199)
@@ -324,6 +324,10 @@
         """Represents map layer in the map canvas
         """
         Layer.__init__(self, *args, **kwargs)
+        if self.type in ('vector'):  # will add d.vect.thematic
+            self._legrow = grass.tempfile(create=True)
+        else:
+            self._legrow = ''
 
     def GetMapset(self):
         """Get mapset of map layer
@@ -364,6 +368,7 @@
         self.thread = gThread()
 
         self.updateProgress = Signal('RenderLayerMgr.updateProgress')
+        self.renderingFailed = Signal('RenderLayerMgr.renderingFailed')
 
         self._startTime = None
         self._render_env = env
@@ -383,20 +388,26 @@
         env_cmd = env.copy()
         env_cmd.update(self._render_env)
         env_cmd['GRASS_RENDER_FILE'] = self.layer.mapfile
+        if self.layer.GetType() in ('vector'):
+            if os.path.isfile(self.layer._legrow):
+                os.remove(self.layer._legrow)
+            env_cmd['GRASS_LEGEND_FILE'] = self.layer._legrow
 
         cmd_render = copy.deepcopy(cmd)
         cmd_render[1]['quiet'] = True  # be quiet
 
         self._startTime = time.time()
         self.thread.Run(callable=self._render, cmd=cmd_render, env=env_cmd,
-                        ondone=self.OnRenderDone)
+                        ondone=self.OnRenderDone, userdata={'cmd': cmd})
         self.layer.forceRender = False
 
     def _render(self, cmd, env):
-        try:
-            return grass.run_command(cmd[0], env=env, **cmd[1])
-        except CalledModuleError as e:
-            return 1
+        p = grass.start_command(cmd[0], env=env, stderr=grass.PIPE, **cmd[1])
+        stdout, stderr = p.communicate()
+        if p.returncode:
+            return stderr
+        else:
+            return None
 
     def Abort(self):
         """Abort rendering process"""
@@ -419,13 +430,14 @@
 
         Emits updateProcess
         """
-        Debug.msg(1, "RenderLayerMgr.OnRenderDone(%s): ret=%d time=%f" %
+        Debug.msg(1, "RenderLayerMgr.OnRenderDone(%s): err=%s time=%f" %
                   (self.layer, event.ret, time.time() - self._startTime))
-        if event.ret != 0:
-            try:
-                os.remove(self.layer.mapfile)
-            except:
-                pass
+        if event.ret is not None:
+            cmd = cmdtuple_to_list(event.userdata['cmd'])
+            self.renderingFailed.emit(cmd=cmd, error=event.ret)
+            # don't remove layer if overlay, we need to keep the old one
+            if self.layer.type != 'overlay':
+                try_remove(self.layer.mapfile)
 
         self.updateProgress.emit(layer=self.layer)
 
@@ -443,6 +455,7 @@
 
         self.updateMap = Signal('RenderMapMgr.updateMap')
         self.updateProgress = Signal('RenderMapMgr.updateProgress')
+        self.renderingFailed = Signal('RenderMapMgr.renderingFailed')
         self.renderDone = Signal('RenderMapMgr.renderDone')
         self.renderDone.connect(self.OnRenderDone)
 
@@ -450,10 +463,13 @@
         self._render_env = {"GRASS_RENDER_BACKGROUNDCOLOR": "000000",
                             "GRASS_RENDER_FILE_COMPRESSION": "0",
                             "GRASS_RENDER_TRUECOLOR": "TRUE",
-                            "GRASS_RENDER_TRANSPARENT": "TRUE"}
+                            "GRASS_RENDER_TRANSPARENT": "TRUE",
+                            "GRASS_LEGEND_FILE": self.Map.legfile
+                            }
 
         self._init()
         self._rendering = False
+        self._old_legend = []
 
     def _init(self, env=None):
         """Init render manager
@@ -469,6 +485,9 @@
         if os.path.exists(self.Map.mapfile):
             os.remove(self.Map.mapfile)
 
+    def UpdateRenderEnv(self, env):
+        self._render_env.update(env)
+
     def _renderLayers(self, env, force=False, overlaysOnly=False):
         """Render all map layers into files
 
@@ -511,6 +530,7 @@
         env['GRASS_REGION'] = self.Map.SetRegion(windres)
         env['GRASS_RENDER_WIDTH'] = str(self.Map.width)
         env['GRASS_RENDER_HEIGHT'] = str(self.Map.height)
+
         if UserSettings.Get(group='display', key='driver',
                             subkey='type') == 'png':
             env['GRASS_RENDER_IMMEDIATE'] = 'png'
@@ -519,6 +539,25 @@
 
         return env
 
+    def RenderOverlays(self, force=False):
+        """Render only overlays
+
+        :param bool force: force rendering all map layers
+        """
+        if self._rendering:
+            Debug.msg(
+                1, "RenderMapMgr().RenderOverlays(): cancelled (already rendering)")
+            return
+
+        wx.BeginBusyCursor()
+        self._rendering = True
+
+        env = self.GetRenderEnv()
+        self._init(env)
+        # no layer composition afterwards
+        if self._renderLayers(env, force, overlaysOnly=True) == 0:
+            self.renderDone.emit()
+
     def Render(self, force=False, windres=False):
         """Render map composition
 
@@ -535,9 +574,10 @@
 
         env = self.GetRenderEnv(windres)
         self._init(env)
-        if self._renderLayers(env, force, windres) == 0:
+        if self._renderLayers(env, force) == 0:
             self.renderDone.emit()
 
+
     def OnRenderDone(self):
         """Rendering process done
 
@@ -549,6 +589,8 @@
         masks = list()
         opacities = list()
 
+        # TODO: g.pnmcomp is now called every time
+        # even when only overlays are rendered
         for layer in self.layers:
             if layer.GetType() == 'overlay':
                 continue
@@ -584,11 +626,33 @@
         Debug.msg(1, "RenderMapMgr.OnRenderDone() time=%f sec (comp: %f)" %
                   (stop - self._startTime, stop - startCompTime))
 
+        # Update legfile
+        new_legend = []
+        with open(self.Map.legfile, "w") as outfile:
+            for layer in reversed(self.layers):
+                if layer.GetType() not in ('vector'):
+                    continue
+
+                if os.path.isfile(layer._legrow) and layer._legrow[-1].isdigit() \
+                   and layer.hidden is False:
+                    with open(layer._legrow) as infile:
+                        line = infile.read()
+                        outfile.write(line)
+                        new_legend.append(line)
+
         self._rendering = False
         if wx.IsBusy():
             wx.EndBusyCursor()
 
-        self.updateMap.emit()
+        # if legend file changed, rerender vector legend
+        if new_legend != self._old_legend:
+            self._old_legend = new_legend
+            for layer in self.layers:
+                if layer.GetType() == 'overlay' and layer.GetName() == 'vectleg':
+                    layer.forceRender = True
+            self.Render()
+        else:
+            self.updateMap.emit()
 
     def Abort(self):
         """Abort all rendering processes"""
@@ -652,7 +716,10 @@
                 'progresVal'] == self.progressInfo['range']:
             self.renderDone.emit()
 
+    def RenderingFailed(self, cmd, error):
+        self.renderingFailed.emit(cmd=cmd, error=error)
 
+
 class Map(object):
 
     def __init__(self, gisrc=None):
@@ -677,8 +744,11 @@
         self.gisrc = gisrc
 
         # generated file for g.pnmcomp output for rendering the map
+        self.legfile = grass.tempfile(create=False) + '.leg'
+        self.tmpdir = os.path.dirname(self.legfile)
         self.mapfile = grass.tempfile(create=False) + '.ppm'
 
+
         # setting some initial env. variables
         if not self.GetWindow():
             sys.stderr.write(_("Trying to recover from default region..."))
@@ -1249,6 +1319,10 @@
                 basefile = os.path.join(base, tempbase) + r'.*'
                 for f in glob.glob(basefile):
                     os.remove(f)
+
+            if layer.GetType() in ('vector'):
+                os.remove(layer._legrow)
+
             list.remove(layer)
 
             self.layerRemoved.emit(layer=layer)
@@ -1413,6 +1487,7 @@
                     string=True))))
         if renderMgr:
             renderMgr.updateProgress.connect(self.renderMgr.ReportProgress)
+            renderMgr.renderingFailed.connect(self.renderMgr.RenderingFailed)
         overlay.forceRender = render
 
         return overlay
@@ -1511,9 +1586,7 @@
 
     def RenderOverlays(self, force):
         """Render overlays only (for nviz)"""
-        for layer in self.overlays:
-            if force or layer.forceRender:
-                layer.Render()
+        self.renderMgr.RenderOverlays(force)
 
     def AbortAllThreads(self):
         """Abort all layers threads e. g. donwloading data"""

Modified: grass/branches/releasebranch_7_2/gui/wxpython/core/utils.py
===================================================================
--- grass/branches/releasebranch_7_2/gui/wxpython/core/utils.py	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/gui/wxpython/core/utils.py	2016-08-21 22:54:25 UTC (rev 69199)
@@ -1022,7 +1022,8 @@
                  'd.to.rast': 'torast',
                  'd.text': 'text',
                  'd.northarrow': 'northarrow',
-                 'd.polar': 'polar'
+                 'd.polar': 'polar',
+                 'd.legend.vect': 'vectleg'
                  }
 ltype2command = {}
 for (cmd, ltype) in command2ltype.items():

Modified: grass/branches/releasebranch_7_2/gui/wxpython/core/workspace.py
===================================================================
--- grass/branches/releasebranch_7_2/gui/wxpython/core/workspace.py	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/gui/wxpython/core/workspace.py	2016-08-21 22:54:25 UTC (rev 69199)
@@ -1517,22 +1517,9 @@
         """Function for writing map elements (barscale, northarrow etc.)
         """
         disp_size = mapdisp.GetMapWindow().GetClientSize()
+        for overlay in mapdisp.decorations.values():
+            self.__writeOverlayParams(disp_size, overlay.cmd, overlay.coords)
 
-        if mapdisp.arrow.IsShown():
-            cmd = mapdisp.arrow.cmd
-            coord_px = mapdisp.arrow.coords
-            self.__writeOverlayParams(disp_size, cmd, coord_px)
-
-        if (mapdisp.legend) and mapdisp.legend.IsShown():
-            cmd = mapdisp.legend.cmd
-            coord_px = mapdisp.legend.coords
-            self.__writeOverlayParams(disp_size, cmd, coord_px)
-
-        if mapdisp.barscale and mapdisp.barscale.IsShown():
-            cmd = mapdisp.barscale.cmd
-            coord_px = mapdisp.barscale.coords
-            self.__writeOverlayParams(disp_size, cmd, coord_px)
-
     def __writeOverlayParams(self, disp_size, cmd, coord_px):
         """
         :param mapdisp: mapdisplay

Modified: grass/branches/releasebranch_7_2/gui/wxpython/gui_core/forms.py
===================================================================
--- grass/branches/releasebranch_7_2/gui/wxpython/gui_core/forms.py	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/gui/wxpython/gui_core/forms.py	2016-08-21 22:54:25 UTC (rev 69199)
@@ -873,7 +873,7 @@
                                           'MapWindow'):
             # display decorations and
             # pressing OK or cancel after setting layer properties
-            if self.task.name in ['d.barscale', 'd.legend', 'd.northarrow', 'd.histogram'] \
+            if self.task.name in ['d.barscale', 'd.legend', 'd.northarrow', 'd.histogram', 'd.text', 'd.legend.vect'] \
                     or len(self.parent.GetLayerInfo(self.layer, key='cmd')) >= 1:
                 self.Hide()
             # canceled layer with nothing set
@@ -1230,7 +1230,7 @@
                         title_txt.SetLabel(title + ':')
                         value = self._getValue(p)
 
-                        if p['name'] == 'icon':  # symbols
+                        if p['name'] in ('icon', 'icon_area', 'icon_line'):  # symbols
                             bitmap = wx.Bitmap(
                                 os.path.join(
                                     globalvar.SYMBDIR,

Modified: grass/branches/releasebranch_7_2/gui/wxpython/gui_core/widgets.py
===================================================================
--- grass/branches/releasebranch_7_2/gui/wxpython/gui_core/widgets.py	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/gui/wxpython/gui_core/widgets.py	2016-08-21 22:54:25 UTC (rev 69199)
@@ -936,10 +936,6 @@
         self.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnPopupMenu)  # wxMSW
         self.Bind(wx.EVT_RIGHT_UP, self.OnPopupMenu)  # wxGTK
 
-    def LoadData(self):
-        """Load data into list"""
-        pass
-
     def OnPopupMenu(self, event):
         """Show popup menu"""
         if self.GetItemCount() < 1:
@@ -1020,10 +1016,12 @@
         if data is None:
             return
 
+        idx = 0
         for item in data:
-            index = self.InsertStringItem(sys.maxsize, str(item[0]))
+            index = self.InsertStringItem(idx, str(item[0]))
             for i in range(1, self.GetColumnCount()):
                 self.SetStringItem(index, i, item[i])
+            idx += 1
 
         # check by default only on one item
         if len(data) == 1 and selectOne:

Modified: grass/branches/releasebranch_7_2/gui/wxpython/lmgr/frame.py
===================================================================
--- grass/branches/releasebranch_7_2/gui/wxpython/lmgr/frame.py	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/gui/wxpython/lmgr/frame.py	2016-08-21 22:54:25 UTC (rev 69199)
@@ -651,10 +651,10 @@
         Also close associated map display
         """
 
-        if UserSettings.Get(group='manager', key='askOnQuit',
-                            subkey='enabled') and self.workspaceChanged:
-            maptree = self.GetLayerTree()
-
+        # save changes in the workspace
+        maptree = self.GetLayerTree()
+        if  self.workspaceChanged and UserSettings.Get(
+                group='manager', key='askOnQuit', subkey='enabled'):
             if self.workspaceFile:
                 message = _("Do you want to save changes in the workspace?")
             else:
@@ -749,19 +749,29 @@
 
         if layertype == 'barscale':
             if len(command) > 1:
-                self.GetMapDisplay().AddBarscale(cmd=command, showDialog=False)
+                self.GetMapDisplay().AddBarscale(cmd=command)
             else:
-                self.GetMapDisplay().AddBarscale(showDialog=True)
+                self.GetMapDisplay().AddBarscale()
         elif layertype == 'rastleg':
             if len(command) > 1:
-                self.GetMapDisplay().AddLegend(cmd=command, showDialog=False)
+                self.GetMapDisplay().AddLegendRast(cmd=command)
             else:
-                self.GetMapDisplay().AddLegend(showDialog=True)
+                self.GetMapDisplay().AddLegendRast()
+        elif layertype == 'vectleg':
+            if len(command) > 1:
+                self.GetMapDisplay().AddLegendVect(cmd=command, showDialog=False)
+            else:
+                self.GetMapDisplay().AddLegendVect(showDialog=True)
         elif layertype == 'northarrow':
             if len(command) > 1:
-                self.GetMapDisplay().AddArrow(cmd=command, showDialog=False)
+                self.GetMapDisplay().AddArrow(cmd=command)
             else:
-                self.GetMapDisplay().AddArrow(showDialog=True)
+                self.GetMapDisplay().AddArrow()
+        elif layertype == 'text':
+            if len(command) > 1:
+                self.GetMapDisplay().AddDtext(cmd=command)
+            else:
+                self.GetMapDisplay().AddDtext()
         elif layertype == 'redraw':
             self.GetMapDisplay().OnRender(None)
         elif layertype == 'export':
@@ -1469,6 +1479,7 @@
             else:
                 maptree.SelectItem(layer, select=False)
 
+
         busy.Destroy()
 
         # set render property again when all layers are loaded
@@ -1479,8 +1490,10 @@
                 # overlay["cmd"][0] name of command e.g. d.barscale, d.legend
                 # overlay["cmd"][1:] parameters and flags
                 if overlay['display'] == i:
+                    if overlay['cmd'][0] == "d.legend.vect":
+                        mapdisplay[i].AddLegendVect(overlay['cmd'])
                     if overlay['cmd'][0] == "d.legend":
-                        mapdisplay[i].AddLegend(overlay['cmd'])
+                        mapdisplay[i].AddLegendRast(overlay['cmd'])
                     if overlay['cmd'][0] == "d.barscale":
                         mapdisplay[i].AddBarscale(overlay['cmd'])
                     if overlay['cmd'][0] == "d.northarrow":

Modified: grass/branches/releasebranch_7_2/gui/wxpython/mapdisp/frame.py
===================================================================
--- grass/branches/releasebranch_7_2/gui/wxpython/mapdisp/frame.py	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/gui/wxpython/mapdisp/frame.py	2016-08-21 22:54:25 UTC (rev 69199)
@@ -43,8 +43,8 @@
 from mapwin.base import MapWindowProperties
 from gui_core.query import QueryDialog, PrepareQueryResults
 from mapwin.buffered import BufferedMapWindow
-from mapwin.decorations import TextLayerDialog, \
-    LegendController, BarscaleController, ArrowController
+from mapwin.decorations import LegendController, BarscaleController, \
+    ArrowController, DtextController, LegendVectController
 from modules.histogram import HistogramFrame
 from wxplot.histogram import HistogramPlotFrame
 from wxplot.profile import ProfileFrame
@@ -128,12 +128,7 @@
 
         # init decoration objects
         self.decorations = {}
-        self.legend = LegendController(self.Map, self._giface)
-        self.barscale = BarscaleController(self.Map, self._giface)
-        self.arrow = ArrowController(self.Map, self._giface)
-        self.decorations[self.legend.id] = self.legend
-        self.decorations[self.barscale.id] = self.barscale
-        self.decorations[self.arrow.id] = self.arrow
+        self._decorationWindows = {}
 
         self.mapWindowProperties.autoRenderChanged.connect(
             lambda value:
@@ -147,12 +142,8 @@
             properties=self.mapWindowProperties, overlays=self.decorations)
         self.MapWindow2D.mapQueried.connect(self.Query)
         self.MapWindow2D.overlayActivated.connect(self._activateOverlay)
-        self.MapWindow2D.overlayHidden.connect(self._hideOverlay)
-        self.MapWindow2D.overlayHidden.connect(self._hideOverlay)
-        for overlay in (self.legend, self.barscale, self.arrow):
-            overlay.overlayChanged.connect(
-                lambda: self.MapWindow2D.UpdateMap(
-                    render=False, renderVector=False))
+        self.MapWindow2D.overlayRemoved.connect(self.RemoveOverlay)
+        self.MapWindow2D.overlayRemoved.connect(self.RemoveOverlay)
         self._setUpMapWindow(self.MapWindow2D)
 
         self.MapWindow2D.mouseHandlerUnregistered.connect(self.ResetPointer)
@@ -254,6 +245,8 @@
 
         #
         self.Map.GetRenderMgr().updateProgress.connect(self.statusbarManager.SetProgress)
+        self.Map.GetRenderMgr().renderingFailed.connect(lambda cmd, error: self._giface.WriteError(
+            _("Failed to run command '{command}'. Details:\n{error}").format(command=' '.join(cmd), error=error)))
 
     def GetMapWindow(self):
         return self.MapWindow
@@ -414,8 +407,7 @@
             self.MapWindow3D.ResetViewHistory()
             self.MapWindow3D.UpdateView(None)
             self.MapWindow3D.overlayActivated.connect(self._activateOverlay)
-            self.MapWindow3D.overlayHidden.connect(self._hideOverlay)
-            self.legend.overlayChanged.connect(self.MapWindow3D.UpdateOverlays)
+            self.MapWindow3D.overlayRemoved.connect(self.RemoveOverlay)
         else:
             self._switchMapWindow(self.MapWindow3D)
             os.environ['GRASS_REGION'] = self.Map.SetRegion(
@@ -432,10 +424,14 @@
 
             self.MapWindow3D.ResetViewHistory()
 
+        # connect signals for updating overlays
+        for overlay in self.decorations.values():
+            overlay.overlayChanged.connect(self.MapWindow3D.UpdateOverlays)
+        self.Map.GetRenderMgr().renderDone.connect(self.MapWindow3D._onUpdateOverlays)
+
         self._giface.updateMap.disconnect(self.MapWindow2D.UpdateMap)
         self._giface.updateMap.connect(self.MapWindow3D.UpdateMap)
         self.MapWindow3D.overlays = self.MapWindow2D.overlays
-        self.MapWindow3D.textdict = self.MapWindow2D.textdict
         # update overlays needs to be called after because getClientSize
         # is called during update and it must give reasonable values
         wx.CallAfter(self.MapWindow3D.UpdateOverlays)
@@ -479,13 +475,16 @@
         self.ending3dMode.emit()
         try:
             self.MapWindow2D.overlays = self.MapWindow3D.overlays
-            self.MapWindow2D.textdict = self.MapWindow3D.textdict
         except AttributeError:
             pass
         # TODO: here we end because self.MapWindow3D is None for a while
         self._giface.updateMap.disconnect(self.MapWindow3D.UpdateMap)
         self._giface.updateMap.connect(self.MapWindow2D.UpdateMap)
-        self.legend.overlayChanged.disconnect(self.MapWindow3D.UpdateOverlays)
+        # disconnect overlays
+        for overlay in self.decorations.values():
+            overlay.overlayChanged.disconnect(self.MapWindow3D.UpdateOverlays)
+        self.Map.GetRenderMgr().renderDone.disconnect(self.MapWindow3D._onUpdateOverlays)
+        self.MapWindow3D.ClearTextures()
 
         self.MapWindow.UpdateMap()
         self._mgr.Update()
@@ -1195,113 +1194,74 @@
 
         :param overlayId: id of overlay
         """
-        if overlayId > 100:
-            self.OnAddText(None)
-        elif overlayId == 0:
-            self.AddLegend(cmd=self.legend.cmd, showDialog=True)
-        elif overlayId == 1:
-            self.AddBarscale(showDialog=True)
-        elif overlayId == 2:
-            self.AddArrow(showDialog=True)
+        dlg = self.decorations[overlayId].dialog
+        if dlg.IsShown():
+            dlg.SetFocus()
+            dlg.Raise()
+        else:
+            dlg.Show()
 
-    def _hideOverlay(self, overlayId):
+    def RemoveOverlay(self, overlayId):
         """Hide overlay.
 
         :param overlayId: id of overlay
         """
-        self.decorations[overlayId].Hide()
+        del self._decorationWindows[self.decorations[overlayId].dialog]
+        self.decorations[overlayId].Remove()
+        del self.decorations[overlayId]
 
-    def AddBarscale(self, cmd=None, showDialog=None):
+    def AddBarscale(self, cmd=None):
         """Handler for scale bar map decoration menu selection."""
         if self.IsPaneShown('3d'):
             self.MapWindow3D.SetDrawScalebar((70, 70))
             return
 
-        if self.barscale.IsShown() and showDialog is None:
-            self.barscale.Hide()
-            return
-
         if cmd:
-            self.barscale.cmd = cmd
+            show = False
+        else:
+            show = True
+            cmd = ['d.barscale']
 
-        if not showDialog:
-            self.barscale.Show()
-            return
-
         # Decoration overlay control dialog
-        if self.barscale.dialog:
-            if self.barscale.dialog.IsShown():
-                self.barscale.dialog.SetFocus()
-                self.barscale.dialog.Raise()
-            else:
-                self.barscale.dialog.Show()
-        else:
-            # If location is latlon, only display north arrow (scale won't work)
-            #        proj = self.Map.projinfo['proj']
-            #        if proj == 'll':
-            #            barcmd = 'd.barscale -n'
-            #        else:
-            #            barcmd = 'd.barscale'
+        GUI(parent=self, giface=self._giface, show=show, modal=False).ParseCommand(
+            cmd, completed=(self.GetOptData, None, None))
 
-            # decoration overlay control dialog
-            GUI(parent=self, giface=self._giface, show=True, modal=False).ParseCommand(
-                self.barscale.cmd, completed=(self.barscale.GetOptData, None, None))
-
         self.MapWindow.mouse['use'] = 'pointer'
 
-    def AddLegend(self, cmd=None, showDialog=None):
-        """Handler for legend map decoration menu selection."""
-        if self.legend.IsShown() and showDialog is None:
-            self.legend.Hide()
-            return
+    def AddLegendRast(self, cmd=None):
+        """Handler for legend raster map decoration menu selection."""
+
         if cmd:
-            self.legend.cmd = cmd
+            show = False
         else:
+            show = True
+            cmd = ['d.legend']
             layers = self._giface.GetLayerList().GetSelectedLayers()
             for layer in layers:
                 if layer.type == 'raster':
-                    isMap = False
-                    # replace map
-                    for i, legendParam in enumerate(self.legend.cmd[1:]):
-                        idx = i + 1
-                        param_val = legendParam.split('=')
-                        if len(param_val) != 2:
-                            continue
-                        param, val = param_val
-                        if param == 'raster':
-                            self.legend.cmd[idx] = 'raster={rast}'.format(
-                                rast=layer.maplayer.name)
-                            isMap = True
-                        elif param in ('use', 'range'):
-                            # clear range or use to avoid problems
-                            del self.legend.cmd[idx]
-
-                    if not isMap:  # for the first time
-                        self.legend.cmd.append(
-                            'raster=%s' %
-                            layer.maplayer.name)
+                    cmd.append('raster={rast}'.format(rast=layer.maplayer.name))
                     break
 
-        if not showDialog and self.legend.CmdIsValid():
-            self.legend.Show()
-            return
+        GUI(parent=self, giface=self._giface, show=show, modal=False).ParseCommand(
+            cmd, completed=(self.GetOptData, None, None))
 
-        # Decoration overlay control dialog
-        # always create new one to avoid problem when switching between maps
-        if self.legend.dialog:
-            if self.legend.dialog.IsShown():
-                self.legend.dialog.SetFocus()
-                self.legend.dialog.Raise()
-            else:
-                self.legend.dialog.Destroy()
-                self.legend.dialog = None
-        if not self.legend.dialog:
-            GUI(parent=self, giface=self._giface, show=True, modal=False).ParseCommand(
-                self.legend.cmd, completed=(self.legend.GetOptData, None, None))
+        self.MapWindow.mouse['use'] = 'pointer'
 
+    def AddLegendVect(self, cmd=None, showDialog=None):
+        """Handler for legend vector map decoration menu selection."""
+
+        if cmd:
+            show = False
+        else:
+            show = True
+            cmd = ['d.legend.vect']
+
+        GUI(parent=self, giface=self._giface, show=show, modal=False).ParseCommand(
+            cmd, completed=(self.GetOptData, None, None))
+
         self.MapWindow.mouse['use'] = 'pointer'
 
-    def AddArrow(self, cmd=None, showDialog=None):
+    def AddArrow(self, cmd=None):
         """Handler for north arrow menu selection."""
         if self.IsPaneShown('3d'):
             # here was opening of appearance page of nviz notebook
@@ -1310,96 +1270,70 @@
             self.MapWindow3D.SetDrawArrow((70, 70))
             return
 
-        if self.arrow.IsShown() and showDialog is None:
-            self.arrow.Hide()
-            return
         if cmd:
-            self.arrow.cmd = cmd
+            show = False
+        else:
+            show = True
+            cmd = ['d.northarrow']
 
-        if not showDialog:
-            self.arrow.Show()
-            return
-
         # Decoration overlay control dialog
-        if self.arrow.dialog:
-            if self.arrow.dialog.IsShown():
-                self.arrow.dialog.SetFocus()
-                self.arrow.dialog.Raise()
-            else:
-                self.arrow.dialog.Show()
-        else:
-            GUI(parent=self, giface=self._giface, show=True, modal=False).ParseCommand(
-                self.arrow.cmd, completed=(self.arrow.GetOptData, None, None))
+        GUI(parent=self, giface=self._giface, show=show, modal=False).ParseCommand(
+            cmd, completed=(self.GetOptData, None, None))
 
         self.MapWindow.mouse['use'] = 'pointer'
 
-    def OnAddText(self, event):
-        """Handler for text decoration menu selection.
-        """
-        if self.MapWindow.dragid > -1:
-            id = self.MapWindow.dragid
-            self.MapWindow.dragid = -1
+    def AddDtext(self, cmd=None):
+        """Handler for d.text menu selection."""
+        if cmd:
+            show = False
         else:
-            # index for overlay layer in render
-            if len(self.MapWindow.textdict.keys()) > 0:
-                id = max(self.MapWindow.textdict.keys()) + 1
-            else:
-                id = 101
+            show = True
+            cmd = ['d.text']
 
-        self.dialogs['text'] = TextLayerDialog(parent=self, ovlId=id,
-                                               title=_('Add text layer'),
-                                               size=(400, 200))
-        self.dialogs['text'].CenterOnParent()
+        # Decoration overlay control dialog
+        GUI(parent=self, giface=self._giface, show=show, modal=False).ParseCommand(
+            cmd, completed=(self.GetOptData, None, None))
 
-        # If OK button pressed in decoration control dialog
-        if self.dialogs['text'].ShowModal() == wx.ID_OK:
-            text = self.dialogs['text'].GetValues()['text']
-            active = self.dialogs['text'].GetValues()['active']
+        self.MapWindow.mouse['use'] = 'pointer'
 
-            # delete object if it has no text or is not active
-            if text == '' or active == False:
-                try:
-                    self.MapWindow2D.pdc.ClearId(id)
-                    self.MapWindow2D.pdc.RemoveId(id)
-                    del self.MapWindow.textdict[id]
-                    if self.IsPaneShown('3d'):
-                        self.MapWindow3D.UpdateOverlays()
-                        self.MapWindow.UpdateMap()
-                    else:
-                        self.MapWindow2D.UpdateMap(
-                            render=False, renderVector=False)
-                except:
-                    pass
-                return
+    def GetOptData(self, dcmd, layer, params, propwin):
+        """Called after options are set through module dialog.
 
-            self.MapWindow.textdict[id] = self.dialogs['text'].GetValues()
+        :param dcmd: resulting command
+        :param layer: not used
+        :param params: module parameters (not used)
+        :param propwin: dialog window
+        """
 
-            if self.IsPaneShown('3d'):
-                self.MapWindow3D.UpdateOverlays()
-                self.MapWindow3D.UpdateMap()
-            else:
-                self.MapWindow2D.pdc.ClearId(id)
-                self.MapWindow2D.pdc.SetId(id)
-                self.MapWindow2D.UpdateMap(render=False, renderVector=False)
+        if not dcmd:
+            return
+        if propwin in self._decorationWindows:
+            overlay = self._decorationWindows[propwin]
+        else:
+            cmd = dcmd[0]
+            if cmd == 'd.northarrow':
+                overlay = ArrowController(self.Map, self._giface)
+            elif cmd == 'd.barscale':
+                overlay = BarscaleController(self.Map, self._giface)
+            elif cmd == 'd.legend':
+                overlay = LegendController(self.Map, self._giface)
+            elif cmd == 'd.legend.vect':
+                overlay = LegendVectController(self.Map, self._giface)
+            elif cmd == 'd.text':
+                overlay = DtextController(self.Map, self._giface)
 
-        self.MapWindow.mouse['use'] = 'pointer'
+            self.decorations[overlay.id] = overlay
+            overlay.overlayChanged.connect(lambda: self.MapWindow2D.UpdateMap(
+                                           render=False, renderVector=False))
+            if self.MapWindow3D:
+                overlay.overlayChanged.connect(self.MapWindow3D.UpdateOverlays)
 
-    def GetOptData(self, dcmd, type, params, propwin):
-        """Callback method for decoration overlay command generated by
-        dialog created in menuform.py
-        """
-        # Reset comand and rendering options in render.Map. Always render decoration.
-        # Showing/hiding handled by PseudoDC
-        self.Map.ChangeOverlay(
-            ovltype=type,
-            type='overlay',
-            name='',
-            command=dcmd,
-            active=True,
-            render=False)
-        self.params[type] = params
-        self.propwin[type] = propwin
+            overlay.dialog = propwin
+            self._decorationWindows[propwin] = overlay
 
+        overlay.cmd = dcmd
+        overlay.Show()
+
     def OnZoomToMap(self, event):
         """Set display extents to match selected raster (including
         NULLs) or vector map.

Modified: grass/branches/releasebranch_7_2/gui/wxpython/mapdisp/main.py
===================================================================
--- grass/branches/releasebranch_7_2/gui/wxpython/mapdisp/main.py	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/gui/wxpython/mapdisp/main.py	2016-08-21 22:54:25 UTC (rev 69199)
@@ -83,6 +83,7 @@
         # TODO temporary solution, layer managment by different tools in GRASS
         # should be resovled
         self.ownedLayers = []
+        self.oldOverlays = []
 
         if mapfile:
             self.mapfileCmd = mapfile
@@ -103,6 +104,15 @@
 
         self.renderMgr = RenderMapMgr(self)
 
+        # update legend file variable with the one d.mon uses
+        with open(monFile['env'], 'r') as f:
+            lines = f.readlines()
+            for line in lines:
+                if 'GRASS_LEGEND_FILE' in line:
+                    legfile = line.split('=', 1)[1].strip()
+                    self.renderMgr.UpdateRenderEnv({'GRASS_LEGEND_FILE': legfile})
+                    break
+
     def GetLayersFromCmdFile(self):
         """Get list of map layers from cmdfile
         """
@@ -141,6 +151,12 @@
                     self.query.emit(ltype=utils.split(dWhatCmd)[
                                     0].split('.')[-1], maps=maps)
                     return
+            else:
+                # clean overlays after erase
+                self.oldOverlays = []
+                overlays = self._giface.GetMapDisplay().decorations.keys()
+                for each in overlays:
+                    self._giface.GetMapDisplay().RemoveOverlay(each)
 
             existingLayers = self.GetListOfLayers()
 
@@ -175,28 +191,36 @@
                                                  layerType=ltype)[0]
 
                 args = {}
-                if ltype in ('barscale', 'rastleg', 'northarrow'):
-                    classLayer = Overlay
+
+                if ltype in ('barscale', 'rastleg', 'northarrow', 'text', 'vectleg'):
+                    # TODO: this is still not optimal
+                    # it is there to prevent adding the same overlay multiple times
+                    if cmd in self.oldOverlays:
+                        continue
                     if ltype == 'rastleg':
-                        args['id'] = 0
+                        self._giface.GetMapDisplay().AddLegendRast(cmd=cmd)
                     elif ltype == 'barscale':
-                        args['id'] = 1
-                    else:
-                        args['id'] = 2
-                else:
-                    classLayer = MapLayer
-                    args['ltype'] = ltype
+                        self._giface.GetMapDisplay().AddBarscale(cmd=cmd)
+                    elif ltype == 'northarrow':
+                        self._giface.GetMapDisplay().AddArrow(cmd=cmd)
+                    elif ltype == 'text':
+                        self._giface.GetMapDisplay().AddDtext(cmd=cmd)
+                    elif ltype == 'vectleg':
+                        self._giface.GetMapDisplay().AddLegendVect(cmd=cmd)
+                    self.oldOverlays.append(cmd)
+                    continue
 
-                mapLayer = classLayer(
-                    name=name,
-                    cmd=cmd,
-                    Map=None,
-                    hidden=True,
-                    render=False,
-                    mapfile=mapFile,
-                    **args)
-                mapLayer.GetRenderMgr().updateProgress.connect(
-                    self.GetRenderMgr().ReportProgress)
+                classLayer = MapLayer
+                args['ltype'] = ltype
+
+                mapLayer = classLayer(name=name,
+                                      cmd=cmd,
+                                      Map=None,
+                                      hidden=True,
+                                      render=False,
+                                      mapfile=mapFile,
+                                      **args)
+                mapLayer.GetRenderMgr().updateProgress.connect(self.GetRenderMgr().ReportProgress)
                 if render_env:
                     mapLayer.GetRenderMgr().UpdateRenderEnv(render_env)
                     render_env = dict()
@@ -316,7 +340,23 @@
     def __init__(self, map, giface):
         self._map = map
         self._giface = giface
+        self._index = 0
 
+    def __len__(self):
+        return len(self._map.GetListOfLayers())
+
+    def __iter__(self):
+        return self
+
+    def next(self):
+        items = self._map.GetListOfLayers()
+        try:
+            result = items[self._index]
+        except IndexError:
+            raise StopIteration
+        self._index += 1
+        return result
+
     def GetSelectedLayers(self, checkedOnly=True):
         # hidden and selected vs checked and selected
         items = self._map.GetListOfLayers()

Modified: grass/branches/releasebranch_7_2/gui/wxpython/mapdisp/toolbars.py
===================================================================
--- grass/branches/releasebranch_7_2/gui/wxpython/mapdisp/toolbars.py	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/gui/wxpython/mapdisp/toolbars.py	2016-08-21 22:54:25 UTC (rev 69199)
@@ -32,11 +32,13 @@
                        label=_('Select vector feature(s)'),
                        desc=_('Select features interactively from vector map')),
     'addBarscale': MetaIcon(img='scalebar-add',
-                            label=_('Show/hide scale bar')),
-    'addLegend': MetaIcon(img='legend-add',
-                          label=_('Show/hide legend')),
+                            label=_('Add scale bar')),
+    'addRasterLegend': MetaIcon(img='legend-add',
+                          label=_('Add raster legend')),
+    'addVectorLegend': MetaIcon(img='legend-add',
+                                label=_('Add vector legend')),
     'addNorthArrow': MetaIcon(img='north-arrow-add',
-                              label=_('Show/hide north arrow')),
+                              label=_('Add north arrow')),
     'analyze': MetaIcon(img='layer-raster-analyze',
                         label=_('Analyze map'),
                         desc=_('Measuring, profiling, histogramming, ...')),
@@ -49,7 +51,7 @@
     'scatter': MetaIcon(img='layer-raster-profile',
                         label=_("Create bivariate scatterplot of raster maps")),
     'addText': MetaIcon(img='text-add',
-                        label=_('Add text layer')),
+                        label=_('Add text')),
     'histogram': MetaIcon(img='layer-raster-histogram',
                           label=_('Create histogram of raster map')),
     'vnet': MetaIcon(img='vector-tools',
@@ -267,14 +269,16 @@
         """Decorations overlay menu
         """
         self._onMenu(
-            ((MapIcons["addLegend"],
-              lambda evt: self.parent.AddLegend()),
+            ((MapIcons["addRasterLegend"],
+              lambda evt: self.parent.AddLegendRast()),
+             (MapIcons["addVectorLegend"],
+              lambda evt: self.parent.AddLegendVect()),
              (MapIcons["addBarscale"],
               lambda evt: self.parent.AddBarscale()),
-                (MapIcons["addNorthArrow"],
-                 lambda evt: self.parent.AddArrow()),
-                (MapIcons["addText"],
-                 self.parent.OnAddText)))
+             (MapIcons["addNorthArrow"],
+              lambda evt: self.parent.AddArrow()),
+             (MapIcons["addText"],
+              lambda evt: self.parent.AddDtext())))
 
     def ExitToolbars(self):
         if self.parent.GetToolbar('vdigit'):

Modified: grass/branches/releasebranch_7_2/gui/wxpython/mapwin/base.py
===================================================================
--- grass/branches/releasebranch_7_2/gui/wxpython/mapwin/base.py	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/gui/wxpython/mapwin/base.py	2016-08-21 22:54:25 UTC (rev 69199)
@@ -125,7 +125,7 @@
         # emitted after double click in pointer mode on legend, text, scalebar
         self.overlayActivated = Signal('MapWindow.overlayActivated')
         # emitted when overlay should be hidden
-        self.overlayHidden = Signal('MapWindow.overlayHidden')
+        self.overlayRemoved = Signal('MapWindow.overlayRemoved')
 
         # mouse attributes -- position on the screen, begin and end of
         # dragging, and type of drawing

Modified: grass/branches/releasebranch_7_2/gui/wxpython/mapwin/buffered.py
===================================================================
--- grass/branches/releasebranch_7_2/gui/wxpython/mapwin/buffered.py	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/gui/wxpython/mapwin/buffered.py	2016-08-21 22:54:25 UTC (rev 69199)
@@ -160,15 +160,12 @@
         self.img = None   # wx.Image object (self.mapfile)
         # decoration overlays
         self.overlays = overlays
-        self._overlayNames = {
-            0: _("legend"),
-            1: _("scale bar"),
-            2: _("north arrow")}
         # images and their PseudoDC ID's for painting and dragging
         self.imagedict = {}
         self.select = {}      # selecting/unselecting decorations for dragging
         self.textdict = {}    # text, font, and color indexed by id
 
+
         # zoom objects
         self.zoomhistory = []  # list of past zoom extents
         self.currzoom = 0  # current set of extents in zoom history being used
@@ -274,27 +271,20 @@
 
         pos = self.ScreenToClient(event.GetPosition())
         idlist = self.pdc.FindObjects(pos[0], pos[1], self.hitradius)
-        separator = True
-        if idlist and idlist[0] in (0, 1, 2):  # legend, scale bar, north arrow
-            if separator:
-                menu.AppendSeparator()
-                separator = False
-            self._hide = wx.NewId()
+        if self.overlays and idlist and [i for i in idlist if i in self.overlays.keys()]:  # legend, scale bar, north arrow, dtext
+            menu.AppendSeparator()
+            removeId = wx.NewId()
             self.Bind(wx.EVT_MENU,
-                      lambda evt: self.overlayHidden.emit(overlayId=idlist[0]),
-                      id=self._hide)
-            menu.Append(
-                self._hide,
-                _("Hide {overlay}").format(
-                    overlay=self._overlayNames[
-                        idlist[0]]))
+                      lambda evt: self.overlayRemoved.emit(overlayId=idlist[0]),
+                      id=removeId)
+            menu.Append(removeId, self.overlays[idlist[0]].removeLabel)
 
-            if idlist[0] == 0:
-                self._resizeLegend = wx.NewId()
+            if self.overlays[idlist[0]].name == 'legend':
+                resizeLegendId = wx.NewId()
                 self.Bind(wx.EVT_MENU,
                           lambda evt: self.overlays[idlist[0]].StartResizing(),
-                          id=self._resizeLegend)
-                menu.Append(self._resizeLegend, _("Resize legend"))
+                          id=resizeLegendId)
+                menu.Append(resizeLegendId, _("Resize legend"))
         self.PopupMenu(menu)
         menu.Destroy()
 
@@ -1071,20 +1061,22 @@
 
         if isinstance(r, list):
             r = wx.Rect(r[0], r[1], r[2], r[3])
-        if id > 100:  # text dragging
+        if id in self.textdict:  # text dragging
             rtop = (r[0], r[1] - r[3], r[2], r[3])
             r = r.Union(rtop)
             rleft = (r[0] - r[2], r[1], r[2], r[3])
             r = r.Union(rleft)
+
         self.pdc.TranslateId(id, dx, dy)
 
         r2 = self.pdc.GetIdBounds(id)
         if isinstance(r2, list):
             r2 = wx.Rect(r[0], r[1], r[2], r[3])
-        if id > 100:  # text
+        if id in self.textdict:  # text
             self.textdict[id]['bbox'] = r2
             self.textdict[id]['coords'][0] += dx
             self.textdict[id]['coords'][1] += dy
+
         r = r.Union(r2)
         r.Inflate(4, 4)
         self.RefreshRect(r, False)
@@ -1514,6 +1506,7 @@
                 idlist.remove(99)
             if idlist != []:
                 self.dragid = idlist[0]  # drag whatever is on top
+
         else:
             pass
         coords = self.Pixel2Cell(self.mouse['begin'])
@@ -1561,13 +1554,12 @@
                 self.dragid >= 0):
             # end drag of overlay decoration
 
-            if self.dragid < 99 and self.dragid in self.overlays:
+            if self.overlays and self.dragid in self.overlays:
                 self.overlays[
                     self.dragid].coords = self.pdc.GetIdBounds(
                     self.dragid)
-            elif self.dragid > 100 and self.dragid in self.textdict:
-                self.textdict[self.dragid][
-                    'bbox'] = self.pdc.GetIdBounds(self.dragid)
+            elif self.dragid in self.textdict:
+                self.textdict[self.dragid]['bbox'] = self.pdc.GetIdBounds(self.dragid)
             else:
                 pass
             self.dragid = None
@@ -1689,6 +1681,13 @@
                 self.digit:
             self._onMouseMoving(event)
 
+        pos = event.GetPosition()
+        idlist = self.pdc.FindObjects(pos[0], pos[1], self.hitradius)
+        if self.overlays and idlist and [i for i in idlist if i in self.overlays.keys()]:  # legend, scale bar, north arrow, dtext
+            self.SetToolTipString("Double click in Pointer mode to set object"
+                                  " properties,\nright click to remove")
+        else:
+            self.SetToolTip(None)
         event.Skip()
 
     def OnCopyCoordinates(self, event):

Modified: grass/branches/releasebranch_7_2/gui/wxpython/mapwin/decorations.py
===================================================================
--- grass/branches/releasebranch_7_2/gui/wxpython/mapwin/decorations.py	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/gui/wxpython/mapwin/decorations.py	2016-08-21 22:54:25 UTC (rev 69199)
@@ -22,7 +22,6 @@
 from core.utils import _
 
 import wx
-from wx.lib.expando import ExpandoTextCtrl, EVT_ETC_LAYOUT_NEEDED
 
 from grass.pydispatch.signal import Signal
 try:
@@ -32,12 +31,6 @@
     hasPIL = False
 
 
-class OverlayId:
-    legendId = 0
-    barscaleId = 1
-    arrowId = 2
-
-
 class OverlayController(object):
 
     """Base class for decorations (barscale, legend) controller."""
@@ -52,7 +45,8 @@
         self._defaultAt = ''
         self._cmd = None   # to be set by user
         self._name = None  # to be defined by subclass
-        self._id = None    # to be defined by subclass
+        self._removeLabel = None  # to be defined by subclass
+        self._id = wx.NewId()
         self._dialog = None
 
         # signals that overlay or its visibility changed
@@ -97,6 +91,11 @@
 
     name = property(fget=GetName)
 
+    def GetRemoveLabel(self):
+        return self._removeLabel
+
+    removeLabel = property(fget=GetRemoveLabel)
+
     def GetId(self):
         return self._id
 
@@ -145,22 +144,12 @@
             self._overlay.SetActive(False)
         self.overlayChanged.emit()
 
-    def GetOptData(self, dcmd, layer, params, propwin):
-        """Called after options are set through module dialog.
+    def Remove(self):
+        if self._dialog:
+            self._dialog.Destroy()
+        self._renderer.DeleteOverlay(self._overlay)
+        self.overlayChanged.emit()
 
-        :param dcmd: resulting command
-        :param layer: not used
-        :param params: module parameters (not used)
-        :param propwin: dialog window
-        """
-        if not dcmd:
-            return
-
-        self._cmd = dcmd
-        self._dialog = propwin
-
-        self.Show()
-
     def _add(self):
         self._overlay = self._renderer.AddOverlay(
             id=self._id,
@@ -189,6 +178,7 @@
                     "Please install Python Imaging Library (PIL)\n"
                     "for better control of legend and other decorations."))
             return 0, 0
+
         for param in self._cmd:
             if not param.startswith('at'):
                 continue
@@ -199,12 +189,35 @@
             return x, y
 
 
+class DtextController(OverlayController):
+
+    def __init__(self, renderer, giface):
+        OverlayController.__init__(self, renderer, giface)
+        self._name = 'text'
+        self._removeLabel = _("Remove text")
+        self._defaultAt = 'at=50,50'
+        self._cmd = ['d.text', self._defaultAt]
+
+    def CmdIsValid(self):
+        inputs = 0
+        for param in self._cmd[1:]:
+            param = param.split('=')
+            if len(param) == 1:
+                inputs += 1
+            else:
+                if param[0] == 'text' and len(param) == 2:
+                    inputs += 1
+        if inputs >= 1:
+            return True
+        return False
+
+
 class BarscaleController(OverlayController):
 
     def __init__(self, renderer, giface):
         OverlayController.__init__(self, renderer, giface)
-        self._id = OverlayId.barscaleId
         self._name = 'barscale'
+        self._removeLabel = _("Remove scale bar")
         # different from default because the reference point is not in the
         # middle
         self._defaultAt = 'at=0,98'
@@ -215,22 +228,34 @@
 
     def __init__(self, renderer, giface):
         OverlayController.__init__(self, renderer, giface)
-        self._id = OverlayId.arrowId
         self._name = 'arrow'
+        self._removeLabel = _("Remove north arrow")
         # different from default because the reference point is not in the
         # middle
         self._defaultAt = 'at=85.0,25.0'
         self._cmd = ['d.northarrow', self._defaultAt]
 
 
+class LegendVectController(OverlayController):
+
+    def __init__(self, renderer, giface):
+        OverlayController.__init__(self, renderer, giface)
+        self._name = 'vectleg'
+        self._removeLabel = _("Remove vector legend")
+        # different from default because the reference point is not in the
+        # middle
+        self._defaultAt = 'at=20.0,80.0'
+        self._cmd = ['d.legend.vect', self._defaultAt]
+
+
 class LegendController(OverlayController):
 
     def __init__(self, renderer, giface):
         OverlayController.__init__(self, renderer, giface)
-        self._id = OverlayId.legendId
         self._name = 'legend'
-        # TODO: synchronize with d.legend?
-        self._defaultAt = 'at=5,50,7,10'
+        self._removeLabel = _("Remove legend")
+        # default is in the center to avoid trimmed legend on the edge
+        self._defaultAt = 'at=5,50,47,50'
         self._cmd = ['d.legend', self._defaultAt]
 
     def GetPlacement(self, screensize):
@@ -243,8 +268,12 @@
         for param in self._cmd:
             if not param.startswith('at'):
                 continue
-            b, t, l, r = [float(number) for number in param.split(
-                '=')[1].split(',')]  # pylint: disable-msg=W0612
+            # if the at= is the default, we will move the legend from the center to bottom left
+            if param == self._defaultAt:
+                b, t, l, r = 5, 50, 7, 10
+            else:
+                b, t, l, r = [float(number) for number in param.split(
+                    '=')[1].split(',')]  # pylint: disable-msg=W0612
             x = int((l / 100.) * screensize[0])
             y = int((1 - t / 100.) * screensize[1])
 
@@ -313,202 +342,3 @@
         self._giface.GetMapDisplay().GetMapToolbar().SelectDefault()
         # redraw
         self.overlayChanged.emit()
-
-
-class TextLayerDialog(wx.Dialog):
-    """!Controls setting options and displaying/hiding map overlay decorations
-    """
-
-    def __init__(self, parent, ovlId, title, name='text', size=wx.DefaultSize,
-                 style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER):
-
-        wx.Dialog.__init__(
-            self,
-            parent=parent,
-            id=wx.ID_ANY,
-            title=title,
-            style=style,
-            size=size)
-
-        self.ovlId = ovlId
-        self.parent = parent
-
-        if self.ovlId in self.parent.MapWindow.textdict.keys():
-            self.currText = self.parent.MapWindow.textdict[self.ovlId]['text']
-            self.currFont = self.parent.MapWindow.textdict[self.ovlId]['font']
-            self.currClr = self.parent.MapWindow.textdict[self.ovlId]['color']
-            self.currRot = self.parent.MapWindow.textdict[
-                self.ovlId]['rotation']
-            self.currCoords = self.parent.MapWindow.textdict[
-                self.ovlId]['coords']
-            self.currBB = self.parent.MapWindow.textdict[self.ovlId]['bbox']
-        else:
-            self.currClr = wx.BLACK
-            self.currText = ''
-            self.currFont = self.GetFont()
-            self.currRot = 0.0
-            self.currCoords = [10, 10]
-            self.currBB = wx.Rect()
-
-        self.sizer = wx.BoxSizer(wx.VERTICAL)
-        box = wx.GridBagSizer(vgap=5, hgap=5)
-
-        # show/hide
-        self.chkbox = wx.CheckBox(parent=self, id=wx.ID_ANY,
-                                  label=_('Show text object'))
-        if self.parent.Map.GetOverlay(self.ovlId) is None:
-            self.chkbox.SetValue(True)
-        else:
-            self.chkbox.SetValue(
-                self.parent.MapWindow.overlays[
-                    self.ovlId]['layer'].IsActive())
-        box.Add(item=self.chkbox, span=(1, 2),
-                pos=(0, 0))
-
-        # text entry
-        box.Add(
-            item=wx.StaticText(
-                parent=self,
-                id=wx.ID_ANY,
-                label=_("Text:")),
-            flag=wx.ALIGN_CENTER_VERTICAL,
-            pos=(
-                1,
-                0))
-
-        self.textentry = ExpandoTextCtrl(
-            parent=self, id=wx.ID_ANY, value="", size=(300, -1))
-        self.textentry.SetFont(self.currFont)
-        self.textentry.SetForegroundColour(self.currClr)
-        self.textentry.SetValue(self.currText)
-        # get rid of unneeded scrollbar when text box first opened
-        self.textentry.SetClientSize((300, -1))
-
-        box.Add(item=self.textentry,
-                flag=wx.EXPAND,
-                pos=(1, 1))
-
-        # rotation
-        box.Add(
-            item=wx.StaticText(
-                parent=self,
-                id=wx.ID_ANY,
-                label=_("Rotation:")),
-            flag=wx.ALIGN_CENTER_VERTICAL,
-            pos=(
-                2,
-                0))
-        self.rotation = wx.SpinCtrl(
-            parent=self, id=wx.ID_ANY, value="", pos=(
-                30, 50), size=(
-                75, -1), style=wx.SP_ARROW_KEYS)
-        self.rotation.SetRange(-360, 360)
-        self.rotation.SetValue(int(self.currRot))
-        box.Add(item=self.rotation,
-                flag=wx.ALIGN_RIGHT,
-                pos=(2, 1))
-
-        # font
-        box.Add(
-            item=wx.StaticText(
-                parent=self,
-                id=wx.ID_ANY,
-                label=_("Font:")),
-            flag=wx.ALIGN_CENTER_VERTICAL,
-            pos=(
-                3,
-                0))
-        fontbtn = wx.Button(parent=self, id=wx.ID_ANY, label=_("Set font"))
-        box.Add(item=fontbtn,
-                flag=wx.ALIGN_RIGHT,
-                pos=(3, 1))
-
-        box.AddGrowableCol(1)
-        box.AddGrowableRow(1)
-        self.sizer.Add(item=box, proportion=1,
-                       flag=wx.ALL | wx.EXPAND, border=10)
-
-        # note
-        box = wx.BoxSizer(wx.HORIZONTAL)
-        label = wx.StaticText(
-            parent=self, id=wx.ID_ANY, label=_(
-                "Drag text with mouse in pointer mode "
-                "to position.\nDouble-click to change options"))
-        box.Add(item=label, proportion=0,
-                flag=wx.ALIGN_CENTRE | wx.ALL, border=5)
-        self.sizer.Add(
-            item=box, proportion=0, flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL |
-            wx.ALIGN_CENTER | wx.ALL, border=5)
-
-        line = wx.StaticLine(parent=self, id=wx.ID_ANY,
-                             size=(20, -1), style=wx.LI_HORIZONTAL)
-        self.sizer.Add(item=line, proportion=0,
-                       flag=wx.EXPAND | wx.ALIGN_CENTRE | wx.ALL, border=5)
-
-        btnsizer = wx.StdDialogButtonSizer()
-
-        btn = wx.Button(parent=self, id=wx.ID_OK)
-        btn.SetDefault()
-        btnsizer.AddButton(btn)
-
-        btn = wx.Button(parent=self, id=wx.ID_CANCEL)
-        btnsizer.AddButton(btn)
-        btnsizer.Realize()
-
-        self.sizer.Add(item=btnsizer, proportion=0,
-                       flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)
-
-        self.SetSizer(self.sizer)
-        self.sizer.Fit(self)
-
-        # bindings
-        self.Bind(EVT_ETC_LAYOUT_NEEDED, self.OnRefit, self.textentry)
-        self.Bind(wx.EVT_BUTTON, self.OnSelectFont, fontbtn)
-        self.Bind(wx.EVT_TEXT, self.OnText, self.textentry)
-        self.Bind(wx.EVT_SPINCTRL, self.OnRotation, self.rotation)
-
-        self.SetMinSize((400, 230))
-
-    def OnRefit(self, event):
-        """Resize text entry to match text"""
-        self.sizer.Fit(self)
-
-    def OnText(self, event):
-        """Change text string"""
-        self.currText = event.GetString()
-
-    def OnRotation(self, event):
-        """Change rotation"""
-        self.currRot = event.GetInt()
-
-        event.Skip()
-
-    def OnSelectFont(self, event):
-        """Change font"""
-        data = wx.FontData()
-        data.EnableEffects(True)
-        data.SetColour(self.currClr)         # set colour
-        data.SetInitialFont(self.currFont)
-
-        dlg = wx.FontDialog(self, data)
-
-        if dlg.ShowModal() == wx.ID_OK:
-            data = dlg.GetFontData()
-            self.currFont = data.GetChosenFont()
-            self.currClr = data.GetColour()
-
-            self.textentry.SetFont(self.currFont)
-            self.textentry.SetForegroundColour(self.currClr)
-
-            self.Layout()
-
-        dlg.Destroy()
-
-    def GetValues(self):
-        """Get text properties"""
-        return {'text': self.currText,
-                'font': self.currFont,
-                'color': self.currClr,
-                'rotation': self.currRot,
-                'coords': self.currCoords,
-                'active': self.chkbox.IsChecked()}

Modified: grass/branches/releasebranch_7_2/gui/wxpython/nviz/mapwindow.py
===================================================================
--- grass/branches/releasebranch_7_2/gui/wxpython/nviz/mapwindow.py	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/gui/wxpython/nviz/mapwindow.py	2016-08-21 22:54:25 UTC (rev 69199)
@@ -160,7 +160,6 @@
         self.imagelist = []
         self.overlay = wx.Overlay()
         #self.pdc = wx.PseudoDC()
-        self.textdict = {}
         self.dragid = -1
         self.hitradius = 5
         # layer manager toolwindow
@@ -473,125 +472,36 @@
             if texture.IsActive():
                 texture.Draw()
 
-    def GetLegendRect(self):
-        """Estimates legend size for dragging"""
-        size = None
-        if 0 in self.overlays:
-            for param in self.overlays[0].cmd[1:]:
-                if param.startswith("at="):
-                    size = map(float, param.split("=")[-1].split(','))
-                    break
-        if size:
-            wSize = self.GetClientSizeTuple()
-            x, y = size[
-                2] / 100. * wSize[0], wSize[1] - (size[1] / 100. * wSize[1])
-            x += self.overlays[1].coords[0]
-            y += self.overlays[1].coords[1]
-            w = (size[3] - size[2]) / 100. * wSize[0]
-            h = (size[1] - size[0]) / 100. * wSize[1]
-
-            rect = wx.Rect(x, y, w, h)
-            return rect
-
-        return wx.Rect()
-
-    def DrawTextImage(self, textDict, relCoords):
-        """Draw overlay text"""
-        bmp = wx.EmptyBitmap(textDict['bbox'][2], textDict['bbox'][3])
-        memDC = wx.MemoryDC()
-        memDC.SelectObject(bmp)
-
-        mask = self.view['background']['color']
-        if mask == textDict['color']:
-            mask = wx.WHITE
-        memDC.SetBackground(wx.Brush(mask))
-        memDC.Clear()
-        memDC.SetFont(textDict['font'])
-        memDC.SetTextForeground(textDict['color'])
-        if textDict['rotation'] == 0:
-            memDC.DrawText(textDict['text'], 0, 0)
-        else:
-            memDC.DrawRotatedText(textDict['text'], relCoords[0], relCoords[1],
-                                  textDict['rotation'])
-        bmp.SetMaskColour(mask)
-        memDC.DrawBitmap(bmp, 0, 0, 1)
-
-        filename = grass.tempfile(create=False) + '.png'
-        bmp.SaveFile(filename, wx.BITMAP_TYPE_PNG)
-        memDC.SelectObject(wx.NullBitmap)
-
-        return filename
-
     def UpdateOverlays(self):
-        """Converts rendered overlay files and text labels to wx.Image
-            and then to textures so that they can be rendered by OpenGL.
-            Updates self.imagelist"""
+        """Renders overlays (legend, text).
+        Once this is done _onUpdateOverlays is called"""
         self.Map.ChangeMapSize(self.GetClientSize())
         self.Map.RenderOverlays(force=True)
 
-        # delete textures
-        for texture in self.imagelist:
-            # inactive overlays, remove text labels
-            if texture.GetId() < 100:
-                if not self.overlays[texture.GetId()].IsShown():
-                    texture.SetActive(False)
-                else:
-                    texture.SetActive(True)
-            else:  # text label
-                if texture.GetId() not in self.textdict:
-                    self.imagelist.remove(texture)
-
-        # update images (only legend so far)
+    def _onUpdateOverlays(self):
+        """Converts rendered overlay files and text labels to wx.Image
+            and then to textures so that they can be rendered by OpenGL.
+            Updates self.imagelist"""
+        # update images (legend and text)
         for oid, overlay in self.overlays.iteritems():
-            if not overlay.IsShown() or oid in (1, 2):  # 0 for barscale
+            if not overlay.IsShown() or overlay.name in ('barscale', 'northarrow'):
                 continue
             if oid not in [t.GetId() for t in self.imagelist]:  # new
-                self.CreateTexture(overlay=overlay.layer)
+                self.CreateTexture(overlay=overlay)
             else:
                 for t in self.imagelist:
                     if t.GetId() == oid:  # check if it is the same
                         if not t.Corresponds(overlay):
                             self.imagelist.remove(t)
-                            t = self.CreateTexture(overlay=overlay.layer)
+                            t = self.CreateTexture(overlay=overlay)
 
-        # update text labels
-        for textId in self.textdict.keys():
-            if textId not in [t.GetId() for t in self.imagelist]:  # new
-                self.CreateTexture(textId=textId)
-            else:
-                for t in self.imagelist:
-                    if t.GetId() == textId:  # check if it is the same
-                        self.textdict[textId]['bbox'] = t.textDict['bbox']
-                        if not t.Corresponds(self.textdict[textId]):
-                            self.imagelist.remove(t)
-                            t = self.CreateTexture(textId=textId)
-                        # always set coordinates, needed for synchr. 2D and 3D
-                        # modes
-                        t.SetCoords(self.textdict[textId]['coords'])
         self.Refresh()
 
-    def CreateTexture(self, overlay=None, textId=None):
-        """Create texture from overlay image or from textdict"""
-        if overlay:  # legend
-            texture = wxnviz.ImageTexture(
-                filepath=overlay.mapfile,
-                overlayId=overlay.id,
-                coords=list(
-                    self.overlays[
-                        overlay.id].coords),
-                cmd=overlay.GetCmd())
-            if overlay.id == 0:  # legend
-                texture.SetBounds(self.GetLegendRect())
-        else:  # text
-            coords, bbox, relCoords = self.TextBounds(self.textdict[textId])
-            self.textdict[textId]['coords'] = coords
-            self.textdict[textId]['bbox'] = bbox
-            file = self.DrawTextImage(self.textdict[textId], relCoords)
-            texture = wxnviz.TextTexture(
-                filepath=file, overlayId=textId, coords=coords,
-                textDict=self.textdict[textId])
-            bbox.OffsetXY(*relCoords)
-            texture.SetBounds(bbox)
+    def CreateTexture(self, overlay):
+        """Create texture from overlay image"""
+        texture = wxnviz.ImageTexture(
+                filepath=overlay.layer.mapfile, overlayId=overlay.id,
+                coords=list(overlay.coords), cmd=overlay.GetCmd())
 
         if not texture.textureId:  # texture too big
             GMessage(
@@ -605,6 +515,9 @@
 
         return texture
 
+    def ClearTextures(self):
+        self.imagelist = []
+
     def FindObjects(self, mouseX, mouseY, radius):
         """Find object which was clicked on"""
         for texture in self.imagelist:
@@ -783,11 +696,12 @@
             self.SetDrawScalebar((pos[0], size[1] - pos[1]))
 
         if self.mouse['use'] == 'pointer':
-            # get decoration or text id
+            # get decoration id
             self.dragid = self.FindObjects(
                 self.mouse['tmp'][0],
                 self.mouse['tmp'][1],
                 self.hitradius)
+
         if self.mouse['use'] == 'fly':
             if not self.timerFly.IsRunning():
                 self.timerFly.Start(self.fly['interval'])
@@ -895,17 +809,11 @@
             if self.dragid >= 0:
                 dx = self.mouse['end'][0] - self.mouse['begin'][0]
                 dy = self.mouse['end'][1] - self.mouse['begin'][1]
-                if self.dragid < 99:
+                if self.dragid in self.overlays:
                     coords = self.overlays[self.dragid].coords
                     self.overlays[
                         self.dragid].coords = [
                         coords[0] + dx, coords[1] + dy]
-                else:  # text
-                    coords = self.textdict[self.dragid]['coords']
-                    self.textdict[
-                        self.dragid]['coords'] = [
-                        coords[0] + dx,
-                        coords[1] + dy]
                 self.dragid = -1
                 self.render['quick'] = False
                 self.Refresh(False)
@@ -2764,13 +2672,6 @@
         """
         self.lmgr.nviz.OnResetView(None)
 
-    def TextBounds(self, textinfo):
-        """Return text boundary data
-
-        :param textinfo: text metadata (text, font, color, rotation)
-        """
-        return self.parent.MapWindow2D.TextBounds(textinfo, relcoords=True)
-
     def DisactivateWin(self):
         """Use when the class instance is hidden in MapFrame."""
         pass

Modified: grass/branches/releasebranch_7_2/gui/wxpython/nviz/wxnviz.py
===================================================================
--- grass/branches/releasebranch_7_2/gui/wxpython/nviz/wxnviz.py	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/gui/wxpython/nviz/wxnviz.py	2016-08-21 22:54:25 UTC (rev 69199)
@@ -49,7 +49,7 @@
 from grass.lib.raster import *
 
 from core.debug import Debug
-from core.utils import _
+from core.utils import _, autoCropImageFromFile
 import grass.script as grass
 
 log = None
@@ -2041,12 +2041,11 @@
         :param coords: image coordinates
         """
         self.path = filepath
-        self.image = wx.Image(filepath, wx.BITMAP_TYPE_ANY)
-        self.width = self.image.GetWidth()
-        self.height = self.image.GetHeight()
+        self.image = autoCropImageFromFile(filepath)
+        self.width = self.orig_width = self.image.GetWidth()
+        self.height = self.orig_height = self.image.GetHeight()
         self.id = overlayId
-        self.coords = [0, 0]
-        self.bounds = wx.Rect()
+        self.coords = coords
         self.active = True
 
         # alpha needs to be initialized
@@ -2131,12 +2130,8 @@
             self.height,
             self.textureId)
 
-    def SetBounds(self, rect):
-        """Set Bounding Rectangle"""
-        self.bounds = rect
-
     def HitTest(self, x, y, radius):
-        copy = wx.Rect(*self.bounds)
+        copy = wx.Rect(self.coords[0], self.coords[1], self.orig_width, self.orig_height)
         copy.Inflate(radius, radius)
         return copy.ContainsXY(x, y)
 
@@ -2144,7 +2139,6 @@
         """Move texture on the screen"""
         self.coords[0] += dx
         self.coords[1] += dy
-        self.bounds.OffsetXY(dx, dy)
 
     def SetCoords(self, coords):
         """Set coordinates"""
@@ -2188,37 +2182,3 @@
 
     def Corresponds(self, item):
         return sorted(self.GetCmd()) == sorted(item.GetCmd())
-
-
-class TextTexture(Texture):
-    """Class representing OpenGL texture as a text label"""
-
-    def __init__(self, filepath, overlayId, coords, textDict):
-        """Load image to texture
-
-        :param filepath: path to image file
-        :param overlayId: id of overlay (101 and more for text)
-        :param coords: text coordinates
-        :param textDict: text properties
-        """
-        Texture.__init__(
-            self,
-            filepath=filepath,
-            overlayId=overlayId,
-            coords=coords)
-
-        self.textDict = textDict
-
-    def GetTextDict(self):
-        """Returns text properties."""
-        return self.textDict
-
-    def Corresponds(self, item):
-        t = self.GetTextDict()
-        for prop in t.keys():
-            if prop in ('coords', 'bbox'):
-                continue
-            if t[prop] != item[prop]:
-                return False
-
-        return True

Modified: grass/branches/releasebranch_7_2/include/symbol.h
===================================================================
--- grass/branches/releasebranch_7_2/include/symbol.h	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/include/symbol.h	2016-08-21 22:54:25 UTC (rev 69199)
@@ -67,6 +67,8 @@
 typedef struct
 {
     double scale;		/* to get symbol of size 1, each vertex must be multiplied by this scale */
+    double yscale;		/* scale in x dimension */
+    double xscale;		/* scale in y dimension */
     int count, alloc;		/* numer of parts */
     SYMBPART **part;		/* objects ( parts ) */
 } SYMBOL;

Modified: grass/branches/releasebranch_7_2/lib/symbol/Makefile
===================================================================
--- grass/branches/releasebranch_7_2/lib/symbol/Makefile	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/lib/symbol/Makefile	2016-08-21 22:54:25 UTC (rev 69199)
@@ -7,7 +7,7 @@
 
 SYMBOL_SRC := $(wildcard symbol/*/*)
 SYMBOL_DST := $(patsubst symbol/%,$(ETC)/symbol/%,$(SYMBOL_SRC))
-SYMBOL_DIRS := $(patsubst %,$(ETC)/symbol/%, demo basic extra geology n_arrows)
+SYMBOL_DIRS := $(patsubst %,$(ETC)/symbol/%, demo basic extra geology legend n_arrows)
 
 default: lib
 	$(MAKE) $(SYMBOL_DST)

Modified: grass/branches/releasebranch_7_2/lib/symbol/read.c
===================================================================
--- grass/branches/releasebranch_7_2/lib/symbol/read.c	2016-08-21 22:40:25 UTC (rev 69198)
+++ grass/branches/releasebranch_7_2/lib/symbol/read.c	2016-08-21 22:54:25 UTC (rev 69199)
@@ -312,10 +312,14 @@
 		sprintf(buf, "Incorrect box definition: '%s'", data);
 		return (err(fp, symb, buf));
 	    }
-	    if (x2 - x > y2 - y)
-		symb->scale = 1 / (x2 - x);
-	    else
-		symb->scale = 1 / (y2 - y);
+	    symb->xscale = 1 / (x2 - x);
+	    symb->yscale = 1 / (y2 - y);
+	    if (x2 - x > y2 - y) {
+		symb->scale = symb->xscale;
+	    }
+	    else {
+		symb->scale = symb->yscale;
+	    }
 	}
 	else if (strcmp(key, "STRING") == 0) {
 	    G_debug(4, "  STRING >");

Added: grass/branches/releasebranch_7_2/lib/symbol/symbol/legend/area
===================================================================
--- grass/branches/releasebranch_7_2/lib/symbol/symbol/legend/area	                        (rev 0)
+++ grass/branches/releasebranch_7_2/lib/symbol/symbol/legend/area	2016-08-21 22:54:25 UTC (rev 69199)
@@ -0,0 +1,13 @@
+VERSION 1.0
+BOX -1 -1 1 1
+POLYGON
+  RING
+    LINE
+      -1 -0.75
+      1 -0.75
+      1 0.75
+      -1 0.75
+    END
+  END
+END
+

Added: grass/branches/releasebranch_7_2/lib/symbol/symbol/legend/area_curved
===================================================================
--- grass/branches/releasebranch_7_2/lib/symbol/symbol/legend/area_curved	                        (rev 0)
+++ grass/branches/releasebranch_7_2/lib/symbol/symbol/legend/area_curved	2016-08-21 22:54:25 UTC (rev 69199)
@@ -0,0 +1,114 @@
+VERSION 1.0
+BOX -1 -1 1 1
+POLYGON
+  RING
+    LINE
+      -00.030 000.615
+      000.064 000.656
+      000.128 000.696
+      000.200 000.768
+      000.260 000.813
+      000.321 000.865
+      000.375 000.903
+      000.431 000.917
+      000.488 000.944
+      000.547 000.944
+      000.605 000.913
+      000.661 000.873
+      000.721 000.835
+      000.773 000.770
+      000.821 000.696
+      000.872 000.648
+      000.909 000.561
+      000.944 000.472
+      000.978 000.366
+      000.992 000.261
+      000.998 000.166
+      001.000 000.094
+      001.000 000.013
+      000.998 -00.071
+      000.976 -00.148
+      000.954 -00.223
+      000.932 -00.297
+      000.903 -00.376
+      000.876 -00.454
+      000.838 -00.527
+      000.804 -00.595
+      000.767 -00.660
+      000.727 -00.726
+      000.681 -00.801
+      000.644 -00.854
+      000.603 -00.903
+      000.551 -00.946
+      000.509 -00.976
+      000.471 -00.996
+      000.429 -01.000
+      000.388 -00.998
+      000.338 -00.996
+      000.289 -00.964
+      000.249 -00.923
+      000.204 -00.875
+      000.163 -00.825
+      000.126 -00.748
+      000.074 -00.670
+      000.038 -00.601
+      -00.008 -00.539
+      -00.051 -00.486
+      -00.095 -00.462
+      -00.144 -00.446
+      -00.194 -00.428
+      -00.242 -00.428
+      -00.286 -00.430
+      -00.337 -00.432
+      -00.377 -00.448
+      -00.420 -00.466
+      -00.461 -00.478
+      -00.509 -00.480
+      -00.552 -00.480
+      -00.595 -00.488
+      -00.642 -00.496
+      -00.687 -00.498
+      -00.731 -00.498
+      -00.779 -00.488
+      -00.822 -00.462
+      -00.864 -00.450
+      -00.925 -00.416
+      -00.953 -00.347
+      -00.984 -00.267
+      -00.995 -00.196
+      -01.000 -00.108
+      -01.000 -00.013
+      -01.000 000.078
+      -00.985 000.182
+      -00.972 000.273
+      -00.958 000.357
+      -00.941 000.431
+      -00.912 000.519
+      -00.892 000.599
+      -00.878 000.670
+      -00.852 000.728
+      -00.834 000.778
+      -00.800 000.839
+      -00.773 000.889
+      -00.739 000.942
+      -00.703 000.976
+      -00.667 000.994
+      -00.624 001.000
+      -00.586 000.992
+      -00.541 000.980
+      -00.495 000.956
+      -00.445 000.942
+      -00.403 000.919
+      -00.365 000.881
+      -00.332 000.835
+      -00.289 000.797
+      -00.239 000.760
+      -00.200 000.720
+      -00.157 000.672
+      -00.115 000.629
+      -00.075 000.617
+      000.023 000.629
+    END
+  END
+END
+

Added: grass/branches/releasebranch_7_2/lib/symbol/symbol/legend/line
===================================================================
--- grass/branches/releasebranch_7_2/lib/symbol/symbol/legend/line	                        (rev 0)
+++ grass/branches/releasebranch_7_2/lib/symbol/symbol/legend/line	2016-08-21 22:54:25 UTC (rev 69199)
@@ -0,0 +1,8 @@
+VERSION 1.0
+BOX -1 -1 1 1
+STRING
+  LINE
+    -1 0
+    1 0
+  END
+END

Added: grass/branches/releasebranch_7_2/lib/symbol/symbol/legend/line_crooked
===================================================================
--- grass/branches/releasebranch_7_2/lib/symbol/symbol/legend/line_crooked	                        (rev 0)
+++ grass/branches/releasebranch_7_2/lib/symbol/symbol/legend/line_crooked	2016-08-21 22:54:25 UTC (rev 69199)
@@ -0,0 +1,13 @@
+VERSION 1.0
+BOX -1 -1 1 1
+STRING
+  LINE
+    -1 -0.33
+    -0.33  0.33
+     0.33 -0.33
+     1  0.33
+	 0.33 -0.33
+	-0.33  0.33
+	-1 -0.33
+  END
+END



More information about the grass-commit mailing list