[GRASS-SVN] r31854 - in grass-addons/visualization/nviz2: lib
wxpython wxpython/nviz
svn_grass at osgeo.org
svn_grass at osgeo.org
Thu Jun 26 10:05:06 EDT 2008
Author: martinl
Date: 2008-06-26 10:05:06 -0400 (Thu, 26 Jun 2008)
New Revision: 31854
Added:
grass-addons/visualization/nviz2/wxpython/wxgui_utils.py
Modified:
grass-addons/visualization/nviz2/lib/change_view.c
grass-addons/visualization/nviz2/wxpython/mapdisp.py
grass-addons/visualization/nviz2/wxpython/nviz.py
grass-addons/visualization/nviz2/wxpython/nviz/change_view.cpp
grass-addons/visualization/nviz2/wxpython/nviz/dig_types.i
grass-addons/visualization/nviz2/wxpython/nviz/draw.cpp
grass-addons/visualization/nviz2/wxpython/nviz/nviz.h
Log:
nviz2: wxGUI integration in progress (reset view, surface properties)
Modified: grass-addons/visualization/nviz2/lib/change_view.c
===================================================================
--- grass-addons/visualization/nviz2/lib/change_view.c 2008-06-26 12:39:06 UTC (rev 31853)
+++ grass-addons/visualization/nviz2/lib/change_view.c 2008-06-26 14:05:06 UTC (rev 31854)
@@ -16,6 +16,7 @@
\date 2008
*/
+#include <grass/glocale.h>
#include <grass/nviz.h>
/*!
@@ -104,6 +105,12 @@
ypos = 1.0 - y_pos;
ypos = (ypos < 0) ? 0 : (ypos > 1.0) ? 1.0 : ypos;
+ if (x_pos < 0.0 || x_pos > 1.0 ||
+ y_pos < 0.0 || y_pos > 1.0) {
+ G_warning (_("Invalid view position coordinates, using %f,%f"),
+ xpos, 1.0 - ypos);
+ }
+
GS_get_from(from);
tempx = xpos * RANGE - RANGE_OFFSET;
@@ -116,7 +123,7 @@
GS_moveto(from);
- Nviz_draw_quick(data);
+ /* Nviz_draw_quick(data); */
}
return 1;
@@ -146,7 +153,7 @@
GS_setlight_position(1, from[X], from[Y], from[Z], 0);
*/
- Nviz_draw_quick(data);
+ /* Nviz_draw_quick(data); */
}
return 1;
@@ -167,7 +174,7 @@
fov = (int) (10 * persp);
GS_set_fov(fov);
- Nviz_draw_quick(data);
+ /* Nviz_draw_quick(data); */
return 1;
}
@@ -184,7 +191,7 @@
{
GS_set_twist(10 * twist);
- Nviz_draw_quick(data);
+ /* Nviz_draw_quick(data); */
return 1;
}
@@ -207,7 +214,7 @@
GS_set_global_exag(exag);
Nviz_update_ranges(data);
- Nviz_draw_quick(data);
+ /* Nviz_draw_quick(data); */
}
return 1;
Modified: grass-addons/visualization/nviz2/wxpython/mapdisp.py
===================================================================
--- grass-addons/visualization/nviz2/wxpython/mapdisp.py 2008-06-26 12:39:06 UTC (rev 31853)
+++ grass-addons/visualization/nviz2/wxpython/mapdisp.py 2008-06-26 14:05:06 UTC (rev 31854)
@@ -2560,13 +2560,15 @@
self.MapWindow3D = nviz.GLWindow(self, id=wx.ID_ANY,
Map=self.Map, tree=self.tree, gismgr=self.gismanager)
self.nvizToolWin = nviz.NvizToolWindow(self, id=wx.ID_ANY,
- settings=self.MapWindow3D.view)
+ mapWindow=self.MapWindow3D)
#
# add Nviz toolbar and disable 2D display mode tools
#
self.toolbars['nviz'] = toolbars.NvizToolbar(self, self.Map)
self.toolbars['map'].Enable2D(False)
+ self.toggleStatus.Enable(False)
+
self.nvizToolWin.Show()
#
@@ -2628,6 +2630,7 @@
self.toolbars['map'].combo.SetValue ("Tools")
self.toolbars['map'].Enable2D(True)
+ self.toggleStatus.Enable(True)
self._mgr.Update()
Modified: grass-addons/visualization/nviz2/wxpython/nviz/change_view.cpp
===================================================================
--- grass-addons/visualization/nviz2/wxpython/nviz/change_view.cpp 2008-06-26 12:39:06 UTC (rev 31853)
+++ grass-addons/visualization/nviz2/wxpython/nviz/change_view.cpp 2008-06-26 14:05:06 UTC (rev 31854)
@@ -45,10 +45,10 @@
Nviz_get_exag_height(&vp_height, NULL, NULL);
Nviz_change_exag(data,
- 1.0);
+ VIEW_DEFAULT_ZEXAG);
- SetView(0.85, 0.85,
- vp_height, 40.0, 0.0);
+ SetView(VIEW_DEFAULT_POS_X, VIEW_DEFAULT_POS_Y,
+ vp_height, VIEW_DEFAULT_PERSP, VIEW_DEFAULT_TWIST);
return vp_height;
}
@@ -62,7 +62,6 @@
\param twist
\return 1 on success
- \return 0 on failure
*/
int Nviz::SetView(float x, float y,
float height, float persp, float twist)
@@ -78,3 +77,15 @@
return 1;
}
+
+/*!
+ \brief Set z-exag value
+
+ \param z_exag value
+
+ \return 1
+*/
+int Nviz::SetZExag(float z_exag)
+{
+ return Nviz_change_exag(data, z_exag);
+}
Modified: grass-addons/visualization/nviz2/wxpython/nviz/dig_types.i
===================================================================
--- grass-addons/visualization/nviz2/wxpython/nviz/dig_types.i 2008-06-26 12:39:06 UTC (rev 31853)
+++ grass-addons/visualization/nviz2/wxpython/nviz/dig_types.i 2008-06-26 14:05:06 UTC (rev 31854)
@@ -13,3 +13,11 @@
#define GV_LINES (GV_LINE | GV_BOUNDARY )
#define PORT_DOUBLE_MAX 1.7976931348623157e+308
+
+/* extracted from gui/wxpython/nviz/nviz.h */
+
+#define VIEW_DEFAULT_POS_X 0.85
+#define VIEW_DEFAULT_POS_Y 0.85
+#define VIEW_DEFAULT_PERSP 40.0
+#define VIEW_DEFAULT_TWIST 0.0
+#define VIEW_DEFAULT_ZEXAG 1.0
Modified: grass-addons/visualization/nviz2/wxpython/nviz/draw.cpp
===================================================================
--- grass-addons/visualization/nviz2/wxpython/nviz/draw.cpp 2008-06-26 12:39:06 UTC (rev 31853)
+++ grass-addons/visualization/nviz2/wxpython/nviz/draw.cpp 2008-06-26 14:05:06 UTC (rev 31854)
@@ -19,13 +19,22 @@
#include "nviz.h"
-void Nviz::Draw()
+/*!
+ \brief Draw map
+
+ \param quick true for quick rendering
+*/
+void Nviz::Draw(bool quick)
{
GS_clear(data->bgcolor);
Nviz_draw_cplane(data, -1, -1);
- Nviz_draw_all (data);
+ if (!quick)
+ Nviz_draw_all (data);
+ else
+ Nviz_draw_quick(data);
+
return;
}
Modified: grass-addons/visualization/nviz2/wxpython/nviz/nviz.h
===================================================================
--- grass-addons/visualization/nviz2/wxpython/nviz/nviz.h 2008-06-26 12:39:06 UTC (rev 31853)
+++ grass-addons/visualization/nviz2/wxpython/nviz/nviz.h 2008-06-26 14:05:06 UTC (rev 31854)
@@ -24,6 +24,12 @@
#include <wx/glcanvas.h>
+#define VIEW_DEFAULT_POS_X 0.85
+#define VIEW_DEFAULT_POS_Y 0.85
+#define VIEW_DEFAULT_PERSP 40.0
+#define VIEW_DEFAULT_TWIST 0.0
+#define VIEW_DEFAULT_ZEXAG 1.0
+
class Nviz
{
private:
@@ -54,6 +60,7 @@
float SetViewDefault();
int SetView(float, float,
float, float, float);
+ int SetZExag(float);
/* init.cpp */
void InitView();
@@ -64,7 +71,7 @@
/* draw */
/* draw.cpp */
- void Draw();
+ void Draw(bool);
void EraseMap();
};
Modified: grass-addons/visualization/nviz2/wxpython/nviz.py
===================================================================
--- grass-addons/visualization/nviz2/wxpython/nviz.py 2008-06-26 12:39:06 UTC (rev 31853)
+++ grass-addons/visualization/nviz2/wxpython/nviz.py 2008-06-26 14:05:06 UTC (rev 31854)
@@ -7,6 +7,7 @@
- GLWindow
- NvizToolWindow
- ViewPositionWindow
+ - RasterPropertiesDialog
(C) 2008 by the GRASS Development Team
@@ -103,18 +104,31 @@
#
self.view = { 'persp' : { 'value' : 40,
'min' : 0,
- 'max' : 100
+ 'max' : 100,
+ 'step' : 5,
+ 'update' : False,
},
'pos' : { 'value' : (0.85, 0.85),
+ 'update' : False,
},
'height' : { 'value': -1,
'min' : -2245, # TODO: determine min/max height
- 'max' : 3695
+ 'max' : 3695,
+ 'step' : 100,
+ 'update' : False,
},
'twist' : { 'value' : 0,
'min' : -180,
- 'max' : 180
+ 'max' : 180,
+ 'step' : 5,
+ 'update' : False,
},
+ 'z-exag' : { 'value': 1.0,
+ 'min' : 0.0,
+ 'max' : 10,
+ 'step' : 1,
+ 'update' : False
+ },
}
self.size = None
@@ -168,9 +182,9 @@
current = event.GetPositionTuple()[:]
Debug.msg (5, "GLWindow.OnMouseMotion(): wheel=%d" % wheel)
if wheel > 0:
- value = -5 # TODO: settings
+ value = -1 * self.view['persp']['step']
else:
- value = 5
+ value = self.view['persp']['step']
self.view['persp']['value'] += value
if self.view['persp']['value'] < 0:
self.view['persp']['value'] = 0
@@ -198,12 +212,15 @@
def OnLeftUp(self, event):
self.ReleaseMouse()
- def UpdateMap(self, render=True):
+ def UpdateMap(self, render=False):
"""
Updates the canvas anytime there is a change to the
underlaying images or to the geometry of the canvas.
- @todo render=False
+ render:
+ - None do not render (todo)
+ - True render
+ - False quick render
@param render re-render map composition
"""
@@ -228,20 +245,33 @@
#glRotatef((self.y - self.lastY) * yScale, 1.0, 0.0, 0.0);
#glRotatef((self.x - self.lastX) * xScale, 0.0, 1.0, 0.0);
- if render is True:
+ if render is not None:
self.parent.onRenderGauge.Show()
if self.parent.onRenderGauge.GetRange() > 0:
self.parent.onRenderGauge.SetValue(1)
self.parent.onRenderTimer.Start(100)
self.parent.onRenderCounter = 0
- self.nvizClass.SetView(self.view['pos']['value'][0], self.view['pos']['value'][1],
- self.view['height']['value'],
- self.view['persp']['value'],
- self.view['twist']['value'])
+ if self.view['pos']['update'] or \
+ self.view['height']['update'] or \
+ self.view['persp']['update'] or \
+ self.view['twist']['update']:
+ self.nvizClass.SetView(self.view['pos']['value'][0], self.view['pos']['value'][1],
+ self.view['height']['value'],
+ self.view['persp']['value'],
+ self.view['twist']['value'])
+ for control in ('pos', 'height', 'persp', 'twist'):
+ self.view[control]['update'] = False
- self.nvizClass.Draw()
+ if self.view['z-exag']['update']:
+ self.nvizClass.SetZExag(self.view['z-exag']['value'])
+ self.view['z-exag']['update'] = False
+ if render is True:
+ self.nvizClass.Draw(False)
+ elif render is False:
+ self.nvizClass.Draw(True) # quick
+
self.SwapBuffers()
stop = time.clock()
@@ -262,6 +292,8 @@
Debug.msg(3, "GLWindow.UpdateMap(): render=%s, -> time=%g" % \
(render, (stop-start)))
+ print '# %.6f' % (stop-start)
+
def EraseMap(self):
"""
Erase the canvas
@@ -299,17 +331,31 @@
self.nvizClass.SetViewportDefault()
+ def ResetView(self):
+ """Reset to default view"""
+ self.view['pos']['value'] = (wxnviz.VIEW_DEFAULT_POS_X,
+ wxnviz.VIEW_DEFAULT_POS_Y)
+ self.view['height']['value'] = self.nvizClass.SetViewDefault()
+ self.view['persp']['value'] = wxnviz.VIEW_DEFAULT_PERSP
+ self.view['twist']['value'] = wxnviz.VIEW_DEFAULT_TWIST
+ self.view['z-exag']['value'] = wxnviz.VIEW_DEFAULT_ZEXAG
+
+ for control in ('pos', 'height', 'persp',
+ 'twist', 'z-exag'):
+ self.view[control]['update'] = True
+
class NvizToolWindow(wx.Frame):
"""Experimental window for Nviz tools
- @todo integrated with Map display
+ @todo integrate with Map display
"""
def __init__(self, parent=None, id=wx.ID_ANY, title=_("Nviz tools"),
pos=wx.DefaultPosition, size=wx.DefaultSize,
- style=wx.DEFAULT_FRAME_STYLE, settings={}):
+ style=wx.DEFAULT_FRAME_STYLE, mapWindow=None):
self.parent = parent # MapFrame
- self.settings = settings # GLWindow.view
+ self.mapWindow = mapWindow
+ self.settings = mapWindow.view # GLWindow.view
wx.Frame.__init__(self, parent, id, title, pos, size, style)
@@ -338,20 +384,24 @@
pageSizer = wx.BoxSizer(wx.VERTICAL)
gridSizer = wx.GridBagSizer(vgap=3, hgap=3)
+ # position
posSizer = wx.GridBagSizer(vgap=3, hgap=3)
posSizer.Add(item=wx.StaticText(panel, id=wx.ID_ANY, label=_("W")),
pos=(1, 0), flag=wx.ALIGN_CENTER)
posSizer.Add(item=wx.StaticText(panel, id=wx.ID_ANY, label=_("N")),
pos=(0, 1), flag=wx.ALIGN_CENTER | wx.ALIGN_BOTTOM)
- posSizer.Add(item=ViewPositionWindow(panel, id=wx.ID_ANY, size=(175, 175),
- settings=self.settings, mapwindow=self.parent.MapWindow3D),
+ viewPos = ViewPositionWindow(panel, id=wx.ID_ANY, size=(175, 175),
+ settings=self.settings, mapwindow=self.mapWindow)
+ self.win['pos'] = viewPos.GetId()
+ posSizer.Add(item=viewPos,
pos=(1, 1), flag=wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL)
posSizer.Add(item=wx.StaticText(panel, id=wx.ID_ANY, label=_("S")),
pos=(2, 1), flag=wx.ALIGN_CENTER | wx.ALIGN_TOP)
posSizer.Add(item=wx.StaticText(panel, id=wx.ID_ANY, label=_("E")),
pos=(1, 2), flag=wx.ALIGN_CENTER)
gridSizer.Add(item=posSizer, pos=(0, 0))
-
+
+ # perspective
self.CreateControl(panel, 'persp')
gridSizer.Add(item=wx.StaticText(panel, id=wx.ID_ANY, label=_("Perspective:")),
pos=(1, 0), flag=wx.ALIGN_CENTER)
@@ -359,6 +409,7 @@
gridSizer.Add(item=self.FindWindowById(self.win['persp']['spin']), pos=(3, 0),
flag=wx.ALIGN_CENTER)
+ # twist
self.CreateControl(panel, 'twist')
gridSizer.Add(item=wx.StaticText(panel, id=wx.ID_ANY, label=_("Twist:")),
pos=(1, 1), flag=wx.ALIGN_CENTER)
@@ -366,7 +417,9 @@
gridSizer.Add(item=self.FindWindowById(self.win['twist']['spin']), pos=(3, 1),
flag=wx.ALIGN_CENTER)
+ # height + z-exag
self.CreateControl(panel, 'height', sliderHor=False)
+ self.CreateControl(panel, 'z-exag', sliderHor=False)
heightSizer = wx.GridBagSizer(vgap=3, hgap=3)
heightSizer.Add(item=wx.StaticText(panel, id=wx.ID_ANY, label=_("Height:")),
pos=(0, 0), flag=wx.ALIGN_LEFT, span=(1, 2))
@@ -375,8 +428,53 @@
heightSizer.Add(item=self.FindWindowById(self.win['height']['spin']),
flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.TOP |
wx.BOTTOM | wx.RIGHT, pos=(1, 1))
+ heightSizer.Add(item=wx.StaticText(panel, id=wx.ID_ANY, label=_("Z-exag:")),
+ pos=(0, 2), flag=wx.ALIGN_LEFT, span=(1, 2))
+ heightSizer.Add(item=self.FindWindowById(self.win['z-exag']['slider']),
+ flag=wx.ALIGN_RIGHT, pos=(1, 2))
+ heightSizer.Add(item=self.FindWindowById(self.win['z-exag']['spin']),
+ flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT | wx.TOP |
+ wx.BOTTOM | wx.RIGHT, pos=(1, 3))
+
gridSizer.Add(item=heightSizer, pos=(0, 1), flag=wx.ALIGN_RIGHT)
+ # view setup + reset
+ viewSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+ viewSizer.Add(item=wx.StaticText(panel, id=wx.ID_ANY,
+ label=_("Look at:")),
+ flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=5)
+
+ viewType = wx.Choice (parent=panel, id=wx.ID_ANY, size=(125, -1),
+ choices = [_("top"),
+ _("north"),
+ _("south"),
+ _("east"),
+ _("west"),
+ _("north-west"),
+ _("north-east"),
+ _("south-east"),
+ _("south-west")])
+ viewType.SetSelection(0)
+ viewType.Bind(wx.EVT_CHOICE, self.OnLookAt)
+ # self.win['lookAt'] = viewType.GetId()
+ viewSizer.Add(item=viewType, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL,
+ border=5)
+
+ reset = wx.Button(panel, id=wx.ID_ANY, label=_("Reset"))
+ reset.SetToolTipString(_("Reset to default view"))
+ # self.win['reset'] = reset.GetId()
+ reset.Bind(wx.EVT_BUTTON, self.OnResetView)
+
+ viewSizer.Add(item=reset, proportion=1,
+ flag=wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT,
+ border=5)
+
+ gridSizer.AddGrowableCol(3)
+ gridSizer.Add(item=viewSizer, pos=(4, 0), span=(1, 2),
+ flag=wx.EXPAND)
+
+ # body
pageSizer.Add(item=gridSizer, proportion=1,
flag=wx.EXPAND | wx.ALL,
border=5)
@@ -408,6 +506,11 @@
initial=self.settings[name]['value'],
min=self.settings[name]['min'],
max=self.settings[name]['max'])
+ # spin = wx.SpinButton(parent=parent, id=wx.ID_ANY)
+ # spin.SetValue (self.settings[name]['value'])
+ # spin.SetRange(self.settings[name]['min'],
+ # self.settings[name]['max'])
+
spin.Bind(wx.EVT_SPINCTRL, self.OnChangeValue)
self.win[name]['spin'] = spin.GetId()
@@ -416,18 +519,27 @@
"""Update dialog settings"""
for control in ('height',
'persp',
- 'twist'):
+ 'twist',
+ 'z-exag'):
for win in self.win[control].itervalues():
self.FindWindowById(win).SetValue(self.settings[control]['value'])
+ self.FindWindowById(self.win['pos']).Draw()
+ self.FindWindowById(self.win['pos']).Refresh(False)
+
self.Refresh(False)
def OnChangeValue(self, event):
# find control
winName = ''
for name in self.win.iterkeys():
- for win in self.win[name].itervalues():
- if win == event.GetId():
+ if type(self.win[name]) is type({}):
+ for win in self.win[name].itervalues():
+ if win == event.GetId():
+ winName = name
+ break
+ else:
+ if self.win[name] == event.GetId():
winName = name
break
@@ -438,8 +550,43 @@
for win in self.win[winName].itervalues():
self.FindWindowById(win).SetValue(self.settings[winName]['value'])
- self.parent.MapWindow3D.Refresh(False)
+ self.settings[winName]['update'] = True
+
+ self.mapWindow.Refresh(False)
+
+ def OnResetView(self, event):
+ """Reset to default view"""
+ self.mapWindow.ResetView()
+ self.UpdateSettings()
+ self.mapWindow.Refresh(False)
+
+ def OnLookAt(self, event):
+ """Look at"""
+ sel = event.GetSelection()
+ if sel == 0: # top
+ self.settings['pos']['value'] = (0.5, 0.5)
+ elif sel == 1: # north
+ self.settings['pos']['value'] = (0.5, 0.0)
+ elif sel == 2: # south
+ self.settings['pos']['value'] = (0.5, 1.0)
+ elif sel == 3: # east
+ self.settings['pos']['value'] = (1.0, 0.5)
+ elif sel == 4: # west
+ self.settings['pos']['value'] = (0.0, 0.5)
+ elif sel == 5: # north-west
+ self.settings['pos']['value'] = (0.0, 0.0)
+ elif sel == 6: # north-east
+ self.settings['pos']['value'] = (1.0, 0.0)
+ elif sel == 7: # south-east
+ self.settings['pos']['value'] = (1.0, 1.0)
+ elif sel == 8: # south-west
+ self.settings['pos']['value'] = (0.0, 1.0)
+
+ self.settings['pos']['update'] = True
+ self.UpdateSettings()
+ self.mapWindow.Refresh(False)
+
class ViewPositionWindow(wx.Window):
"""Position control window (for NvizToolWindow)"""
def __init__(self, parent, id, mapwindow,
@@ -465,19 +612,17 @@
def Draw(self, pos=None):
w, h = self.GetClientSize()
- w2 = w / 2
- h2 = h / 2
if pos is None:
x, y = self.settings['pos']['value']
- x = x * w2 + w2
- y = y * h2 + h2
+ x = x * w
+ y = y * h
else:
x, y = pos
self.pdc.Clear()
self.pdc.BeginDrawing()
- self.pdc.DrawLine(w2, h2, x, y)
+ self.pdc.DrawLine(w / 2, h / 2, x, y)
self.pdc.DrawCircle(x, y, 5)
self.pdc.EndDrawing()
@@ -495,10 +640,126 @@
self.Draw(pos=(x, y))
self.Refresh(False)
w, h = self.GetClientSize()
- w2 = w / 2.
- h2 = h / 2.
- x = x / w2 - 1
- y = y / h2 - 1
+ x = float(x) / w
+ y = float(y) / h
self.settings['pos']['value'] = (x, y)
+ self.settings['pos']['update'] = True
- self.mapWindow.Refresh(False)
+ self.mapWindow.Refresh(eraseBackground=False)
+ # self.mapWindow.UpdateMap()
+ # self.mapWindow.OnPaint(None)
+
+class RasterPropertiesDialog(wx.Dialog):
+ """Nviz raster properties
+
+ @todo integrate into Nviz tools window or d.rast dialog ?
+ """
+ def __init__(self, parent, map,
+ pos=wx.DefaultPosition,
+ style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER):
+
+ self.map = map
+
+ wx.Dialog.__init__(self, parent=parent, id=wx.ID_ANY,
+ style=style, pos=pos)
+ self.SetTitle(_("Properties of raster map <%s>") % self.map)
+
+ self.panel = wx.Panel(self, id=wx.ID_ANY)
+
+ bodySizer = wx.BoxSizer(wx.VERTICAL)
+
+ #
+ # surface attributes box
+ #
+ bodySizer.Add(item=self.__surfaceAttributes(), proportion=1,
+ flag=wx.ALL | wx.EXPAND, border=3)
+
+ #
+ # button (see menuform)
+ #
+ btnsizer = wx.BoxSizer(orient=wx.HORIZONTAL)
+ # cancel
+ btn_cancel = wx.Button(parent=self.panel, id=wx.ID_CANCEL)
+ btn_cancel.SetToolTipString(_("Cancel the command settings and ignore changes"))
+ btnsizer.Add(item=btn_cancel, proportion=0, flag=wx.ALL | wx.ALIGN_CENTER, border=10)
+ btn_cancel.Bind(wx.EVT_BUTTON, self.OnCancel)
+ btn_apply = wx.Button(parent=self.panel, id=wx.ID_APPLY)
+ btn_ok = wx.Button(parent=self.panel, id=wx.ID_OK)
+ btn_ok.SetDefault()
+
+ btnsizer.Add(item=btn_apply, proportion=0,
+ flag=wx.ALL | wx.ALIGN_CENTER,
+ border=10)
+ btnsizer.Add(item=btn_ok, proportion=0,
+ flag=wx.ALL | wx.ALIGN_CENTER,
+ border=10)
+
+ btn_apply.Bind(wx.EVT_BUTTON, self.OnApply)
+ btn_ok.Bind(wx.EVT_BUTTON, self.OnOK)
+
+ bodySizer.Add(item=btnsizer, proportion=0, flag=wx.ALIGN_CENTER)
+
+ self.panel.SetSizer(bodySizer)
+ bodySizer.Fit(self.panel)
+
+ def __surfaceAttributes(self):
+ """Sourface attributes section"""
+ box = wx.StaticBox (parent=self.panel, id=wx.ID_ANY,
+ label=" %s " % (_("Surface attributes")))
+ sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
+
+ gridSizer = wx.GridBagSizer(vgap=3, hgap=3)
+
+ # labels
+ col = 0
+ for type in (_("Attribute"),
+ _("Use"),
+ _("Map"),
+ _("Constant")):
+ gridSizer.Add(item=wx.StaticText(parent=self.panel, id=wx.ID_ANY,
+ label=type),
+ pos=(0, col))
+ col += 1
+
+ # type
+ row = 1
+ for attr in (_("Topography"),
+ _("Color"),
+ _("Mask"),
+ _("Transparency"),
+ _("Shininess"),
+ _("Emission")):
+ gridSizer.Add(item=wx.StaticText(parent=self.panel, id=wx.ID_ANY,
+ label=attr + ':'),
+ pos=(row, 0), flag=wx.ALIGN_CENTER_VERTICAL)
+ use = wx.Choice (parent=self.panel, id=wx.ID_ANY, size=(100, -1),
+ choices = [_("map"),
+ _("constant")])
+ gridSizer.Add(item=use, flag=wx.ALIGN_CENTER_VERTICAL,
+ pos=(row, 1))
+
+
+ row += 1
+
+ sizer.Add(item=gridSizer, proportion=1,
+ flag=wx.ALL | wx.EXPAND, border=3)
+
+ return sizer
+
+ def OnCancel(self, event):
+ """Cancel button pressed"""
+ self.Close()
+
+ def OnOK(self, event):
+ """OK button pressed
+
+ Update map and close dialog
+ """
+ pass
+
+ def OnApply(self, event):
+ """Apply button pressed
+
+ Update map (don't close dialog)
+ """
+ pass
Added: grass-addons/visualization/nviz2/wxpython/wxgui_utils.py
===================================================================
--- grass-addons/visualization/nviz2/wxpython/wxgui_utils.py (rev 0)
+++ grass-addons/visualization/nviz2/wxpython/wxgui_utils.py 2008-06-26 14:05:06 UTC (rev 31854)
@@ -0,0 +1,1157 @@
+"""
+MODULE: wxgui_utils.py
+
+CLASSES:
+ * AbstractLayer
+ * Layer
+ * LayerTree
+
+PURPOSE: Utility classes for GRASS wxPython GUI. Main functions include tree control
+ for GIS map layer management, command console, and command parsing.
+
+AUTHORS: The GRASS Development Team
+ Michael Barton (Arizona State University)
+ Jachym Cepicky (Mendel University of Agriculture)
+ Martin Landa <landa.martin gmail.com>
+
+COPYRIGHT: (C) 2007-2008 by the GRASS Development Team
+ This program is free software under the GNU General Public
+ License (>=v2). Read the file COPYING that comes with GRASS
+ for details.
+"""
+
+import os
+import sys
+import string
+
+import wx
+import wx.lib.customtreectrl as CT
+import wx.combo
+import wx.lib.newevent
+
+import globalvar
+import menuform
+import mapdisp
+import render
+import gcmd
+import grassenv
+import histogram
+import utils
+import profile
+from debug import Debug as Debug
+from icon import Icons as Icons
+from preferences import globalSettings as UserSettings
+try:
+ import subprocess
+except:
+ from compat import subprocess
+
+TREE_ITEM_HEIGHT = 25
+
+class LayerTree(CT.CustomTreeCtrl):
+ """
+ Creates layer tree structure
+ """
+ # def __init__(self, parent, id, pos, size, style):
+ def __init__(self, parent,
+ id=wx.ID_ANY, pos=wx.DefaultPosition,
+ size=wx.DefaultSize, style=wx.SUNKEN_BORDER,
+ ctstyle=CT.TR_HAS_BUTTONS | CT.TR_HAS_VARIABLE_ROW_HEIGHT |
+ CT.TR_HIDE_ROOT | CT.TR_ROW_LINES | CT.TR_FULL_ROW_HIGHLIGHT |
+ CT.TR_EDIT_LABELS | CT.TR_MULTIPLE,
+ idx=None, gismgr=None, notebook=None, auimgr=None, showMapDisplay=True):
+ CT.CustomTreeCtrl.__init__(self, parent, id, pos, size, style, ctstyle)
+
+ ### SetAutoLayout() causes that no vertical scrollbar is displayed
+ ### when some layers are not visible in layer tree
+ # self.SetAutoLayout(True)
+ self.SetGradientStyle(1)
+ self.EnableSelectionGradient(True)
+ self.SetFirstGradientColour(wx.Colour(100, 100, 100))
+ self.SetSecondGradientColour(wx.Colour(150, 150, 150))
+
+ self.Map = render.Map() # instance of render.Map to be associated with display
+ self.root = None # ID of layer tree root node
+ self.groupnode = 0 # index value for layers
+ self.optpage = {} # dictionary of notebook option pages for each map layer
+ self.layer_selected = None # ID of currently selected layer
+ self.saveitem = {} # dictionary to preserve layer attributes for drag and drop
+ self.first = True # indicates if a layer is just added or not
+ self.drag = False # flag to indicate a drag event is in process
+ self.disp_idx = idx
+ self.gismgr = gismgr
+ self.notebook = notebook # GIS Manager notebook for layer tree
+ self.treepg = parent # notebook page holding layer tree
+ self.auimgr = auimgr # aui manager
+
+ # init associated map display
+ self.mapdisplay = mapdisp.MapFrame(self,
+ id=wx.ID_ANY, pos=wx.DefaultPosition,
+ size=globalvar.MAP_WINDOW_SIZE,
+ style=wx.DEFAULT_FRAME_STYLE,
+ tree=self, notebook=self.notebook,
+ gismgr=self.gismgr, page=self.treepg,
+ Map=self.Map, auimgr=self.auimgr)
+
+ # title
+ self.mapdisplay.SetTitle(_("GRASS GIS Map Display: " +
+ str(self.disp_idx + 1) +
+ " - Location: " + grassenv.GetGRASSVariable("LOCATION_NAME")))
+
+ # show new display
+ if showMapDisplay is True:
+ self.mapdisplay.Show()
+ self.mapdisplay.Refresh()
+ self.mapdisplay.Update()
+
+ self.root = self.AddRoot(_("Map Layers"))
+ self.SetPyData(self.root, (None,None))
+
+ #create image list to use with layer tree
+ il = wx.ImageList(16, 16, mask=False)
+
+ trart = wx.ArtProvider.GetBitmap(wx.ART_FOLDER_OPEN, wx.ART_OTHER, (16, 16))
+ self.folder_open = il.Add(trart)
+ trart = wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_OTHER, (16, 16))
+ self.folder = il.Add(trart)
+
+ bmpsize = (16, 16)
+ trgif = Icons["addrast"].GetBitmap(bmpsize)
+ self.rast_icon = il.Add(trgif)
+
+ trgif = Icons["addrgb"].GetBitmap(bmpsize)
+ self.rgb_icon = il.Add(trgif)
+
+ trgif = Icons["addhis"].GetBitmap(bmpsize)
+ self.his_icon = il.Add(trgif)
+
+ trgif = Icons["addshaded"].GetBitmap(bmpsize)
+ self.shaded_icon = il.Add(trgif)
+
+ trgif = Icons["addrarrow"].GetBitmap(bmpsize)
+ self.rarrow_icon = il.Add(trgif)
+
+ trgif = Icons["addrnum"].GetBitmap(bmpsize)
+ self.rnum_icon = il.Add(trgif)
+
+ trgif = Icons["addvect"].GetBitmap(bmpsize)
+ self.vect_icon = il.Add(trgif)
+
+ trgif = Icons["addthematic"].GetBitmap(bmpsize)
+ self.theme_icon = il.Add(trgif)
+
+ trgif = Icons["addchart"].GetBitmap(bmpsize)
+ self.chart_icon = il.Add(trgif)
+
+ trgif = Icons["addgrid"].GetBitmap(bmpsize)
+ self.grid_icon = il.Add(trgif)
+
+ trgif = Icons["addgeodesic"].GetBitmap(bmpsize)
+ self.geodesic_icon = il.Add(trgif)
+
+ trgif = Icons["addrhumb"].GetBitmap(bmpsize)
+ self.rhumb_icon = il.Add(trgif)
+
+ trgif = Icons["addlabels"].GetBitmap(bmpsize)
+ self.labels_icon = il.Add(trgif)
+
+ trgif = Icons["addcmd"].GetBitmap(bmpsize)
+ self.cmd_icon = il.Add(trgif)
+
+ self.AssignImageList(il)
+
+ # use when groups implemented
+ ## self.tree.SetItemImage(self.root, fldridx, wx.TreeItemIcon_Normal)
+ ## self.tree.SetItemImage(self.root, fldropenidx, wx.TreeItemIcon_Expanded)
+
+ self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.OnExpandNode)
+ self.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.OnCollapseNode)
+ self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnActivateLayer)
+ self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnChangeSel)
+ self.Bind(CT.EVT_TREE_ITEM_CHECKED, self.OnLayerChecked)
+ self.Bind(wx.EVT_TREE_DELETE_ITEM, self.OnDeleteLayer)
+ self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnBeginDrag)
+ self.Bind(wx.EVT_TREE_END_DRAG, self.OnEndDrag)
+ self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.OnLayerContextMenu)
+ self.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.OnChangeLayerName)
+ self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
+ # self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
+
+ def OnKeyUp(self, event):
+ """Key pressed"""
+ key = event.GetKeyCode()
+ if key == wx.WXK_DELETE and self.gismgr:
+ self.gismgr.OnDeleteLayer(None)
+
+ event.Skip()
+
+ def OnChangeLayerName (self, event):
+ """Change layer name"""
+ Debug.msg (3, "LayerTree.OnChangeLayerName: name=%s" % event.GetLabel())
+
+ def OnLayerContextMenu (self, event):
+ """Contextual menu for item/layer"""
+ if not self.layer_selected:
+ event.Skip()
+ return
+
+ ltype = self.GetPyData(self.layer_selected)[0]['type']
+
+ Debug.msg (4, "LayerTree.OnContextMenu: layertype=%s" % \
+ ltype)
+
+ ## pos = event.GetPosition()
+ ## pos = self.ScreenToClient(pos)
+
+ if not hasattr (self, "popupID1"):
+ self.popupID1 = wx.NewId()
+ self.popupID2 = wx.NewId()
+ self.popupID3 = wx.NewId()
+ self.popupID4 = wx.NewId()
+ self.popupID5 = wx.NewId()
+ self.popupID6 = wx.NewId()
+ self.popupID7 = wx.NewId()
+ self.popupID8 = wx.NewId()
+ self.popupID9 = wx.NewId()
+ self.popupID10 = wx.NewId()
+ self.popupID11 = wx.NewId() # nviz
+
+ self.popupMenu = wx.Menu()
+ # general item
+ self.popupMenu.Append(self.popupID1, text=_("Remove"))
+ self.Bind(wx.EVT_MENU, self.gismgr.OnDeleteLayer, id=self.popupID1)
+
+ if ltype != "command": # rename
+ self.popupMenu.Append(self.popupID2, text=_("Rename"))
+ self.Bind(wx.EVT_MENU, self.RenameLayer, id=self.popupID2)
+
+ # map layer items
+ if ltype != "group" and \
+ ltype != "command": # properties
+ self.popupMenu.AppendSeparator()
+ self.popupMenu.Append(self.popupID8, text=_("Change opacity level"), kind=wx.ITEM_CHECK)
+ if self.FindWindowById(self.GetPyData(self.layer_selected)[0]['ctrl']).GetName() == 'spinCtrl':
+ checked = True
+ else:
+ checked = False
+ self.popupMenu.Check(self.popupID8, checked)
+ self.Bind(wx.EVT_MENU, self.OnPopupOpacityLevel, id=self.popupID8)
+ self.popupMenu.Append(self.popupID3, text=_("Properties"))
+ self.Bind(wx.EVT_MENU, self.OnPopupProperties, id=self.popupID3)
+ self.popupMenu.Append(self.popupID9, text=_("Zoom to selected map"))
+ self.Bind(wx.EVT_MENU, self.mapdisplay.MapWindow.ZoomToMap, id=self.popupID9)
+ self.popupMenu.Append(self.popupID10, text=_("Set computational region from selected map"))
+ self.Bind(wx.EVT_MENU, self.OnSetCompRegFromMap, id=self.popupID10)
+
+ # specific items
+ try:
+ mltype = self.GetPyData(self.layer_selected)[0]['type']
+ except:
+ mltype = None
+ #
+ # vector layers (specific items)
+ #
+ if mltype and mltype == "vector":
+ self.popupMenu.AppendSeparator()
+ self.popupMenu.Append(self.popupID4, text=_("Show attribute data"))
+ self.Bind (wx.EVT_MENU, self.gismgr.OnShowAttributeTable, id=self.popupID4)
+
+ self.popupMenu.Append(self.popupID5, text=_("Start editing"))
+ self.popupMenu.Append(self.popupID6, text=_("Stop editing"))
+ self.popupMenu.Enable(self.popupID6, False)
+ self.Bind (wx.EVT_MENU, self.OnStartEditing, id=self.popupID5)
+ self.Bind (wx.EVT_MENU, self.OnStopEditing, id=self.popupID6)
+
+ layer = self.GetPyData(self.layer_selected)[0]['maplayer']
+ # enable editing only for vector map layers available in the current mapset
+ digit = self.mapdisplay.toolbars['vdigit']
+ if layer.GetMapset() != grassenv.GetGRASSVariable("MAPSET"):
+ # only vector map in current mapset can be edited
+ self.popupMenu.Enable (self.popupID5, False)
+ self.popupMenu.Enable (self.popupID6, False)
+ elif digit and digit.layerSelectedID != None:
+ # vector map already edited
+ if digit.layers[digit.layerSelectedID] == layer:
+ self.popupMenu.Enable (self.popupID5, False)
+ self.popupMenu.Enable(self.popupID6, True)
+ self.popupMenu.Enable(self.popupID1, False)
+ else:
+ self.popupMenu.Enable(self.popupID5, False)
+ self.popupMenu.Enable(self.popupID6, False)
+ self.popupMenu.Append(self.popupID7, _("Metadata"))
+ self.Bind (wx.EVT_MENU, self.OnMetadata, id=self.popupID7)
+
+ #
+ # raster layers (specific items)
+ #
+ elif mltype and mltype == "raster":
+ self.popupMenu.AppendSeparator()
+ self.popupMenu.Append(self.popupID4, _("Histogram"))
+ self.Bind (wx.EVT_MENU, self.OnHistogram, id=self.popupID4)
+ self.popupMenu.Append(self.popupID5, _("Profile"))
+ self.Bind (wx.EVT_MENU, self.OnProfile, id=self.popupID5)
+ self.popupMenu.Append(self.popupID6, _("Metadata"))
+ self.Bind (wx.EVT_MENU, self.OnMetadata, id=self.popupID6)
+ if self.mapdisplay.toolbars['nviz']:
+ self.popupMenu.Append(self.popupID11, _("Nviz properties"))
+ self.Bind (wx.EVT_MENU, self.OnNvizProperties, id=self.popupID11)
+
+ ## self.PopupMenu(self.popupMenu, pos)
+ self.PopupMenu(self.popupMenu)
+ self.popupMenu.Destroy()
+
+ def OnMetadata(self, event):
+ """Print metadata of raster/vector map layer
+ TODO: Dialog to modify metadata
+ """
+ mapLayer = self.GetPyData(self.layer_selected)[0]['maplayer']
+ mltype = self.GetPyData(self.layer_selected)[0]['type']
+
+ if mltype == 'raster':
+ cmd = ['r.info']
+ elif mltype == 'vector':
+ cmd = ['v.info']
+ cmd.append('map=%s' % mapLayer.name)
+
+ # print output to command log area
+ self.gismgr.goutput.RunCmd(cmd)
+
+ def OnSetCompRegFromMap(self, event):
+ """Set computational region from selected map"""
+ mapLayer = self.GetPyData(self.layer_selected)[0]['maplayer']
+ mltype = self.GetPyData(self.layer_selected)[0]['type']
+
+ cmd = ['g.region',
+ '-p'] # print by default
+
+ # TODO: other elements
+ if mltype == 'raster':
+ cmd.append('rast=%s' % mapLayer.name)
+ elif mltype == 'vector':
+ cmd.append('vect=%s' % mapLayer.name)
+
+ # print output to command log area
+ self.gismgr.goutput.RunCmd(cmd)
+
+ def OnProfile(self, event):
+ """Plot profile of given raster map layer"""
+ mapLayer = self.GetPyData(self.layer_selected)[0]['maplayer']
+ if not mapLayer.name:
+ wx.MessageBox(parent=self,
+ message=_("Unable to create profile of "
+ "raster map."),
+ caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
+ return False
+
+ if not hasattr (self, "profileFrame"):
+ self.profileFrame = None
+
+ if hasattr (self.mapdisplay, "profile") and self.mapdisplay.profile:
+ self.profileFrame = self.mapdisplay.profile
+
+ if not self.profileFrame:
+ self.profileFrame = profile.ProfileFrame(self.mapdisplay,
+ id=wx.ID_ANY, pos=wx.DefaultPosition, size=(700,300),
+ style=wx.DEFAULT_FRAME_STYLE, rasterList=[mapLayer.name])
+ # show new display
+ self.profileFrame.Show()
+
+ def OnHistogram(self, event):
+ """
+ Plot histogram for given raster map layer
+ """
+ mapLayer = self.GetPyData(self.layer_selected)[0]['maplayer']
+ if not mapLayer.name:
+ wx.MessageBox(parent=self,
+ message=_("Unable to display histogram of "
+ "raster map."),
+ caption=_("Error"), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
+ return False
+
+ if not hasattr (self, "histogramFrame"):
+ self.histogramFrame = None
+
+ if hasattr (self.mapdisplay, "histogram") and self.mapdisplay.histogram:
+ self.histogramFrame = self.mapdisplay.histogram
+
+ if not self.histogramFrame:
+ self.histogramFrame = histogram.HistFrame(self,
+ id=wx.ID_ANY,
+ pos=wx.DefaultPosition, size=globalvar.HIST_WINDOW_SIZE,
+ style=wx.DEFAULT_FRAME_STYLE)
+ # show new display
+ self.histogramFrame.Show()
+
+ self.histogramFrame.SetHistLayer(['d.histogram', 'map=%s' % mapLayer.name])
+ self.histogramFrame.HistWindow.UpdateHist()
+ self.histogramFrame.Refresh()
+ self.histogramFrame.Update()
+
+ return True
+
+ def OnStartEditing (self, event):
+ """
+ Start editing vector map layer requested by the user
+ """
+ try:
+ maplayer = self.GetPyData(self.layer_selected)[0]['maplayer']
+ except:
+ event.Skip()
+ return
+
+ if not self.mapdisplay.toolbars['vdigit']: # enable tool
+ self.mapdisplay.AddToolbar("digit")
+ else: # tool already enabled
+ pass
+
+ # mark layer as 'edited'
+ self.mapdisplay.toolbars['vdigit'].StartEditing (maplayer)
+
+ def OnStopEditing (self, event):
+ """
+ Stop editing the current vector map layer
+ """
+ try:
+ maplayer = self.GetPyData(self.layer_selected)[0]['maplayer']
+ except:
+ event.Skip()
+ return
+
+ self.mapdisplay.toolbars['vdigit'].OnExit()
+ self.mapdisplay.imgVectorMap = None
+
+ def OnPopupProperties (self, event):
+ """Popup properties dialog"""
+ self.PropertiesDialog(self.layer_selected)
+
+ def OnPopupOpacityLevel(self, event):
+ """Popup opacity level indicator"""
+ if not self.GetPyData(self.layer_selected)[0]['ctrl']:
+ return
+
+ win = self.FindWindowById(self.GetPyData(self.layer_selected)[0]['ctrl'])
+ type = win.GetName()
+
+ self.layer_selected.DeleteWindow()
+
+ opacity = self.GetPyData(self.layer_selected)[0]['maplayer'].GetOpacity()
+ if type == 'staticText':
+ ctrl = wx.SpinCtrl(self, id=wx.ID_ANY, value="",
+ style=wx.SP_ARROW_KEYS, initial=100, min=0, max=100,
+ name='spinCtrl')
+ ctrl.SetValue(opacity)
+ self.Bind(wx.EVT_SPINCTRL, self.OnOpacity, ctrl)
+ else:
+ ctrl = wx.StaticText(self, id=wx.ID_ANY,
+ name='staticText')
+ if opacity < 100:
+ ctrl.SetLabel(' (' + str(opacity) + '%)')
+
+ self.GetPyData(self.layer_selected)[0]['ctrl'] = ctrl.GetId()
+ self.layer_selected.SetWindow(ctrl)
+
+ self.RefreshSelected()
+ self.Refresh()
+
+ def OnNvizProperties(self, event):
+ """Nviz-related properties (raster/vector/volume)
+
+ @todo vector/volume
+ """
+ import nviz
+ dlg = nviz.RasterPropertiesDialog(parent=self,
+ map=self.GetPyData(self.layer_selected)[0]['maplayer'].name)
+ dlg.Show()
+
+ def RenameLayer (self, event):
+ """Rename layer"""
+ self.EditLabel(self.layer_selected)
+
+ def AddLayer(self, ltype, lname=None, lchecked=None,
+ lopacity=None, lcmd=None, lgroup=None):
+ """Add new item to the layer tree, create corresponding MapLayer instance.
+ Launch property dialog if needed (raster, vector, etc.)
+
+ Note: lcmd is given as a list
+ """
+
+ self.first = True
+ params = {} # no initial options parameters
+
+ # deselect active item
+ if self.layer_selected:
+ self.SelectItem(self.layer_selected, select=False)
+
+ Debug.msg (3, "LayerTree().AddLayer(): ltype=%s" % (ltype))
+
+ if ltype == 'command':
+ # generic command item
+ ctrl = wx.TextCtrl(self, id=wx.ID_ANY, value='',
+ pos=wx.DefaultPosition, size=(250,25),
+ style=wx.TE_MULTILINE|wx.TE_WORDWRAP)
+ ctrl.Bind(wx.EVT_TEXT_ENTER, self.OnCmdChanged)
+ ctrl.Bind(wx.EVT_TEXT, self.OnCmdChanged)
+ elif ltype == 'group':
+ # group item
+ ctrl = None
+ grouptext = _('Layer group:') + str(self.groupnode)
+ self.groupnode += 1
+ else:
+ # all other items (raster, vector, ...)
+ if UserSettings.Get(group='manager', key='changeOpacityLevel', subkey='enabled'):
+ ctrl = wx.SpinCtrl(self, id=wx.ID_ANY, value="",
+ style=wx.SP_ARROW_KEYS, initial=100, min=0, max=100,
+ name='spinCtrl')
+
+ self.Bind(wx.EVT_SPINCTRL, self.OnOpacity, ctrl)
+ else:
+ ctrl = wx.StaticText(self, id=wx.ID_ANY,
+ name='staticText')
+ # add layer to the layer tree
+ if self.layer_selected and self.layer_selected != self.GetRootItem():
+ if self.GetPyData(self.layer_selected)[0]['type'] != 'group':
+ if lgroup is False:
+ # last child of root
+ layer = self.AppendItem(parentId=self.root,
+ text='', ct_type=1, wnd=ctrl)
+ elif lgroup is None or lgroup is True:
+ # insert item as last child
+ parent = self.GetItemParent(self.layer_selected)
+ # layer = self.InsertItem(parentId=parent, input=self.GetPrevSibling(self.layer_selected),
+ # text='', ct_type=1, wnd=ctrl)
+ layer = self.AppendItem(parentId=parent,
+ text='', ct_type=1, wnd=ctrl)
+
+ else: # group (first child of self.layer_selected)
+ layer = self.PrependItem(parent=self.layer_selected,
+ text='', ct_type=1, wnd=ctrl)
+ self.Expand(self.layer_selected)
+ else: # add first layer to the layer tree (first child of root)
+ layer = self.PrependItem(parent=self.root, text='', ct_type=1, wnd=ctrl)
+
+ # layer is initially unchecked as inactive (beside 'command')
+ # use predefined value if given
+ if lchecked is not None:
+ checked = lchecked
+ else:
+ checked = True
+
+ self.CheckItem(layer, checked=checked)
+
+ # select new item
+ self.SelectItem(layer, select=True)
+ self.layer_selected = layer
+
+ # add text and icons for each layer ltype
+ if ltype == 'raster':
+ self.SetItemImage(layer, self.rast_icon)
+ self.SetItemText(layer, '%s %s' % (_('raster'), _('(double click to set properties)')))
+ elif ltype == 'rgb':
+ self.SetItemImage(layer, self.rgb_icon)
+ self.SetItemText(layer, '%s %s' % (_('RGB'), _('(double click to set properties)')))
+ elif ltype == 'his':
+ self.SetItemImage(layer, self.his_icon)
+ self.SetItemText(layer, '%s %s' % (_('HIS'), _('(double click to set properties)')))
+ elif ltype == 'shaded':
+ self.SetItemImage(layer, self.shaded_icon)
+ self.SetItemText(layer, '%s %s' % (_('Shaded relief'), _('(double click to set properties)')))
+ elif ltype == 'rastnum':
+ self.SetItemImage(layer, self.rnum_icon)
+ self.SetItemText(layer, '%s %s' % (_('raster cell numbers'), _('(double click to set properties)')))
+ elif ltype == 'rastarrow':
+ self.SetItemImage(layer, self.rarrow_icon)
+ self.SetItemText(layer, '%s %s' % (_('raster flow arrows'), _('(double click to set properties)')))
+ elif ltype == 'vector':
+ self.SetItemImage(layer, self.vect_icon)
+ self.SetItemText(layer, '%s %s' % (_('vector'), _('(double click to set properties)')))
+ elif ltype == 'thememap':
+ self.SetItemImage(layer, self.theme_icon)
+ self.SetItemText(layer, '%s %s' % (_('thematic map'), _('(double click to set properties)')))
+ elif ltype == 'themechart':
+ self.SetItemImage(layer, self.chart_icon)
+ self.SetItemText(layer, '%s %s' % (_('thematic charts'), _('(double click to set properties)')))
+ elif ltype == 'grid':
+ self.SetItemImage(layer, self.grid_icon)
+ self.SetItemText(layer, '%s %s' % (_('grid'), _('(double click to set properties)')))
+ elif ltype == 'geodesic':
+ self.SetItemImage(layer, self.geodesic_icon)
+ self.SetItemText(layer, '%s %s' % (_('geodesic line'), _('(double click to set properties)')))
+ elif ltype == 'rhumb':
+ self.SetItemImage(layer, self.rhumb_icon)
+ self.SetItemText(layer, '%s %s' % (_('rhumbline'), _('(double click to set properties)')))
+ elif ltype == 'labels':
+ self.SetItemImage(layer, self.labels_icon)
+ self.SetItemText(layer, '%s %s' % (_('vector labels'), _('(double click to set properties)')))
+ elif ltype == 'command':
+ self.SetItemImage(layer, self.cmd_icon)
+ elif ltype == 'group':
+ self.SetItemImage(layer, self.folder)
+ self.SetItemText(layer, grouptext)
+
+ self.first = False
+
+ if ltype != 'group':
+ if lopacity:
+ opacity = lopacity
+ if UserSettings.Get(group='manager', key='changeOpacityLevel', subkey='enabled'):
+ ctrl.SetValue(int(lopacity * 100))
+ else:
+ if opacity < 1.0:
+ ctrl.SetLabel(' (' + str(int(opacity * 100)) + '%)')
+ else:
+ opacity = 1.0
+ if lcmd and len(lcmd) > 1:
+ cmd = lcmd
+ render = False
+ name = utils.GetLayerNameFromCmd(lcmd)
+ else:
+ cmd = []
+ render = False
+ name = None
+
+ if ctrl:
+ ctrlId = ctrl.GetId()
+ else:
+ ctrlId = None
+
+ # add a data object to hold the layer's command (does not apply to generic command layers)
+ self.SetPyData(layer, ({'cmd': cmd,
+ 'type' : ltype,
+ 'ctrl' : ctrlId,
+ 'maplayer' : None,
+ 'prowin' : None},
+ None))
+
+ maplayer = self.Map.AddLayer(type=ltype, command=self.GetPyData(layer)[0]['cmd'], name=name,
+ l_active=checked, l_hidden=False,
+ l_opacity=opacity, l_render=render)
+ self.GetPyData(layer)[0]['maplayer'] = maplayer
+
+ # run properties dialog if no properties given
+ if len(cmd) == 0:
+ self.PropertiesDialog(layer, show=True)
+
+ else: # group
+ self.SetPyData(layer, ({'cmd': None,
+ 'type' : ltype,
+ 'ctrl' : None,
+ 'maplayer' : None,
+ 'prowin' : None},
+ None))
+
+ # use predefined layer name if given
+ if lname:
+ if ltype != 'command':
+ self.SetItemText(layer, lname)
+ else:
+ ctrl.SetValue(lname)
+
+ # updated progress bar range (mapwindow statusbar)
+ if checked is True:
+ self.mapdisplay.onRenderGauge.SetRange(len(self.Map.GetListOfLayers(l_active=True)))
+
+ # layer.SetHeight(TREE_ITEM_HEIGHT)
+
+ return layer
+
+ def PropertiesDialog (self, layer, show=True):
+ """Launch the properties dialog"""
+ if self.GetPyData(layer)[0].has_key('propwin') and \
+ self.GetPyData(layer)[0]['propwin'] is not None:
+ # recycle GUI dialogs
+ if self.GetPyData(layer)[0]['propwin'].IsShown():
+ self.GetPyData(layer)[0]['propwin'].SetFocus()
+ else:
+ self.GetPyData(layer)[0]['propwin'].Show()
+ return
+
+ completed = ''
+ params = self.GetPyData(layer)[1]
+ ltype = self.GetPyData(layer)[0]['type']
+
+ Debug.msg (3, "LayerTree.PropertiesDialog(): ltype=%s" % \
+ ltype)
+
+ if self.GetPyData(layer)[0]['cmd']:
+ cmdValidated = menuform.GUI().ParseCommand(self.GetPyData(layer)[0]['cmd'],
+ completed=(self.GetOptData,layer,params),
+ parentframe=self, show=show)
+ self.GetPyData(layer)[0]['cmd'] = cmdValidated
+ elif ltype == 'raster':
+ cmd = ['d.rast']
+ if UserSettings.Get(group='cmd', key='rasterOverlay', subkey='enabled'):
+ cmd.append('-o')
+ menuform.GUI().ParseCommand(cmd, completed=(self.GetOptData,layer,params),
+ parentframe=self)
+ elif ltype == 'rgb':
+ menuform.GUI().ParseCommand(['d.rgb'], completed=(self.GetOptData,layer,params),
+ parentframe=self)
+ elif ltype == 'his':
+ menuform.GUI().ParseCommand(['d.his'], completed=(self.GetOptData,layer,params),
+ parentframe=self)
+ elif ltype == 'shaded':
+ menuform.GUI().ParseCommand(['d.shadedmap'], completed=(self.GetOptData,layer,params),
+ parentframe=self)
+ elif ltype == 'rastarrow':
+ menuform.GUI().ParseCommand(['d.rast.arrow'], completed=(self.GetOptData,layer,params),
+ parentframe=self)
+ elif ltype == 'rastnum':
+ menuform.GUI().ParseCommand(['d.rast.num'], completed=(self.GetOptData,layer,params),
+ parentframe=self)
+ elif ltype == 'vector':
+ menuform.GUI().ParseCommand(['d.vect'], completed=(self.GetOptData,layer,params),
+ parentframe=self)
+ elif ltype == 'thememap':
+ # -s flag requested, otherwise only first thematic category is displayed
+ # should be fixed by C-based d.thematic.* modules
+ menuform.GUI().ParseCommand(['d.vect.thematic', '-s'],
+ completed=(self.GetOptData,layer,params),
+ parentframe=self)
+ elif ltype == 'themechart':
+ menuform.GUI().ParseCommand(['d.vect.chart'],
+ completed=(self.GetOptData,layer,params),
+ parentframe=self)
+ elif ltype == 'grid':
+ menuform.GUI().ParseCommand(['d.grid'], completed=(self.GetOptData,layer,params),
+ parentframe=self)
+ elif ltype == 'geodesic':
+ menuform.GUI().ParseCommand(['d.geodesic'], completed=(self.GetOptData,layer,params),
+ parentframe=self)
+ elif ltype == 'rhumb':
+ menuform.GUI().ParseCommand(['d.rhumbline'], completed=(self.GetOptData,layer,params),
+ parentframe=self)
+ elif ltype == 'labels':
+ menuform.GUI().ParseCommand(['d.labels'], completed=(self.GetOptData,layer,params),
+ parentframe=self)
+ elif ltype == 'cmdlayer':
+ pass
+ elif ltype == 'group':
+ pass
+
+ def OnActivateLayer(self, event):
+ """Click on the layer item.
+ Launch property dialog, or expand/collapse group of items, etc."""
+
+ layer = event.GetItem()
+ self.layer_selected = layer
+
+ self.PropertiesDialog (layer)
+
+ if self.GetPyData(layer)[0]['type'] == 'group':
+ if self.IsExpanded(layer):
+ self.Collapse(layer)
+ else:
+ self.Expand(layer)
+
+ def OnDeleteLayer(self, event):
+ """Remove selected layer item from the layer tree"""
+
+ item = event.GetItem()
+
+ try:
+ item.properties.Close(True)
+ except:
+ pass
+
+ if item != self.root:
+ Debug.msg (3, "LayerTree.OnDeleteLayer(): name=%s" % \
+ (self.GetItemText(item)))
+ else:
+ self.root = None
+
+ # unselect item
+ self.Unselect()
+ self.layer_selected = None
+
+ try:
+ if self.GetPyData(item)[0]['type'] != 'group':
+ self.Map.DeleteLayer( self.GetPyData(item)[0]['maplayer'])
+ except:
+ pass
+
+ # redraw map if auto-rendering is enabled
+ if self.mapdisplay.autoRender.GetValue():
+ self.mapdisplay.OnRender(None)
+
+ if self.mapdisplay.toolbars['vdigit']:
+ self.mapdisplay.toolbars['vdigit'].UpdateListOfLayers (updateTool=True)
+
+ # update progress bar range (mapwindow statusbar)
+ self.mapdisplay.onRenderGauge.SetRange(len(self.Map.GetListOfLayers(l_active=True)))
+
+ event.Skip()
+
+ def OnLayerChecked(self, event):
+ """Enable/disable given layer item"""
+ item = event.GetItem()
+ checked = item.IsChecked()
+
+ if self.drag == False and self.first == False:
+ # change active parameter for item in layers list in render.Map
+ if self.GetPyData(item)[0]['type'] == 'group':
+ child, cookie = self.GetFirstChild(item)
+ while child:
+ self.CheckItem(child, checked)
+ self.Map.ChangeLayerActive(self.GetPyData(child)[0]['maplayer'], checked)
+ child = self.GetNextSibling(child)
+ else:
+ self.Map.ChangeLayerActive(self.GetPyData(item)[0]['maplayer'], checked)
+
+ # update progress bar range (mapwindow statusbar)
+ self.mapdisplay.onRenderGauge.SetRange(len(self.Map.GetListOfLayers(l_active=True)))
+
+ # redraw map if auto-rendering is enabled
+ if self.mapdisplay.autoRender.GetValue():
+ self.mapdisplay.OnRender(None)
+
+ def OnCmdChanged(self, event):
+ """Change command string"""
+ ctrl = event.GetEventObject().GetId()
+ cmd = event.GetString()
+ layer = None
+
+ layer = self.GetFirstVisibleItem()
+
+ while layer and layer.IsOk():
+ if self.GetPyData(layer)[0]['ctrl'] == ctrl:
+ break
+
+ layer = self.GetNextVisible(layer)
+
+ # change parameters for item in layers list in render.Map
+ if layer and self.drag == False:
+ self.ChangeLayer(layer)
+ self.GetPyData(layer)[0]['cmd'] = cmd.split(' ')
+ maplayer = self.GetPyData(layer)[0]['maplayer']
+ for option in maplayer.GetCmd():
+ if 'map=' in option:
+ mapname = option.split('=')[1]
+ self.Map.ChangeLayerName(maplayer, mapname)
+
+ event.Skip()
+
+ def OnOpacity(self, event):
+ """
+ Set opacity level for map layer
+ """
+ Debug.msg (3, "LayerTree.OnOpacity(): %s" % event.GetInt())
+
+ ctrl = event.GetEventObject().GetId()
+ maplayer = None
+
+ vislayer = self.GetFirstVisibleItem()
+
+ layer = None
+ for item in range(0, self.GetCount()):
+ if self.GetPyData(vislayer)[0]['ctrl'] == ctrl:
+ layer = vislayer
+
+ if not self.GetNextVisible(vislayer):
+ break
+ else:
+ vislayer = self.GetNextVisible(vislayer)
+
+ if layer:
+ maplayer = self.GetPyData(layer)[0]['maplayer']
+
+ opacity = event.GetInt() / 100.
+ # change opacity parameter for item in layers list in render.Map
+ if maplayer and self.drag == False:
+ self.Map.ChangeOpacity(maplayer, opacity)
+
+ # redraw map if auto-rendering is enabled
+ if self.mapdisplay.autoRender.GetValue():
+ self.mapdisplay.OnRender(None)
+
+ def OnChangeSel(self, event):
+ """Selection changed"""
+ oldlayer = event.GetOldItem()
+ layer = event.GetItem()
+ self.layer_selected = layer
+ try:
+ self.RefreshLine(oldlayer)
+ self.RefreshLine(layer)
+ except:
+ pass
+
+ def OnCollapseNode(self, event):
+ """
+ Collapse node
+ """
+ if self.GetPyData(self.layer_selected)[0]['type'] == 'group':
+ self.SetItemImage(self.layer_selected, self.folder)
+
+ def OnExpandNode(self, event):
+ """
+ Expand node
+ """
+ self.layer_selected = event.GetItem()
+ if self.GetPyData(self.layer_selected)[0]['type'] == 'group':
+ self.SetItemImage(self.layer_selected, self.folder_open)
+
+ def OnBeginDrag(self, event):
+ """
+ Drag and drop of tree nodes
+ """
+
+ item = event.GetItem()
+ Debug.msg (3, "LayerTree.OnBeginDrag(): layer=%s" % \
+ (self.GetItemText(item)))
+
+ event.Allow()
+ self.drag = True
+ self.DoSelectItem(item, unselect_others=True)
+
+ # save everthing associated with item to drag
+ self.dragItem = item
+
+ def RecreateItem (self, event, oldItem, parent=None):
+ """
+ Recreate item (needed for OnEndDrag())
+ """
+ Debug.msg (4, "LayerTree.RecreateItem(): layer=%s" % \
+ self.GetItemText(oldItem))
+
+ # fetch data (olditem)
+ text = self.GetItemText(oldItem)
+ image = self.GetItemImage(oldItem, 0)
+ if self.GetPyData(oldItem)[0]['ctrl']:
+ oldctrl = self.FindWindowById(self.GetPyData(oldItem)[0]['ctrl'])
+ else:
+ oldctrl = None
+ checked = self.IsItemChecked(oldItem)
+
+ # recreate spin/text control for layer
+ if self.GetPyData(oldItem)[0]['type'] == 'command':
+ newctrl = wx.TextCtrl(self, id=wx.ID_ANY, value='',
+ pos=wx.DefaultPosition, size=(250,25),
+ style=wx.TE_MULTILINE|wx.TE_WORDWRAP)
+ try:
+ newctrl.SetValue(self.GetPyData(oldItem)[0]['maplayer'].GetCmd(string=True))
+ except:
+ pass
+ newctrl.Bind(wx.EVT_TEXT_ENTER, self.OnCmdChanged)
+ newctrl.Bind(wx.EVT_TEXT, self.OnCmdChanged)
+ elif self.GetPyData(oldItem)[0]['type'] == 'group' or oldctrl is None:
+ newctrl = None
+ else:
+ opacity = self.GetPyData(oldItem)[0]['maplayer'].GetOpacity()
+ if oldctrl.GetName() == 'staticText':
+ newctrl = wx.StaticText(self, id=wx.ID_ANY,
+ name='staticText')
+ if opacity < 100:
+ newctrl.SetLabel(' (' + str(opacity) + '%)')
+ else:
+ newctrl = wx.SpinCtrl(self, id=wx.ID_ANY, value="",
+ style=wx.SP_ARROW_KEYS, min=0, max=100,
+ name='spinCtrl')
+ newctrl.SetValue(opacity)
+ self.Bind(wx.EVT_SPINCTRL, self.OnOpacity, newctrl)
+
+ # decide where to put new layer and put it there
+ if not parent:
+ flag = self.HitTest(event.GetPoint())[1]
+ else:
+ flag = 0
+
+ if self.GetPyData(oldItem)[0]['type'] == 'group':
+ windval = None
+ data = None
+ else:
+ windval = self.GetPyData(self.layer_selected)[0]['maplayer'].GetOpacity()
+ data = self.GetPyData(oldItem)
+
+ # create GenericTreeItem instance
+ if flag & wx.TREE_HITTEST_ABOVE:
+ newItem = self.PrependItem(self.root, text=text, \
+ ct_type=1, wnd=newctrl, image=image, \
+ data=data)
+ elif (flag & wx.TREE_HITTEST_BELOW) or (flag & wx.TREE_HITTEST_NOWHERE) \
+ or (flag & wx.TREE_HITTEST_TOLEFT) or (flag & wx.TREE_HITTEST_TORIGHT):
+ newItem = self.AppendItem(self.root, text=text, \
+ ct_type=1, wnd=newctrl, image=image, \
+ data=data)
+ else:
+ if parent:
+ afteritem = parent
+ else:
+ afteritem = event.GetItem()
+
+ if self.GetPyData(afteritem)[0]['type'] == 'group':
+ parent = afteritem
+ newItem = self.AppendItem(parent, text=text, \
+ ct_type=1, wnd=newctrl, image=image, \
+ data=data)
+ self.Expand(afteritem)
+ else:
+ parent = self.GetItemParent(afteritem)
+ newItem = self.InsertItem(parent, afteritem, text=text, \
+ ct_type=1, wnd=newctrl, image=image, \
+ data=data)
+
+ # add layer at new position
+ self.SetPyData(newItem, self.GetPyData(oldItem))
+ if newctrl:
+ self.GetPyData(newItem)[0]['ctrl'] = newctrl.GetId()
+ else:
+ self.GetPyData(newItem)[0]['ctrl'] = None
+
+ self.CheckItem(newItem, checked=checked)
+
+ # newItem.SetHeight(TREE_ITEM_HEIGHT)
+
+ return newItem
+
+ def OnEndDrag(self, event):
+ """
+ Insert copy of layer in new
+ position and delete original at old position
+ """
+
+ self.drag = True
+ try:
+ old = self.dragItem # make sure this member exists
+ except:
+ return
+
+ Debug.msg (4, "LayerTree.OnEndDrag(): layer=%s" % \
+ (self.GetItemText(self.dragItem)))
+
+ newItem = self.RecreateItem (event, self.dragItem)
+
+ if self.GetPyData(newItem)[0]['type'] == 'group':
+ (child, cookie) = self.GetFirstChild(self.dragItem)
+ if child:
+ while child:
+ self.RecreateItem(event, child, parent=newItem)
+ self.Delete(child)
+ child = self.GetNextChild(old, cookie)[0]
+
+ self.Expand(newItem)
+
+ # delete layer at original position
+ try:
+ self.Delete(old) # entry in render.Map layers list automatically deleted by OnDeleteLayer handler
+ except AttributeError:
+ # FIXME being ugly (item.SetWindow(None))
+ pass
+
+ # reorder layers in render.Map to match new order after drag and drop
+ self.ReorderLayers()
+
+ # select new item
+ self.SelectItem(newItem)
+
+ # completed drag and drop
+ self.drag = False
+
+ def GetOptData(self, dcmd, layer, params, propwin):
+ """Process layer data"""
+
+ # set layer text to map name
+ if dcmd:
+ mapname = utils.GetLayerNameFromCmd(dcmd)
+ self.SetItemText(layer, mapname)
+
+ # update layer data
+ if params:
+ self.SetPyData(layer, (self.GetPyData(layer)[0], params))
+ if dcmd:
+ self.GetPyData(layer)[0]['cmd'] = dcmd
+ self.GetPyData(layer)[0]['propwin'] = propwin
+
+ # check layer as active
+ # self.CheckItem(layer, checked=True)
+
+ # change parameters for item in layers list in render.Map
+ self.ChangeLayer(layer)
+
+ def ReorderLayers(self):
+ """Add commands from data associated with
+ any valid layers (checked or not) to layer list in order to
+ match layers in layer tree."""
+
+ # make a list of visible layers
+ treelayers = []
+
+ vislayer = self.GetFirstVisibleItem()
+
+ if not vislayer:
+ return
+
+ itemList = ""
+
+ for item in range(0, self.GetCount()):
+ itemList += self.GetItemText(vislayer) + ','
+ if self.GetPyData(vislayer)[0]['type'] != 'group':
+ treelayers.append(self.GetPyData(vislayer)[0]['maplayer'])
+
+ if not self.GetNextVisible(vislayer):
+ break
+ else:
+ vislayer = self.GetNextVisible(vislayer)
+
+ Debug.msg (4, "LayerTree.ReoderLayers(): items=%s" % \
+ (itemList))
+
+ # reorder map layers
+ treelayers.reverse()
+ self.Map.ReorderLayers(treelayers)
+
+ def ChangeLayer(self, item):
+ """Change layer"""
+
+ type = self.GetPyData(item)[0]['type']
+
+ if type == 'command':
+ win = self.FindWindowById(self.GetPyData(item)[0]['ctrl'])
+ if win.GetValue() != None:
+ cmdlist = win.GetValue().split(' ')
+ opac = 1.0
+ chk = self.IsItemChecked(item)
+ hidden = not self.IsVisible(item)
+ elif type != 'group':
+ if self.GetPyData(item)[0] is not None:
+ cmdlist = self.GetPyData(item)[0]['cmd']
+ opac = self.GetPyData(item)[0]['maplayer'].GetOpacity(float=True)
+ chk = self.IsItemChecked(item)
+ hidden = not self.IsVisible(item)
+ maplayer = self.Map.ChangeLayer(layer=self.GetPyData(item)[0]['maplayer'], type=type,
+ command=cmdlist, name=self.GetItemText(item),
+ l_active=chk, l_hidden=hidden, l_opacity=opac, l_render=False)
+
+ self.GetPyData(item)[0]['maplayer'] = maplayer
+
+ # if digitization tool enabled -> update list of available vector map layers
+ if self.mapdisplay.toolbars['vdigit']:
+ self.mapdisplay.toolbars['vdigit'].UpdateListOfLayers(updateTool=True)
+
+ # redraw map if auto-rendering is enabled
+ if self.mapdisplay.autoRender.GetValue():
+ self.mapdisplay.OnRender(None)
+
+ # item.SetHeight(TREE_ITEM_HEIGHT)
+
+ def OnCloseWindow(self, event):
+ pass
+ # self.Map.Clean()
+
+ def FindItemByData(self, key, value):
+ """Find item based on key and value (see PyData[0])"""
+ item = self.GetFirstChild(self.root)[0]
+ return self.__FindSubItemByData(item, key, value)
+
+ def __FindSubItemByData(self, item, key, value):
+ """Support method for FindItemByValue"""
+ while item and item.IsOk():
+ itemValue = self.GetPyData(item)[0][key]
+ if value == itemValue:
+ return item
+ if self.GetPyData(item)[0]['type'] == 'group':
+ subItem = self.GetFirstChild(item)[0]
+ found = self.__FindSubItemByData(subItem, key, value)
+ if found:
+ return found
+ item = self.GetNextSibling(item)
+
+ return None
More information about the grass-commit
mailing list