[GRASS-SVN] r53437 - in grass-addons/grass7/gui/wxpython: . wx.animation wx.animation/animation

svn_grass at osgeo.org svn_grass at osgeo.org
Wed Oct 17 05:24:38 PDT 2012


Author: annakrat
Date: 2012-10-17 05:24:37 -0700 (Wed, 17 Oct 2012)
New Revision: 53437

Added:
   grass-addons/grass7/gui/wxpython/wx.animation/
   grass-addons/grass7/gui/wxpython/wx.animation/animation/
   grass-addons/grass7/gui/wxpython/wx.animation/animation/animation.py
   grass-addons/grass7/gui/wxpython/wx.animation/animation/controller.py
   grass-addons/grass7/gui/wxpython/wx.animation/animation/dialogs.py
   grass-addons/grass7/gui/wxpython/wx.animation/animation/frame.py
   grass-addons/grass7/gui/wxpython/wx.animation/animation/mapwindow.py
   grass-addons/grass7/gui/wxpython/wx.animation/animation/nviztask.py
   grass-addons/grass7/gui/wxpython/wx.animation/animation/temporal_manager.py
   grass-addons/grass7/gui/wxpython/wx.animation/animation/toolbars.py
   grass-addons/grass7/gui/wxpython/wx.animation/animation/utils.py
   grass-addons/grass7/gui/wxpython/wx.animation/wxGUI.Animation.html
   grass-addons/grass7/gui/wxpython/wx.animation/wxGUI_animation_tool.jpg
Log:
wxGUI: animation tool added

Added: grass-addons/grass7/gui/wxpython/wx.animation/animation/animation.py
===================================================================
--- grass-addons/grass7/gui/wxpython/wx.animation/animation/animation.py	                        (rev 0)
+++ grass-addons/grass7/gui/wxpython/wx.animation/animation/animation.py	2012-10-17 12:24:37 UTC (rev 53437)
@@ -0,0 +1,185 @@
+"""!
+ at package animation.animation
+
+ at brief Animation class controls frame order
+
+Classes:
+ - animation::Animation
+
+(C) 2012 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.
+
+ at author Anna Kratochvilova <kratochanna gmail.com>
+"""
+
+import wx
+from utils import Orientation, ReplayMode
+
+class Animation(wx.EvtHandler):
+    """!Animation class specifies which frame to show at which instance."""
+    def __init__(self):
+        wx.EvtHandler.__init__(self)
+
+        self.currentIndex = 0
+        self.frames = []
+        # states
+        self.orientation = Orientation.FORWARD
+        self.replayMode = ReplayMode.ONESHOT
+        
+        self.callbackUpdateFrame = None
+        self.callbackEndAnimation = None
+        self.callbackOrientationChanged = None
+
+        self.isActive = False
+
+    def IsActive(self):
+        """!Returns if the animation is active or not"""
+        return self.isActive
+
+    def SetActive(self, active):
+        self.isActive = active
+
+    def SetFrames(self, frames):
+        """!Sets animation frames.
+
+        @param frames list of strings
+        """
+        self.frames = frames
+
+    def GetCount(self):
+        """!Get frame count."""
+        return len(self.frames)
+
+    count = property(fget = GetCount)
+
+    def GetReplayMode(self):
+        """!Returns replay mode (loop)."""
+        return self._replayMode
+
+    def SetReplayMode(self, mode):
+        self._replayMode = mode
+
+    replayMode = property(fset = SetReplayMode, fget = GetReplayMode)
+
+    def GetOrientation(self):
+        return self._orientation
+
+    def SetOrientation(self, mode):
+        self._orientation = mode
+
+    orientation = property(fset = SetOrientation, fget = GetOrientation)
+
+    def SetCallbackUpdateFrame(self, callback):
+        """!Sets function to be called when updating frame."""
+        self.callbackUpdateFrame = callback
+
+    def SetCallbackEndAnimation(self, callback):
+        """!Sets function to be called when animation ends."""
+        self.callbackEndAnimation = callback
+
+    def SetCallbackOrientationChanged(self, callback):
+        """!Sets function to be called when orientation changes."""
+        self.callbackOrientationChanged = callback
+
+    def Start(self):
+        if not self.IsActive():
+            return 
+
+    def Pause(self, paused):
+        if not self.IsActive():
+            return
+
+    def Stop(self):
+        if not self.IsActive():
+            return
+        self.currentIndex = 0
+        self.callbackEndAnimation(self.currentIndex, self.frames[self.currentIndex])
+
+    def _arrivedToEnd(self):
+        """!Decides which action to do after animation end (stop, repeat)."""
+        if not self.IsActive():
+            return
+        if self.replayMode == ReplayMode.ONESHOT:
+            self.Stop()
+
+        if self.orientation == Orientation.FORWARD:
+            if self.replayMode == ReplayMode.REPEAT:
+                self.currentIndex = 0
+            elif self.replayMode == ReplayMode.REVERSE:
+                self.orientation = Orientation.BACKWARD
+                self.currentIndex = self.count - 2 # -1
+                self.callbackOrientationChanged(Orientation.BACKWARD)
+        else:
+            if self.replayMode == ReplayMode.REPEAT:
+                self.currentIndex = self.count - 1
+            elif self.replayMode == ReplayMode.REVERSE:
+                self.orientation = Orientation.FORWARD
+                self.currentIndex = 1 # 0
+                self.callbackOrientationChanged(Orientation.FORWARD)
+
+    def Update(self):
+        """!Updates frame."""
+        if not self.IsActive():
+            return
+
+        self.callbackUpdateFrame(self.currentIndex, self.frames[self.currentIndex])
+        if self.orientation == Orientation.FORWARD:
+            self.currentIndex += 1
+            if self.currentIndex == self.count:
+                self._arrivedToEnd()
+        else:
+            self.currentIndex -= 1
+            if self.currentIndex == -1:
+                self._arrivedToEnd()
+                
+    def FrameChangedFromOutside(self, index):
+        """!Let the animation know that frame was changed from outside."""
+        if not self.IsActive():
+            return
+        self.currentIndex = index
+        self.callbackUpdateFrame(self.currentIndex, self.frames[self.currentIndex])
+
+    def PreviousFrameIndex(self):
+        if not self.IsActive():
+            return
+        if self.orientation == Orientation.FORWARD:
+            self.currentIndex -= 1
+            if self.currentIndex == -1:
+                self.currentIndex = 0
+        else:
+            self.currentIndex += 1
+            if self.currentIndex == self.count:
+                self.currentIndex = self.count - 1
+
+    def NextFrameIndex(self):
+        if not self.IsActive():
+            return
+        if self.orientation == Orientation.FORWARD:
+            self.currentIndex += 1
+            if self.currentIndex == self.count:
+                self.currentIndex = self.count - 1
+        else:
+            self.currentIndex -= 1
+            if self.currentIndex == -1:
+                self.currentIndex = 0
+
+#def test():
+#    import wx
+#    app = wx.PySimpleApp()
+#    a = Animation()
+#
+#
+#    frame = wx.Frame(None)
+#    frame.Show()
+#
+#    a.SetReplayMode(ReplayMode.REVERSE)
+#    a.Start()
+#    app.MainLoop()
+#
+#
+#if __name__ == '__main__':
+#    test()
+
+

Added: grass-addons/grass7/gui/wxpython/wx.animation/animation/controller.py
===================================================================
--- grass-addons/grass7/gui/wxpython/wx.animation/animation/controller.py	                        (rev 0)
+++ grass-addons/grass7/gui/wxpython/wx.animation/animation/controller.py	2012-10-17 12:24:37 UTC (rev 53437)
@@ -0,0 +1,477 @@
+"""!
+ at package animation.controller
+
+ at brief Animations management
+
+Classes:
+ - controller::AnimationController
+
+(C) 2012 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.
+
+ at author Anna Kratochvilova <kratochanna gmail.com>
+"""
+import wx
+
+from core.gcmd import GException, GError, GMessage
+import grass.script as grass
+
+from temporal_manager import TemporalManager
+from dialogs import InputDialog, EditDialog, AnimationData
+from utils import TemporalMode, Orientation
+
+class AnimationController(wx.EvtHandler):
+    def __init__(self, frame, sliders, animations, mapwindows, providers, bitmapPool):
+        wx.EvtHandler.__init__(self)
+
+        self.mapwindows = mapwindows
+
+        self.frame = frame
+        self.sliders = sliders
+        self.slider = self.sliders['temporal']
+        self.animationToolbar = None
+
+        self.temporalMode = None
+        self.animationData = []
+
+        self.timer = wx.Timer(self, id = wx.NewId())
+
+        self.animations = animations
+        self.bitmapPool = bitmapPool
+        self.bitmapProviders = providers
+        for anim, win, provider in zip(self.animations, self.mapwindows, self.bitmapProviders):
+            anim.SetCallbackUpdateFrame(lambda index, dataId, win = win, provider = provider : self.UpdateFrame(index, win, provider, dataId))
+            anim.SetCallbackEndAnimation(lambda index, dataId, win = win, provider = provider: self.UpdateFrameEnd(index, win, provider, dataId))
+            anim.SetCallbackOrientationChanged(self.OrientationChangedInReverseMode)
+
+        for slider in self.sliders.values():
+            slider.SetCallbackSliderChanging(self.SliderChanging)
+            slider.SetCallbackSliderChanged(self.SliderChanged)
+            slider.SetCallbackFrameIndexChanged(self.ChangeFrame)
+
+        self.runAfterReleasingSlider = None
+
+        self.temporalManager = TemporalManager()
+        self.Bind(wx.EVT_TIMER, self.OnTimerTick, self.timer)
+
+        self.timeTick = 200
+
+    def SetAnimationToolbar(self, toolbar):
+        self.animationToolbar = toolbar
+
+    def GetTimeTick(self):
+        return self._timeTick
+
+    def SetTimeTick(self, value):
+        self._timeTick = value
+        if self.timer.IsRunning():
+            self.timer.Stop()
+            self.timer.Start(self._timeTick)
+        self.DisableSliderIfNeeded()
+
+    timeTick = property(fget = GetTimeTick, fset = SetTimeTick)
+        
+    def OnTimerTick(self, event):
+        for anim in self.animations:
+            anim.Update()
+
+    def StartAnimation(self):
+        # if self.timer.IsRunning():
+        #     self.timer.Stop()
+
+        for anim in self.animations:
+            if self.timer.IsRunning():
+                anim.NextFrameIndex()
+            anim.Start()
+        if not self.timer.IsRunning():
+            self.timer.Start(self.timeTick)
+            self.DisableSliderIfNeeded()
+
+    def PauseAnimation(self, paused):
+        if paused:
+            if self.timer.IsRunning():
+                self.timer.Stop()
+                self.DisableSliderIfNeeded()
+        else:
+            if not self.timer.IsRunning():
+                self.timer.Start(self.timeTick)
+                self.DisableSliderIfNeeded()
+        
+        for anim in self.animations:
+            anim.Pause(paused)
+
+    def EndAnimation(self):
+        if self.timer.IsRunning():
+            self.timer.Stop()
+            self.DisableSliderIfNeeded()
+
+        for anim in self.animations:
+            anim.Stop()
+
+    def UpdateFrameEnd(self, index, win, provider, dataId):
+        if self.timer.IsRunning():
+            self.timer.Stop()
+            self.DisableSliderIfNeeded()
+
+        self.animationToolbar.Stop()
+        
+        self.UpdateFrame(index, win, provider, dataId)
+
+    def UpdateFrame(self, index, win, provider, dataId):
+        bitmap = provider.GetBitmap(dataId)
+        if dataId is None:
+            dataId = ''
+        win.DrawBitmap(bitmap, dataId)
+        # self.frame.SetStatusText(dataId)
+        self.slider.UpdateFrame(index)
+        self.UpdateReloadStatus()
+
+    def SliderChanging(self, index):
+        if self.runAfterReleasingSlider is None:
+            self.runAfterReleasingSlider = self.timer.IsRunning()
+        self.PauseAnimation(True)
+        self.ChangeFrame(index)
+
+    def SliderChanged(self):
+        if self.runAfterReleasingSlider:
+            self.PauseAnimation(False)
+        self.runAfterReleasingSlider = None
+
+    def ChangeFrame(self, index):
+        for anim in self.animations:
+            anim.FrameChangedFromOutside(index)
+
+    def DisableSliderIfNeeded(self):
+        if self.timer.IsRunning() and self._timeTick < 100:
+            self.slider.EnableSlider(False)
+        else:
+            self.slider.EnableSlider(True)
+            
+
+    def OrientationChangedInReverseMode(self, mode):
+        if mode == Orientation.FORWARD:
+            self.animationToolbar.PlayForward()
+        elif mode == Orientation.BACKWARD:
+            self.animationToolbar.PlayBack()
+
+    def SetReplayMode(self, mode):
+        for anim in self.animations:
+            anim.replayMode = mode
+
+    def SetOrientation(self, mode):
+        for anim in self.animations:
+            anim.orientation = mode
+
+
+    def SetTemporalMode(self, mode):
+        self._temporalMode = mode
+
+    def GetTemporalMode(self):
+        return self._temporalMode
+
+    temporalMode = property(fget = GetTemporalMode, fset = SetTemporalMode)
+
+    def GetTimeGranularity(self):
+        if self.temporalMode == TemporalMode.TEMPORAL:
+            return self.temporalManager.GetGranularity(original = False)
+
+        return None
+
+    def EditAnimations(self):
+        # running = False
+        # if self.timer.IsRunning():
+        #     running = True
+        self.EndAnimation()
+
+        dlg = EditDialog(parent = self.frame, evalFunction = self.EvaluateInput,
+                          animationData = self.animationData, maxAnimations = len(self.animations))
+        if dlg.ShowModal() == wx.ID_CANCEL:
+            dlg.Destroy()
+            return
+        self.animationData, self.temporalMode, self.temporalManager = dlg.GetResult()
+        dlg.Destroy()
+
+        self._setAnimations()
+
+    def AddAnimation(self):
+        # check if we can add more animations
+        found = False
+        indices = [anim.windowIndex for anim in self.animationData]
+        for windowIndex in range(len(self.animations)):
+            if windowIndex not in indices:
+                found = True
+                break
+                
+        if not found:
+            GMessage(parent = self.frame, message = _("Maximum number of animations is %s.") % len(self.animations))
+            return
+
+        # running = False
+        # if self.timer.IsRunning():
+        #     running = True
+        self.EndAnimation()
+        #     self.PauseAnimation(True)
+
+        animData = AnimationData()
+        # number of active animations
+        animationIndex = len([anim for anim in self.animations if anim.IsActive()])
+        animData.SetDefaultValues(windowIndex, animationIndex)
+        dlg = InputDialog(parent = self.frame, mode = 'add', animationData = animData)
+        if dlg.ShowModal() == wx.ID_CANCEL:
+            dlg.Destroy()
+            return
+        dlg.Destroy()
+        # check compatibility
+        if animData.windowIndex in indices:
+            GMessage(parent = self.frame, message = _("More animations are using one window."
+                                                      " Please select different window for each animation."))
+            return
+        try:
+            temporalMode, tempManager = self.EvaluateInput(self.animationData + [animData])
+        except GException, e:
+            GError(parent = self.frame, message = e.value, showTraceback = False)
+            return
+        # if ok, set temporal mode
+        self.temporalMode = temporalMode
+        self.temporalManager = tempManager
+        # add data
+        windowIndex = animData.windowIndex
+        self.animationData.append(animData)
+        self._setAnimations()
+
+    def SetAnimations(self, raster = None, strds = None):
+        """!Set animation data directly.
+
+        @param raster list of lists of raster maps or None
+        @param strds list of strds or None
+        """
+        try:
+            animationData = []
+            for i in range(len(self.animations)):
+                if raster is not None and type(raster[i]) == list and raster[i]:
+                    anim = AnimationData()
+                    anim.SetDefaultValues(i, i)
+                    anim.inputMapType = 'rast'
+                    anim.inputData = ','.join(raster[i])
+                    animationData.append(anim)
+
+                elif strds is not None and strds[i]:
+                    anim = AnimationData()
+                    anim.SetDefaultValues(i, i)
+                    anim.inputMapType = 'strds'
+                    anim.inputData = strds[i]
+                    animationData.append(anim)
+
+        except (GException, ValueError, IOError) as e:
+            GError(parent = self.frame, message = str(e),
+                   showTraceback = False, caption = _("Invalid input"))
+            return
+        try:
+            temporalMode, tempManager = self.EvaluateInput(animationData)
+        except GException, e:
+            GError(parent = self.frame, message = e.value, showTraceback = False)
+            return
+        self.animationData = animationData
+        self.temporalManager = tempManager
+        self.temporalMode = temporalMode
+        self._setAnimations()
+
+    def _setAnimations(self):
+        indices = [anim.windowIndex for anim in self.animationData]
+
+        self._updateWindows(activeIndices = indices)
+
+        if self.temporalMode == TemporalMode.TEMPORAL:
+            timeLabels, mapNamesDict = self.temporalManager.GetLabelsAndMaps()
+        else:
+            timeLabels, mapNamesDict = None, None
+
+        self._updateSlider(timeLabels = timeLabels)
+        self._updateAnimations(activeIndices = indices, mapNamesDict = mapNamesDict)
+        self._updateRegion()
+        self._updateBitmapData()
+        # if running:
+        #     self.PauseAnimation(False)
+        #     # self.StartAnimation()
+        # else:
+        self.EndAnimation()
+
+        self.UpdateReloadStatus()
+
+    def _updateSlider(self, timeLabels = None):
+        if self.temporalMode == TemporalMode.NONTEMPORAL:
+            self.frame.SetSlider('nontemporal')
+            self.slider = self.sliders['nontemporal']
+            frameCount = len(self.animationData[0].mapData) # should be the same for all
+            self.slider.SetFrames(frameCount)
+        elif self.temporalMode == TemporalMode.TEMPORAL:
+            self.frame.SetSlider('temporal')
+            self.slider = self.sliders['temporal']
+            self.slider.SetTemporalType(self.temporalManager.temporalType)
+            self.slider.SetFrames(timeLabels)
+        else:
+            self.frame.SetSlider(None)
+            self.slider = None
+
+    def _updateAnimations(self, activeIndices, mapNamesDict = None):
+        if self.temporalMode == TemporalMode.NONTEMPORAL:
+            for i in range(len(self.animations)):
+                if i not in activeIndices:
+                    self.animations[i].SetActive(False)
+                    continue
+                anim = [anim for anim in self.animationData if anim.windowIndex == i][0]
+                self.animations[i].SetFrames(anim.mapData)
+                self.animations[i].SetActive(True)
+        else:
+            for i in range(len(self.animations)):
+                if i not in activeIndices:
+                    self.animations[i].SetActive(False)
+                    continue
+                anim = [anim for anim in self.animationData if anim.windowIndex == i][0]
+                self.animations[i].SetFrames(mapNamesDict[anim.inputData])
+                self.animations[i].SetActive(True)
+
+    def _updateWindows(self, activeIndices):
+        # add or remove window
+        for windowIndex in range(len(self.animations)):
+            if not self.frame.IsWindowShown(windowIndex) and windowIndex in activeIndices:
+                self.frame.AddWindow(windowIndex)
+            elif self.frame.IsWindowShown(windowIndex) and windowIndex not in activeIndices:
+                self.frame.RemoveWindow(windowIndex)
+
+    def _updateBitmapData(self):
+        # unload data:
+        for prov in self.bitmapProviders:
+            prov.Unload()
+
+        # load data
+        for animData in self.animationData:
+            if animData.viewMode == '2d':
+                self._load2DData(animData)
+            else:
+                self._load3DData(animData)
+
+        # clear bitmapPool
+        usedNames = []
+        for prov in self.bitmapProviders:
+            names = prov.GetDataNames()
+            if names:
+                usedNames.extend(names)
+        self.bitmapPool.Clear(usedNames)
+
+    def _load2DData(self, animationData):
+        prov = self.bitmapProviders[animationData.windowIndex]
+        prov.SetData(datasource = animationData.mapData)
+
+        self.bitmapProviders[animationData.windowIndex].Load()
+
+    def _load3DData(self, animationData):
+        prov = self.bitmapProviders[animationData.windowIndex]
+        prov.SetData(datasource = animationData.GetNvizCommands(), 
+                     dataNames = animationData.mapData, dataType = 'nviz',
+                     suffix = animationData.nvizParameter)
+
+        self.bitmapProviders[animationData.windowIndex].Load()
+
+    def EvaluateInput(self, animationData):
+        stds = 0
+        maps = 0
+        mapCount = set()
+        tempManager = None
+        windowIndex = []
+        for anim in animationData:
+
+            mapCount.add(len(anim.mapData))
+            windowIndex.append(anim.windowIndex)
+
+            if anim.inputMapType in ('rast', 'vect'):
+                maps += 1
+            elif anim.inputMapType in ('strds', 'stvds'):
+                stds += 1
+
+        if maps and stds:
+            temporalMode = TemporalMode.NONTEMPORAL
+        elif maps:
+            temporalMode = TemporalMode.NONTEMPORAL
+        elif stds:
+            temporalMode = TemporalMode.TEMPORAL
+        else:
+            temporalMode = None
+
+        if temporalMode == TemporalMode.NONTEMPORAL:
+            if len(mapCount) > 1:
+                raise GException(_("Inconsistent number of maps, please check input data."))
+        elif temporalMode == TemporalMode.TEMPORAL:
+            tempManager = TemporalManager()
+            # these raise GException:
+            for anim in animationData:
+                if anim.inputMapType not in ('strds', 'stvds'):
+                    continue
+                tempManager.AddTimeSeries(anim.inputData, anim.inputMapType)
+            tempManager.EvaluateInputData()
+
+        return temporalMode, tempManager
+
+    def Reload(self):
+        self.EndAnimation()
+
+        self._updateRegion()
+        activeIndices = [anim.windowIndex for anim in self.animationData]
+        for index in activeIndices:
+            self.bitmapProviders[index].Load(force = True)
+
+        self.EndAnimation()
+
+        self.UpdateReloadStatus()
+
+    def UpdateReloadStatus(self):
+        activeIndices = [anim.windowIndex for anim in self.animationData]
+        for i, win in enumerate(self.mapwindows):
+            if i in activeIndices and win.IsRescaled():
+                self.frame.SetStatusText(_("Reload recommended"), 1)
+                return
+        self.frame.SetStatusText('', 1)
+
+    def _updateRegion(self):
+        grassRegion = grass.region()
+        for anim in self.animationData:
+            if anim.viewMode == '2d':
+                self.mapwindows[anim.windowIndex].SetRegion(grassRegion)
+            else:
+                loadSize = self.bitmapProviders[anim.windowIndex].GetLoadSize() or \
+                           self.mapwindows[anim.windowIndex].GetClientSize()
+                region = {}
+                region['cols'], region['rows'] = loadSize
+                self.mapwindows[anim.windowIndex].SetRegion(region)
+
+
+
+#def test():
+#    import gettext
+#    gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
+#
+#    import grass.script as grass
+#    import wx
+#    app = wx.PySimpleApp()
+#    wx.InitAllImageHandlers()
+#    # app.MainLoop()
+#
+#    bitmaps = {}
+#    rasters = ['elevation.dem']
+#    # rasters = ['streams']
+#    # rasters = grass.read_command("g.mlist", type = 'rast', fs = ',', quiet = True).strip().split(',')
+#
+#    # print nrows, ncols
+#    size = (300,100)
+#    newSize, scale = ComputeScale(size)
+#    # print scale
+#    LoadRasters(rasters = rasters, bitmaps = bitmaps, scale = scale, size = newSize)
+#
+#    for b in bitmaps.keys():
+#        bitmaps[b].SaveFile('/home/anna/testy/ctypes/' + b + '.png', wx.BITMAP_TYPE_PNG)
+#
+#if __name__ == '__main__':
+#
+#    test()

Added: grass-addons/grass7/gui/wxpython/wx.animation/animation/dialogs.py
===================================================================
--- grass-addons/grass7/gui/wxpython/wx.animation/animation/dialogs.py	                        (rev 0)
+++ grass-addons/grass7/gui/wxpython/wx.animation/animation/dialogs.py	2012-10-17 12:24:37 UTC (rev 53437)
@@ -0,0 +1,810 @@
+"""!
+ at package animation.dialogs
+
+ at brief Dialogs for animation management, changing speed of animation
+
+Classes:
+ - dialogs::SpeedDialog
+ - dialogs::InputDialog
+ - dialogs::EditDialog
+ - dialogs::AnimationData
+
+
+(C) 2012 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.
+
+ at author Anna Kratochvilova <kratochanna gmail.com>
+"""
+import os
+import sys
+import wx
+import copy
+import datetime
+from wx.lib.newevent import NewEvent
+import wx.lib.filebrowsebutton as filebrowse
+
+if __name__ == '__main__':
+    sys.path.append(os.path.join(os.environ['GISBASE'], "etc", "gui", "wxpython"))
+
+import grass.temporal as tgis
+
+from core.gcmd import GMessage, GError, GException
+from core import globalvar
+from gui_core import gselect
+from gui_core.dialogs import MapLayersDialog, EVT_APPLY_MAP_LAYERS
+from core.settings import UserSettings
+
+from utils import TemporalMode, validateTimeseriesName, validateMapNames
+from nviztask import NvizTask
+
+SpeedChangedEvent, EVT_SPEED_CHANGED = NewEvent()
+
+class SpeedDialog(wx.Dialog):
+    def __init__(self, parent, title = _("Adjust speed of animation"),
+                 temporalMode = None, minimumDuration = 20, timeGranularity = None,
+                 initialSpeed = 200):#, framesCount = None
+        wx.Dialog.__init__(self, parent = parent, id = wx.ID_ANY, title = title,
+                           style = wx.DEFAULT_DIALOG_STYLE)
+
+        self.minimumDuration = minimumDuration
+        # self.framesCount = framesCount
+        self.defaultSpeed = initialSpeed
+        self.lastAppliedValue = self.defaultSpeed
+        self.lastAppliedValueTemp = self.defaultSpeed
+
+        self._layout()
+
+        self.temporalMode = temporalMode
+        self.timeGranularity = timeGranularity
+
+        self._fillUnitChoice(self.choiceUnits)
+        self.InitTimeSpin(self.defaultSpeed)
+
+    def SetTimeGranularity(self, gran):
+        self._timeGranularity = gran
+
+    def GetTimeGranularity(self):
+        return self._timeGranularity
+
+    timeGranularity = property(fset = SetTimeGranularity, fget = GetTimeGranularity)
+
+    def SetTemporalMode(self, mode):
+        self._temporalMode = mode
+        self._setTemporalMode()
+
+    def GetTemporalMode(self):
+        return self._temporalMode
+
+    temporalMode = property(fset = SetTemporalMode, fget = GetTemporalMode)
+
+    def _layout(self):
+        """!Layout window"""
+        mainSizer = wx.BoxSizer(wx.VERTICAL)
+        #
+        # simple mode
+        #
+        self.nontemporalBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
+                                           label = ' %s ' % _("Simple mode"))
+        box = wx.StaticBoxSizer(self.nontemporalBox, wx.VERTICAL)
+        gridSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
+
+        labelDuration = wx.StaticText(self, id = wx.ID_ANY, label = _("Frame duration:"))
+        labelUnits = wx.StaticText(self, id = wx.ID_ANY, label = _("ms"))
+        self.spinDuration = wx.SpinCtrl(self, id = wx.ID_ANY, min = self.minimumDuration, 
+                                        max = 10000, initial = self.defaultSpeed)
+        # TODO total time
+
+        gridSizer.Add(item = labelDuration, pos = (0, 0), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
+        gridSizer.Add(item = self.spinDuration, pos = (0, 1), flag = wx.ALIGN_CENTER)
+        gridSizer.Add(item = labelUnits, pos = (0, 2), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
+        gridSizer.AddGrowableCol(0)
+
+        box.Add(item = gridSizer, proportion = 1, border = 5, flag = wx.ALL | wx.EXPAND)
+        self.nontemporalSizer = gridSizer
+        mainSizer.Add(item = box, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 5)
+        #
+        # temporal mode
+        #
+        self.temporalBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
+                                        label = ' %s ' % _("Temporal mode"))
+        box = wx.StaticBoxSizer(self.temporalBox, wx.VERTICAL)
+        gridSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
+
+        labelTimeUnit = wx.StaticText(self, id = wx.ID_ANY, label = _("Time unit:"))
+        labelDuration = wx.StaticText(self, id = wx.ID_ANY, label = _("Duration of time unit:"))
+        labelUnits = wx.StaticText(self, id = wx.ID_ANY, label = _("ms"))
+        self.spinDurationTemp = wx.SpinCtrl(self, id = wx.ID_ANY, min = self.minimumDuration,
+                                            max = 10000, initial = self.defaultSpeed)
+        self.choiceUnits = wx.Choice(self, id = wx.ID_ANY)
+
+        # TODO total time
+
+        gridSizer.Add(item = labelTimeUnit, pos = (0, 0), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
+        gridSizer.Add(item = self.choiceUnits, pos = (0, 1), flag = wx.ALIGN_CENTER | wx.EXPAND)
+        gridSizer.Add(item = labelDuration, pos = (1, 0), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
+        gridSizer.Add(item = self.spinDurationTemp, pos = (1, 1), flag = wx.ALIGN_CENTER | wx.EXPAND)
+        gridSizer.Add(item = labelUnits, pos = (1, 2), flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
+        gridSizer.AddGrowableCol(1)
+
+        self.temporalSizer = gridSizer
+        box.Add(item = gridSizer, proportion = 1, border = 5, flag = wx.ALL | wx.EXPAND)
+        mainSizer.Add(item = box, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 5)
+        
+        self.btnOk = wx.Button(self, wx.ID_OK)
+        self.btnApply = wx.Button(self, wx.ID_APPLY)
+        self.btnCancel = wx.Button(self, wx.ID_CANCEL)
+        self.btnOk.SetDefault()
+
+        self.btnOk.Bind(wx.EVT_BUTTON, self.OnOk)
+        self.btnApply.Bind(wx.EVT_BUTTON, self.OnApply)
+        self.btnCancel.Bind(wx.EVT_BUTTON, self.OnCancel)
+        self.Bind(wx.EVT_CLOSE,  self.OnCancel)
+        # button sizer
+        btnStdSizer = wx.StdDialogButtonSizer()
+        btnStdSizer.AddButton(self.btnOk)
+        btnStdSizer.AddButton(self.btnApply)
+        btnStdSizer.AddButton(self.btnCancel)
+        btnStdSizer.Realize()
+        
+        mainSizer.Add(item = btnStdSizer, proportion = 0,
+                      flag = wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT, border = 5)
+
+        self.SetSizer(mainSizer)
+        mainSizer.Fit(self)
+
+    def _setTemporalMode(self):
+        self.nontemporalBox.Enable(self.temporalMode == TemporalMode.NONTEMPORAL)
+        self.temporalBox.Enable(self.temporalMode == TemporalMode.TEMPORAL)
+        for child in self.temporalSizer.GetChildren():
+            child.GetWindow().Enable(self.temporalMode == TemporalMode.TEMPORAL)
+        for child in self.nontemporalSizer.GetChildren():
+            child.GetWindow().Enable(self.temporalMode == TemporalMode.NONTEMPORAL)
+
+        self.Layout()
+
+    def _fillUnitChoice(self, choiceWidget):
+        timeUnitsChoice = [_("year"), _("month"), _("day"), _("hour"), _("minute"), _("second")]
+        timeUnits = ["years", "months", "days", "hours", "minutes", "seconds"]
+        for item, cdata in zip(timeUnitsChoice, timeUnits):
+            choiceWidget.Append(item, cdata)
+
+        if self.temporalMode == TemporalMode.TEMPORAL:
+            unit = self.timeGranularity.split()[1]
+            try:
+                index = timeUnits.index(unit)
+            except ValueError:
+                index = 0
+            choiceWidget.SetSelection(index)
+        else:
+            choiceWidget.SetSelection(0)
+
+    def OnOk(self, event):
+        self._apply()
+        self.OnCancel(None)
+
+    def OnApply(self, event):
+        self._apply()
+
+    def OnCancel(self, event):
+        self.spinDuration.SetValue(self.lastAppliedValue)
+        self.spinDurationTemp.SetValue(self.lastAppliedValueTemp)
+        self.Hide()
+
+    def InitTimeSpin(self, timeTick):
+        if self.temporalMode == TemporalMode.TEMPORAL:
+            index = self.choiceUnits.GetSelection()
+            unit = self.choiceUnits.GetClientData(index)
+            delta = self._timedelta(unit = unit, number = 1)
+            seconds1 = self._total_seconds(delta)
+
+            number, unit = self.timeGranularity.split()
+            number = float(number)
+            delta = self._timedelta(unit = unit, number = number)
+            seconds2 = self._total_seconds(delta)
+            value = timeTick
+            ms = value * seconds1 / float(seconds2)
+            self.spinDurationTemp.SetValue(ms)
+        else:
+            self.spinDuration.SetValue(timeTick)
+
+    def _apply(self):
+        if self.temporalMode == TemporalMode.NONTEMPORAL:
+            ms = self.spinDuration.GetValue()
+            self.lastAppliedValue = self.spinDuration.GetValue()
+        elif self.temporalMode == TemporalMode.TEMPORAL:
+            index = self.choiceUnits.GetSelection()
+            unit = self.choiceUnits.GetClientData(index)
+            delta = self._timedelta(unit = unit, number = 1)
+            seconds1 = self._total_seconds(delta)
+
+
+            number, unit = self.timeGranularity.split()
+            number = float(number)
+            delta = self._timedelta(unit = unit, number = number)
+            seconds2 = self._total_seconds(delta)
+
+            value = self.spinDurationTemp.GetValue()
+            ms = value * seconds2 / float(seconds1)
+            if ms < self.minimumDuration:
+                GMessage(parent = self, message = _("Animation speed is too high."))
+                return
+            self.lastAppliedValueTemp = self.spinDurationTemp.GetValue()
+        else:
+            return
+
+        event = SpeedChangedEvent(ms = ms)
+        wx.PostEvent(self, event)
+
+    def _timedelta(self, unit, number):
+        if unit == "years":
+            delta = datetime.timedelta(days = 365 * number)
+        elif unit == "months":
+            delta = datetime.timedelta(days = 30 * number)
+        elif unit == "days":
+            delta = datetime.timedelta(days = 1 * number)
+        elif unit == "hours":
+            delta = datetime.timedelta(hours = 1 * number)
+        elif unit == "minutes":
+            delta = datetime.timedelta(minutes = 1 * number)
+        elif unit == "seconds":
+            delta = datetime.timedelta(seconds = 1 * number)
+
+        return delta
+
+    def _total_seconds(self, delta):
+        """!timedelta.total_seconds is new in version 2.7.
+        """
+        return delta.seconds + delta.days * 24 * 3600
+
+
+class InputDialog(wx.Dialog):
+    def __init__(self, parent, mode, animationData):
+        wx.Dialog.__init__(self, parent = parent, id = wx.ID_ANY,
+                           style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
+        if mode == 'add':
+            self.SetTitle(_("Add new animation"))
+        elif mode == 'edit':
+            self.SetTitle(_("Edit animation"))
+
+        self.animationData = animationData
+
+        self._layout()
+        self.OnViewMode(event = None)
+
+    def _layout(self):
+        mainSizer = wx.BoxSizer(wx.VERTICAL)
+
+        self.windowChoice = wx.Choice(self, id = wx.ID_ANY,
+                                      choices = [_("top left"), _("top right"),
+                                                 _("bottom left"), _("bottom right")])
+        self.windowChoice.SetSelection(self.animationData.windowIndex)
+
+        self.nameCtrl = wx.TextCtrl(self, id = wx.ID_ANY, value = self.animationData.name)
+
+        self.nDChoice = wx.Choice(self, id = wx.ID_ANY)
+        mode = self.animationData.viewMode
+        index = 0
+        for i, (viewMode, viewModeName) in enumerate(self.animationData.viewModes):
+            self.nDChoice.Append(viewModeName, clientData = viewMode)
+            if mode == viewMode:
+                index = i
+
+        self.nDChoice.SetSelection(index)
+        # TODO
+        self.nDChoice.SetToolTipString(_(""))
+        self.nDChoice.Bind(wx.EVT_CHOICE, self.OnViewMode)
+
+        gridSizer = wx.FlexGridSizer(cols = 2, hgap = 5, vgap = 5)
+        gridSizer.Add(item = wx.StaticText(self, id = wx.ID_ANY, label = _("Name:")),
+                      flag = wx.ALIGN_CENTER_VERTICAL)
+        gridSizer.Add(item = self.nameCtrl, proportion = 1, flag = wx.EXPAND)
+        gridSizer.Add(item = wx.StaticText(self, id = wx.ID_ANY, label = _("Window position:")),
+                      flag = wx.ALIGN_CENTER_VERTICAL)
+        gridSizer.Add(item = self.windowChoice, proportion = 1, flag = wx.ALIGN_RIGHT)
+        gridSizer.Add(item = wx.StaticText(self, id = wx.ID_ANY, label = _("View mode:")),
+                      flag = wx.ALIGN_CENTER_VERTICAL)
+        gridSizer.Add(item = self.nDChoice, proportion = 1, flag = wx.ALIGN_RIGHT)
+        gridSizer.AddGrowableCol(0, 1)
+        gridSizer.AddGrowableCol(1, 1)
+        mainSizer.Add(item = gridSizer, proportion = 1, flag = wx.ALL | wx.EXPAND, border = 5)
+
+        self.dataPanel = self._createDataPanel()
+        self.threeDPanel = self._create3DPanel()
+        mainSizer.Add(item = self.dataPanel, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 3)
+        mainSizer.Add(item = self.threeDPanel, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 3)
+
+        # buttons
+        self.btnOk = wx.Button(self, wx.ID_OK)
+        self.btnCancel = wx.Button(self, wx.ID_CANCEL)
+        self.btnOk.SetDefault()
+        self.btnOk.Bind(wx.EVT_BUTTON, self.OnOk)
+        # button sizer
+        btnStdSizer = wx.StdDialogButtonSizer()
+        btnStdSizer.AddButton(self.btnOk)
+        btnStdSizer.AddButton(self.btnCancel)
+        btnStdSizer.Realize()
+        
+        mainSizer.Add(item = btnStdSizer, proportion = 0,
+                      flag = wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT, border = 5)
+
+        self.SetSizer(mainSizer)
+        mainSizer.Fit(self)
+
+    def _createDataPanel(self):
+        panel = wx.Panel(self, id = wx.ID_ANY)
+        dataStBox = wx.StaticBox(parent = panel, id = wx.ID_ANY,
+                               label = ' %s ' % _("Data"))
+        dataBoxSizer = wx.StaticBoxSizer(dataStBox, wx.VERTICAL)
+
+        self.dataChoice = wx.Choice(panel, id = wx.ID_ANY)
+        self._setMapTypes()
+        self.dataChoice.Bind(wx.EVT_CHOICE, self.OnDataType)
+
+        self.dataSelect = gselect.Select(parent = panel, id = wx.ID_ANY,
+                                           size = globalvar.DIALOG_GSELECT_SIZE)
+
+
+        iconTheme = UserSettings.Get(group = 'appearance', key = 'iconTheme', subkey = 'type')
+        bitmapPath = os.path.join(globalvar.ETCICONDIR, iconTheme, 'layer-open.png')
+        if os.path.isfile(bitmapPath) and os.path.getsize(bitmapPath):
+            bitmap = wx.Bitmap(name = bitmapPath)
+        else:
+            bitmap = wx.ArtProvider.GetBitmap(id = wx.ART_MISSING_IMAGE, client = wx.ART_TOOLBAR)
+        self.addManyMapsButton = wx.BitmapButton(panel, id = wx.ID_ANY, bitmap = bitmap)
+        self.addManyMapsButton.Bind(wx.EVT_BUTTON, self.OnAddMaps)
+
+        self.OnDataType(None)
+        if self.animationData.inputData is None:
+            self.dataSelect.SetValue('')
+        else:
+            self.dataSelect.SetValue(self.animationData.inputData)
+
+        hbox = wx.BoxSizer(wx.HORIZONTAL)
+        hbox.Add(item = wx.StaticText(panel, wx.ID_ANY, label = _("Input data type:")),
+                proportion = 1, flag = wx.ALIGN_CENTER_VERTICAL)
+        hbox.Add(item = self.dataChoice, proportion = 1, flag = wx.EXPAND)
+        dataBoxSizer.Add(item = hbox, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 3)
+
+        hbox = wx.BoxSizer(wx.HORIZONTAL)
+        # hbox.Add(item = wx.StaticText(panel, wx.ID_ANY, label = _("Input data:")),
+        #         proportion = 0, flag = wx.ALIGN_CENTER_VERTICAL)
+        hbox.Add(item = self.dataSelect, proportion = 1, flag = wx.ALIGN_CENTER)
+        hbox.Add(item = self.addManyMapsButton, proportion = 0, flag = wx.LEFT, border = 5)
+        dataBoxSizer.Add(item = hbox, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 3)
+        
+        panel.SetSizerAndFit(dataBoxSizer)
+        panel.SetAutoLayout(True)
+
+        return panel
+
+    def _create3DPanel(self):
+        panel = wx.Panel(self, id = wx.ID_ANY)
+        dataStBox = wx.StaticBox(parent = panel, id = wx.ID_ANY,
+                                 label = ' %s ' % _("3D view parameters"))
+        dataBoxSizer = wx.StaticBoxSizer(dataStBox, wx.VERTICAL)
+
+        # workspace file
+        self.fileSelector = filebrowse.FileBrowseButton(parent = panel, id = wx.ID_ANY, 
+                                                    size = globalvar.DIALOG_GSELECT_SIZE,
+                                                    labelText = _("Workspace file:"),
+                                                    dialogTitle = _("Choose workspace file to import 3D view parameters"),
+                                                    buttonText = _('Browse'),
+                                                    startDirectory = os.getcwd(), fileMode = 0,
+                                                    fileMask = "GRASS Workspace File (*.gxw)|*.gxw")
+        if self.animationData.workspaceFile:
+            self.fileSelector.SetValue(self.animationData.workspaceFile)
+        self.paramLabel = wx.StaticText(panel, wx.ID_ANY, label = _("Parameter for animation:"))
+        self.paramChoice = wx.Choice(panel, id = wx.ID_ANY, choices = self.animationData.nvizParameters)
+        self.paramChoice.SetStringSelection(self.animationData.nvizParameter)
+
+        hbox = wx.BoxSizer(wx.HORIZONTAL)
+        hbox.Add(item = self.fileSelector, proportion = 1, flag = wx.EXPAND | wx.ALIGN_CENTER)
+        dataBoxSizer.Add(item = hbox, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 3)
+
+        hbox = wx.BoxSizer(wx.HORIZONTAL)
+        hbox.Add(item = self.paramLabel, proportion = 1, flag = wx.ALIGN_CENTER_VERTICAL)
+        hbox.Add(item = self.paramChoice, proportion = 1, flag = wx.EXPAND)
+        dataBoxSizer.Add(item = hbox, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 3)
+        
+        panel.SetSizerAndFit(dataBoxSizer)
+        panel.SetAutoLayout(True)
+
+        return panel
+
+    def _setMapTypes(self, view2d = True):
+        index = 0
+        if view2d:
+            inputTypes = self.animationData.inputMapTypes[::2]
+        else:
+            inputTypes = self.animationData.inputMapTypes
+        self.dataChoice.Clear()
+        for i, (itype, itypeName) in enumerate(inputTypes):
+            self.dataChoice.Append(itypeName, clientData = itype)
+            if itype == self.animationData.inputMapType:
+                index = i
+        self.dataChoice.SetSelection(index)
+
+    def OnViewMode(self, event):
+        mode = self.nDChoice.GetSelection()
+        self.Freeze()
+        sizer = self.threeDPanel.GetContainingSizer()
+        sizer.Show(self.threeDPanel, mode != 0, True)
+        sizer.Layout()
+        self._setMapTypes(mode == 0)
+        self.Layout()
+        self.Fit()
+        self.Thaw()
+
+    def OnDataType(self, event):
+        etype = self.dataChoice.GetClientData(self.dataChoice.GetSelection())
+        if etype in ('rast', 'vect'):
+            self.dataSelect.SetType(etype = etype, multiple = True)
+            self.addManyMapsButton.Enable(True)
+        else:
+            self.dataSelect.SetType(etype = etype, multiple = False)
+            self.addManyMapsButton.Enable(False)
+
+        self.dataSelect.SetValue('')
+
+    def OnAddMaps(self, event):
+        # TODO: fix dialog
+        etype = self.dataChoice.GetClientData(self.dataChoice.GetSelection())
+        index = 0
+        if etype == 'vect':
+            index = 2
+            
+        dlg = MapLayersDialog(self, title = _("Select raster maps for animation"))
+        dlg.Bind(EVT_APPLY_MAP_LAYERS, self.OnApplyMapLayers)
+        dlg.layerType.SetSelection(index)
+        dlg.LoadMapLayers(dlg.GetLayerType(cmd = True),
+                           dlg.mapset.GetStringSelection())
+        if dlg.ShowModal() == wx.ID_OK:
+            self.dataSelect.SetValue(','.join(dlg.GetMapLayers()))
+
+        dlg.Destroy()
+
+    def OnApplyMapLayers(self, event):
+        self.dataSelect.SetValue(','.join(event.mapLayers))
+
+    def _update(self):
+        self.animationData.name = self.nameCtrl.GetValue()
+        self.animationData.windowIndex = self.windowChoice.GetSelection()
+
+        sel = self.dataChoice.GetSelection()
+        self.animationData.inputMapType = self.dataChoice.GetClientData(sel)
+        self.animationData.inputData = self.dataSelect.GetValue()
+        sel = self.nDChoice.GetSelection()
+        self.animationData.viewMode = self.nDChoice.GetClientData(sel)
+
+        if self.threeDPanel.IsShown():
+            self.animationData.workspaceFile = self.fileSelector.GetValue()
+        if self.threeDPanel.IsShown():
+            self.animationData.nvizParameter = self.paramChoice.GetStringSelection()
+
+    def OnOk(self, event):
+        try:
+            self._update()
+            self.EndModal(wx.ID_OK)
+        except (GException, ValueError, IOError) as e:
+            GError(message = str(e), showTraceback = False, caption = _("Invalid input"))
+
+
+class EditDialog(wx.Dialog):
+    def __init__(self, parent, evalFunction, animationData, maxAnimations):
+        wx.Dialog.__init__(self, parent = parent, id = wx.ID_ANY,
+                           style = wx.DEFAULT_DIALOG_STYLE)
+        self.animationData = copy.deepcopy(animationData)
+        self.eval = evalFunction
+        self.SetTitle(_("Add, edit or remove animations"))
+        self._layout()
+        self.SetSize((300, -1))
+        self.maxAnimations = maxAnimations
+        self.result = None
+
+    def _layout(self):
+        mainSizer = wx.BoxSizer(wx.VERTICAL)
+        box = wx.StaticBox (parent = self, id = wx.ID_ANY, label = " %s " % _("List of animations"))
+        sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
+        gridBagSizer = wx.GridBagSizer (hgap = 5, vgap = 5)
+        gridBagSizer.AddGrowableCol(0)
+        # gridBagSizer.AddGrowableCol(1,1)
+        
+        self.listbox = wx.ListBox(self, id = wx.ID_ANY, choices = [], style = wx.LB_SINGLE|wx.LB_NEEDED_SB)
+        self.listbox.Bind(wx.EVT_LISTBOX_DCLICK, self.OnEdit)
+
+        self.addButton = wx.Button(self, id = wx.ID_ANY, label = _("Add"))
+        self.addButton.Bind(wx.EVT_BUTTON, self.OnAdd)
+        self.editButton = wx.Button(self, id = wx.ID_ANY, label = _("Edit"))
+        self.editButton.Bind(wx.EVT_BUTTON, self.OnEdit)
+        self.removeButton = wx.Button(self, id = wx.ID_ANY, label = _("Remove"))
+        self.removeButton.Bind(wx.EVT_BUTTON, self.OnRemove)
+        
+        self._updateListBox()
+        
+        gridBagSizer.Add(self.listbox, pos = (0,0), span = (3, 1), flag = wx.ALIGN_CENTER_VERTICAL| wx.EXPAND, border = 0)
+        gridBagSizer.Add(self.addButton, pos = (0,1), flag = wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, border = 0)
+        gridBagSizer.Add(self.editButton, pos = (1,1), flag = wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, border = 0)
+        gridBagSizer.Add(self.removeButton, pos = (2,1), flag = wx.ALIGN_CENTER_VERTICAL|wx.EXPAND, border = 0)
+        sizer.Add(gridBagSizer, proportion = 0, flag = wx.ALL | wx.EXPAND, border = 5)
+        mainSizer.Add(item = sizer, proportion = 0,
+                      flag = wx.EXPAND | wx.ALL, border = 5)
+
+        # buttons
+        self.btnOk = wx.Button(self, wx.ID_OK)
+        self.btnCancel = wx.Button(self, wx.ID_CANCEL)
+        self.btnOk.SetDefault()
+        self.btnOk.Bind(wx.EVT_BUTTON, self.OnOk)
+        # button sizer
+        btnStdSizer = wx.StdDialogButtonSizer()
+        btnStdSizer.AddButton(self.btnOk)
+        btnStdSizer.AddButton(self.btnCancel)
+        btnStdSizer.Realize()
+        
+        mainSizer.Add(item = btnStdSizer, proportion = 0,
+                      flag = wx.EXPAND | wx.ALL | wx.ALIGN_RIGHT, border = 5)
+
+        self.SetSizer(mainSizer)
+        mainSizer.Fit(self)
+
+    def _updateListBox(self):
+        self.listbox.Clear()
+        for anim in self.animationData:
+            self.listbox.Append(anim.name, clientData = anim)
+        self.listbox.SetSelection(0)
+
+    def _getNextIndex(self):
+        indices = [anim.windowIndex for anim in self.animationData]
+        for i in range(self.maxAnimations):
+            if i not in indices:
+                return i
+        return None
+
+    def OnAdd(self, event):
+        windowIndex = self._getNextIndex()
+        if windowIndex is None:
+            GMessage(self, message = _("Maximum number of animations is %d.") % self.maxAnimations)
+            return
+        animData = AnimationData()
+        # number of active animations
+        animationIndex = len(self.animationData)
+        animData.SetDefaultValues(windowIndex, animationIndex)
+        dlg = InputDialog(parent = self, mode = 'add', animationData = animData)
+        if dlg.ShowModal() == wx.ID_CANCEL:
+            dlg.Destroy()
+            return
+        dlg.Destroy()
+        self.animationData.append(animData)
+        
+        self._updateListBox()
+
+
+    def OnEdit(self, event):
+        index = self.listbox.GetSelection()
+        if index == wx.NOT_FOUND:
+            return
+
+        animData = self.listbox.GetClientData(index)
+        dlg = InputDialog(parent = self, mode = 'edit', animationData = animData)
+        if dlg.ShowModal() == wx.ID_CANCEL:
+            dlg.Destroy()
+            return
+        dlg.Destroy()
+
+        self._updateListBox()
+
+    def OnRemove(self, event):
+        index = self.listbox.GetSelection()
+        if index == wx.NOT_FOUND:
+            return
+
+        animData = self.listbox.GetClientData(index)
+        self.animationData.remove(animData)
+        
+        self._updateListBox()
+
+    def GetResult(self):
+        return self.result
+
+    def OnOk(self, event):
+        indices = set([anim.windowIndex for anim in self.animationData])
+        if len(indices) != len(self.animationData):
+            GError(parent = self, message = _("More animations are using one window."
+                                                " Please select different window for each animation."))
+            return
+        try:
+            temporalMode, tempManager = self.eval(self.animationData)
+        except GException, e:
+            GError(parent = self, message = e.value, showTraceback = False)
+            return
+        self.result = (self.animationData, temporalMode, tempManager)
+
+        self.EndModal(wx.ID_OK)
+
+
+class AnimationData(object):
+    def __init__(self):
+        self._inputMapTypes = [('rast', _("Multiple raster maps")),
+                               ('vect', _("Multiple vector maps")),
+                               ('strds', _("Space time raster dataset")),
+                               ('stvds', _("Space time vector dataset"))]
+        self._inputMapType = 'rast'
+        self.inputData = None
+        self.mapData = None
+        self._viewModes = [('2d', _("2D view")),
+                           ('3d', _("3D view"))]
+        self.viewMode = '2d'
+
+        self.nvizTask = NvizTask()
+        self._nvizParameters = self.nvizTask.ListMapParameters()
+        self.nvizParameter = self._nvizParameters[0]
+
+        self.workspaceFile = None
+
+    def GetName(self):
+        return self._name
+
+    def SetName(self, name):
+        if name == '':
+            raise ValueError(_("No animation name selected."))
+        self._name = name
+
+    name = property(fget = GetName, fset = SetName)
+
+    def GetWindowIndex(self):
+        return self._windowIndex
+
+    def SetWindowIndex(self, windowIndex):
+        self._windowIndex = windowIndex
+
+    windowIndex = property(fget = GetWindowIndex, fset = SetWindowIndex)
+
+    def GetInputMapTypes(self):
+        return self._inputMapTypes
+
+    inputMapTypes = property(fget = GetInputMapTypes)
+
+    def GetInputMapType(self):
+        return self._inputMapType
+        
+    def SetInputMapType(self, itype):
+        if itype in [each[0] for each in self.inputMapTypes]:
+            self._inputMapType = itype
+        else:
+            raise ValueError("Bad input type.")
+
+    inputMapType = property(fget = GetInputMapType, fset = SetInputMapType)
+
+    def GetInputData(self):
+        return self._inputData
+
+    def SetInputData(self, data):
+        if data == '':
+            raise ValueError(_("No data selected."))
+        if data is None:
+            self._inputData = data
+            return
+
+        if self.inputMapType in ('rast', 'vect'):
+            maps = data.split(',')
+            newNames = validateMapNames(maps, self.inputMapType)
+            self._inputData = ','.join(newNames)
+            self.mapData = newNames
+
+        elif self.inputMapType in ('strds', 'stvds'):
+            timeseries = validateTimeseriesName(data, etype = self.inputMapType)
+            if self.inputMapType == 'strds':
+                sp = tgis.SpaceTimeRasterDataset(ident = timeseries)
+            elif self.inputMapType == 'stvds':
+                sp = tgis.SpaceTimeRasterDataset(ident = timeseries)
+            # else ?
+            sp.select()
+            rows = sp.get_registered_maps(columns = "id", where = None, order = "start_time", dbif = None)
+            timeseriesMaps = []
+            if rows:
+                for row in rows:
+                    timeseriesMaps.append(row["id"])
+            self._inputData = timeseries
+            self.mapData = timeseriesMaps
+        else:
+            self._inputData = data
+
+    inputData = property(fget = GetInputData, fset = SetInputData)
+
+    def SetMapData(self, data):
+        self._mapData = data
+
+    def GetMapData(self):
+        return self._mapData
+
+    mapData = property(fget = GetMapData, fset = SetMapData)
+
+    def GetWorkspaceFile(self):
+        return self._workspaceFile
+
+    def SetWorkspaceFile(self, fileName):
+        if fileName is None:
+            self._workspaceFile = None
+            return
+
+        if fileName == '':
+            raise ValueError(_("No workspace file selected."))
+
+        if not os.path.exists(fileName):
+            raise IOError(_("File %s not found") % fileName)
+        self._workspaceFile = fileName
+
+        self.nvizTask.Load(self.workspaceFile)
+
+    workspaceFile = property(fget = GetWorkspaceFile, fset = SetWorkspaceFile)
+
+    def SetDefaultValues(self, windowIndex, animationIndex):
+        self.windowIndex = windowIndex
+        self.name = _("Animation %d") % (animationIndex + 1)
+
+    def GetNvizParameters(self):
+        return self._nvizParameters
+
+    nvizParameters = property(fget = GetNvizParameters)
+
+    def GetNvizParameter(self):
+        return self._nvizParameter
+
+    def SetNvizParameter(self, param):
+        self._nvizParameter = param
+
+    nvizParameter = property(fget = GetNvizParameter, fset = SetNvizParameter)
+
+    def GetViewMode(self):
+        return self._viewMode
+
+    def SetViewMode(self, mode):
+        self._viewMode = mode
+
+    viewMode = property(fget = GetViewMode, fset = SetViewMode)
+
+    def GetViewModes(self):
+        return self._viewModes
+
+    viewModes = property(fget = GetViewModes)
+
+    def GetNvizCommands(self):
+        if not self.workspaceFile or not self.mapData:
+            return []
+
+        cmds = self.nvizTask.GetCommandSeries(series = self.mapData, paramName = self.nvizParameter)
+
+        return cmds
+
+    def __repr__(self):
+        return "%s(%r)" % (self.__class__, self.__dict__)
+
+
+def test():
+    import wx.lib.inspection
+    import gettext
+    gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
+
+    import grass.script as grass
+
+    app = wx.PySimpleApp()
+    anim = AnimationData()
+    anim.SetDefaultValues(animationIndex = 0, windowIndex = 0)
+
+    dlg = InputDialog(parent = None, mode = 'add', animationData = anim)
+    # dlg = EditDialog(parent = None, animationData = [anim])
+    wx.lib.inspection.InspectionTool().Show()
+
+    dlg.Show()
+    # if val == wx.ID_OK:
+    #     dlg.Update()
+    #     # print anim
+    
+    # dlg.Destroy()
+    print anim
+    app.MainLoop()
+
+if __name__ == '__main__':
+
+    test()
\ No newline at end of file

Added: grass-addons/grass7/gui/wxpython/wx.animation/animation/frame.py
===================================================================
--- grass-addons/grass7/gui/wxpython/wx.animation/animation/frame.py	                        (rev 0)
+++ grass-addons/grass7/gui/wxpython/wx.animation/animation/frame.py	2012-10-17 12:24:37 UTC (rev 53437)
@@ -0,0 +1,518 @@
+"""!
+ at package animation.frame
+
+ at brief Animation frame and different types of sliders
+
+Classes:
+ - frame::SwipeMapDialog
+ - frame::AnimationFrame
+ - frame::AnimationsPanel
+ - frame::AnimationSliderBase
+ - frame::SimpleAnimationSlider
+ - frame::TimeAnimationSlider
+
+
+(C) 2012 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.
+
+ at author Anna Kratochvilova <kratochanna gmail.com>
+"""
+import os
+import sys
+import wx
+import wx.aui
+
+if __name__ == '__main__':
+    sys.path.append(os.path.join(os.environ['GISBASE'], "etc", "gui", "wxpython"))
+from core import globalvar
+from gui_core.widgets import IntegerValidator
+from core.gcmd import RunCommand
+
+from mapwindow import AnimationWindow, BitmapProvider, BitmapPool
+from controller import AnimationController
+from animation import Animation
+from toolbars import MainToolbar, AnimationToolbar, MiscToolbar
+from dialogs import SpeedDialog, EVT_SPEED_CHANGED
+from utils import Orientation, ReplayMode, TemporalType
+
+
+MAX_COUNT = 4
+
+class AnimationFrame(wx.Frame):
+    def __init__(self, parent = None, id = wx.ID_ANY, title = _("Animation tool"), rasters = None, timeseries = None):
+        wx.Frame.__init__(self, parent, id, title = title, style = wx.DEFAULT_FRAME_STYLE, size = (800, 600))
+
+        self.SetClientSize(self.GetSize())
+        self.iconsize = (16, 16)
+
+        self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass_map.ico'), wx.BITMAP_TYPE_ICO))
+
+        self.animations = [Animation() for i in range(MAX_COUNT)]
+        self.windows = []
+        self.animationPanel = AnimationsPanel(self, self.windows, initialCount = MAX_COUNT)
+        bitmapPool = BitmapPool()
+        self.providers = [BitmapProvider(frame = self, bitmapPool = bitmapPool) for i in range(MAX_COUNT)]
+        self.animationSliders = {}
+        self.animationSliders['nontemporal'] = SimpleAnimationSlider(self)
+        self.animationSliders['temporal'] = TimeAnimationSlider(self)
+        self.controller = AnimationController(frame = self, 
+                                              sliders = self.animationSliders,
+                                              animations = self.animations,
+                                              mapwindows = self.windows,
+                                              providers = self.providers,
+                                              bitmapPool = bitmapPool)
+        for win, provider in zip(self.windows, self.providers):
+            win.Bind(wx.EVT_SIZE, lambda event, prov = provider: prov.WindowSizeChanged(event))
+
+        self.InitStatusbar()
+        self._mgr = wx.aui.AuiManager(self)
+
+        # toolbars
+        self.toolbars = {}
+        tb = ['miscToolbar', 'animationToolbar', 'mainToolbar']
+        if sys.platform == 'win32':
+            tb.reverse()
+        for toolb in tb:
+            self._addToolbar(toolb)
+
+        self._addPanes()
+        self._mgr.Update()
+
+        self.dialogs = dict()
+        self.dialogs['speed'] = None
+
+        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
+
+    def InitStatusbar(self):
+        """!Init statusbar."""
+        statusbar = self.CreateStatusBar(number = 2, style = 0)
+        statusbar.SetStatusWidths([-3, -2])
+
+    def _addPanes(self):
+        self._mgr.AddPane(self.animationPanel, wx.aui.AuiPaneInfo().CentrePane().
+                  Name('animPanel').CentrePane().CaptionVisible(False).PaneBorder(False).
+                  Floatable(False).BestSize((-1,-1)).
+                  CloseButton(False).DestroyOnClose(True).Layer(0))
+        for name, slider in self.animationSliders.iteritems():
+            self._mgr.AddPane(slider, wx.aui.AuiPaneInfo().PaneBorder(False).Name('slider_' + name).
+                              Layer(1).CaptionVisible(False).BestSize(slider.GetBestSize()).
+                              DestroyOnClose(True).CloseButton(False).Bottom())
+            self._mgr.GetPane('slider_' + name).Hide()
+
+    def _addToolbar(self, name):
+        """!Add defined toolbar to the window
+        
+        Currently known toolbars are:
+         - 'mainToolbar'          - data management
+         - 'animationToolbar'     - animation controls
+         - 'miscToolbar'          - help, close
+        """
+        if name == "mainToolbar":
+            self.toolbars[name] = MainToolbar(self)
+            self._mgr.AddPane(self.toolbars[name],
+                              wx.aui.AuiPaneInfo().
+                              Name('mainToolbar').Caption(_("Main Toolbar")).
+                              ToolbarPane().Top().
+                              LeftDockable(False).RightDockable(False).
+                              BottomDockable(True).TopDockable(True).
+                              CloseButton(False).Layer(2).Row(1).
+                              BestSize((self.toolbars['mainToolbar'].GetBestSize())))
+        elif name == 'animationToolbar':
+            self.toolbars[name] = AnimationToolbar(self)
+            self._mgr.AddPane(self.toolbars[name],
+                              wx.aui.AuiPaneInfo().
+                              Name('animationToolbar').Caption(_("Animation Toolbar")).
+                              ToolbarPane().Top().
+                              LeftDockable(False).RightDockable(False).
+                              BottomDockable(True).TopDockable(True).
+                              CloseButton(False).Layer(2).Row(1).
+                              BestSize((self.toolbars['animationToolbar'].GetBestSize())))
+            self.controller.SetAnimationToolbar(self.toolbars['animationToolbar'])
+        elif name == 'miscToolbar':
+            self.toolbars[name] = MiscToolbar(self)
+            self._mgr.AddPane(self.toolbars[name],
+                              wx.aui.AuiPaneInfo().
+                              Name('miscToolbar').Caption(_("Misc Toolbar")).
+                              ToolbarPane().Top().
+                              LeftDockable(False).RightDockable(False).
+                              BottomDockable(True).TopDockable(True).
+                              CloseButton(False).Layer(2).Row(1).
+                              BestSize((self.toolbars['miscToolbar'].GetBestSize())))
+            self.controller.SetAnimationToolbar(self.toolbars['miscToolbar'])
+
+    def SetAnimations(self, raster = None, strds = None):
+        self.controller.SetAnimations(raster, strds)
+
+    def OnAddAnimation(self, event):
+        self.controller.AddAnimation()
+
+    def AddWindow(self, index):
+        self.animationPanel.AddWindow(index)
+
+    def RemoveWindow(self, index):
+        self.animationPanel.RemoveWindow(index)
+
+    def IsWindowShown(self, index):
+        return self.animationPanel.IsWindowShown(index)
+
+    def OnEditAnimation(self, event):
+        self.controller.EditAnimations()
+        
+    def SetSlider(self, name):
+        if name == 'nontemporal':
+            self._mgr.GetPane('slider_nontemporal').Show()
+            self._mgr.GetPane('slider_temporal').Hide()
+
+        elif name == 'temporal':
+            self._mgr.GetPane('slider_temporal').Show()
+            self._mgr.GetPane('slider_nontemporal').Hide()
+        else:
+            self._mgr.GetPane('slider_temporal').Hide()
+            self._mgr.GetPane('slider_nontemporal').Hide()
+        self._mgr.Update()
+
+    def OnPlayForward(self, event):
+        self.controller.SetOrientation(Orientation.FORWARD)
+        self.controller.StartAnimation()
+
+    def OnPlayBack(self, event):
+        self.controller.SetOrientation(Orientation.BACKWARD)
+        self.controller.StartAnimation()
+
+    def OnPause(self, event):
+        self.controller.PauseAnimation(paused = event.IsChecked())
+
+    def OnStop(self, event):
+        self.controller.EndAnimation()
+
+    def OnOneDirectionReplay(self, event):
+        if event.IsChecked():
+            mode = ReplayMode.REPEAT
+        else:
+            mode = ReplayMode.ONESHOT
+        self.controller.SetReplayMode(mode)
+
+    def OnBothDirectionReplay(self, event):
+        if event.IsChecked():
+            mode = ReplayMode.REVERSE
+        else:
+            mode = ReplayMode.ONESHOT
+        self.controller.SetReplayMode(mode)
+
+    def OnAdjustSpeed(self, event):
+        win = self.dialogs['speed']
+        if win:
+            win.SetTemporalMode(self.controller.GetTemporalMode())
+            win.SetTimeGranularity(self.controller.GetTimeGranularity())
+            win.InitTimeSpin(self.controller.GetTimeTick())
+            if win.IsShown():
+                win.SetFocus()
+            else:
+                win.Show()
+        else: # start
+            win = SpeedDialog(self, temporalMode = self.controller.GetTemporalMode(),
+                              timeGranularity = self.controller.GetTimeGranularity(),
+                              initialSpeed = self.controller.timeTick)
+            self.dialogs['speed'] = win
+            win.Bind(EVT_SPEED_CHANGED, self.OnChangeSpeed)
+            win.Show()
+
+    def OnChangeSpeed(self, event):
+        self.ChangeSpeed(ms = event.ms)
+
+    def ChangeSpeed(self, ms):
+        self.controller.timeTick = ms
+
+    def Reload(self, event):
+        self.controller.Reload()
+
+    def OnHelp(self, event):
+        RunCommand('g.manual',
+                   quiet = True,
+                   entry = 'wxGUI.Animation')
+
+    def OnCloseWindow(self, event):
+        self.Destroy()
+
+    def __del__(self):
+        if hasattr(self, 'controller') and hasattr(self.controller, 'timer'):
+            if self.controller.timer.IsRunning():
+                self.controller.timer.Stop()
+
+
+class AnimationsPanel(wx.Panel):
+    def __init__(self, parent, windows, initialCount = 4):
+        wx.Panel.__init__(self, parent, id = wx.ID_ANY, style = wx.NO_BORDER)
+        self.shown = []
+        self.count = initialCount
+        self.mainSizer = wx.FlexGridSizer(rows = 2, hgap = 0, vgap = 0)
+        for i in range(initialCount):
+            w = AnimationWindow(self)
+            windows.append(w)
+            self.mainSizer.Add(item = w, proportion = 1, flag = wx.EXPAND)
+
+        self.mainSizer.AddGrowableCol(0)
+        self.mainSizer.AddGrowableCol(1)
+        self.mainSizer.AddGrowableRow(0)
+        self.mainSizer.AddGrowableRow(1)
+        self.windows = windows
+        self.SetSizerAndFit(self.mainSizer)
+
+        for i in range(initialCount):
+            self.mainSizer.Hide(windows[i])
+        self.Layout()
+
+
+    def AddWindow(self, index):
+        if len(self.shown) == self.count:
+            return
+        self.mainSizer.Show(self.windows[index])
+        self.shown.append(index)
+        self.Layout()
+
+    def RemoveWindow(self, index):
+        if len(self.shown) == 0:
+            return
+        self.mainSizer.Hide(self.windows[index])
+        self.shown.remove(index)
+        self.Layout()
+
+    def IsWindowShown(self, index):
+        return self.mainSizer.IsShown(self.windows[index])
+
+
+class AnimationSliderBase(wx.Panel):
+    def __init__(self, parent):
+        wx.Panel.__init__(self, parent = parent, id = wx.ID_ANY)
+        self.label1 = wx.StaticText(self, id = wx.ID_ANY)
+        self.slider = wx.Slider(self, id = wx.ID_ANY, style = wx.SL_HORIZONTAL)
+        self.indexField = wx.TextCtrl(self, id = wx.ID_ANY, size = (40, -1),
+                                      style = wx.TE_PROCESS_ENTER | wx.TE_RIGHT,
+                                      validator = IntegerValidator())
+
+        self.callbackSliderChanging = None
+        self.callbackSliderChanged = None
+        self.callbackFrameIndexChanged = None
+
+        self.framesCount = 0
+
+        self.enable = True
+
+        self.slider.Bind(wx.EVT_SPIN, self.OnSliderChanging)
+        self.slider.Bind(wx.EVT_SCROLL_THUMBRELEASE, self.OnSliderChanged)
+        self.indexField.Bind(wx.EVT_TEXT_ENTER, self.OnFrameIndexChanged)
+
+    def UpdateFrame(self, index):
+        if not self.enable:
+            return
+
+        self.slider.SetValue(index)
+        self._updateFrameIndex(index)
+
+    def _updateFrameIndex(self, index):
+        raise NotImplementedError
+
+    def OnFrameIndexChanged(self, event):
+        self._onFrameIndexChanged(event)
+
+    def SetFrames(self, frames):
+        self._setFrames(frames)
+        
+    def _setFrames(self, frames):
+        raise NotImplementedError
+
+    def SetCallbackSliderChanging(self, callback):
+        self.callbackSliderChanging = callback
+
+    def SetCallbackSliderChanged(self, callback):
+        self.callbackSliderChanged = callback
+
+    def SetCallbackFrameIndexChanged(self, callback):
+        self.callbackFrameIndexChanged = callback
+
+    def EnableSlider(self, enable = True):
+        self.enable = enable
+        self.slider.Enable(enable)
+        self.indexField.Enable(enable)
+
+    def OnSliderChanging(self, event):
+        self.callbackSliderChanging(event.GetInt())
+
+    def OnSliderChanged(self, event):
+        self.callbackSliderChanged()
+
+    def _onFrameIndexChanged(self, event):
+        index = self.indexField.GetValue()
+        index = self._validate(index)
+        if index is not None:
+            self.slider.SetValue(index)
+            self.callbackFrameIndexChanged(index)
+
+    def _validate(self, index):
+        try:
+            index = int(index)
+        except ValueError:
+            index = self.slider.GetValue()
+            self.indexField.SetValue(str(index + 1))
+            return None
+        start, end = self.slider.GetRange()
+        index -= 1
+        if index > end:
+            index = end
+            self.indexField.SetValue(str(end + 1))
+        elif index < start:
+            index = start
+            self.indexField.SetValue(str(start + 1))
+
+        return index
+
+
+class SimpleAnimationSlider(AnimationSliderBase):
+    def __init__(self, parent):
+        AnimationSliderBase.__init__(self, parent)
+
+        self._setLabel()
+        self._doLayout()
+
+    def _doLayout(self):
+        hbox = wx.BoxSizer(wx.HORIZONTAL)
+        hbox.Add(item = self.indexField, proportion = 0,
+                 flag = wx.ALIGN_CENTER | wx.LEFT, border = 5)
+        hbox.Add(item = self.label1, proportion = 0, 
+                 flag = wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT, border = 5)
+        hbox.Add(item = self.slider, proportion = 1, flag = wx.ALIGN_CENTER| wx.EXPAND, border = 0)
+        self.SetSizerAndFit(hbox)
+
+    def _setFrames(self, count):
+        self.framesCount = count
+        self.slider.SetRange(0, self.framesCount - 1)
+        self._setLabel()
+
+    def _setLabel(self):
+        label = "/ %(framesCount)s" % {'framesCount': self.framesCount}
+        self.label1.SetLabel(label)
+        self.Layout()
+
+    def _updateFrameIndex(self, index):
+        self.indexField.SetValue(str(index + 1))
+
+
+class TimeAnimationSlider(AnimationSliderBase):
+    def __init__(self, parent):
+        AnimationSliderBase.__init__(self, parent)
+        self.timeLabels = []
+        self.label2 = wx.StaticText(self, id = wx.ID_ANY)
+        self.label3 = wx.StaticText(self, id = wx.ID_ANY)
+        self.label2Length = 0
+        self.temporalType = TemporalType.RELATIVE
+
+        self._setLabel()
+        self._doLayout()
+
+    def _doLayout(self):
+        vbox = wx.BoxSizer(wx.VERTICAL)
+        hbox = wx.BoxSizer(wx.HORIZONTAL)
+        hbox.Add(item = self.label1, proportion = 0, 
+                 flag = wx.ALIGN_CENTER_VERTICAL, border = 0)
+        hbox.AddStretchSpacer()
+        hbox.Add(item = self.indexField, proportion = 0, 
+                 flag = wx.ALIGN_CENTER_VERTICAL, border = 0)
+        hbox.Add(item = self.label2, proportion = 0, 
+                 flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border = 3)
+        hbox.AddStretchSpacer()
+        hbox.Add(item = self.label3, proportion = 0, 
+                 flag = wx.ALIGN_CENTER_VERTICAL, border = 0)
+        vbox.Add(item = hbox, proportion = 0, flag = wx.EXPAND, border = 0)
+
+        hbox = wx.BoxSizer(wx.HORIZONTAL)
+        hbox.Add(item = self.slider, proportion = 1, flag = wx.ALIGN_CENTER | wx.EXPAND, border = 0)
+        vbox.Add(item = hbox, proportion = 0, flag = wx.EXPAND, border = 0)
+
+        self._setTemporalType()
+        self.SetSizerAndFit(vbox)
+
+    def _setTemporalType(self):
+        sizer = self.indexField.GetContainingSizer()
+        # sizer.Show(self.indexField, False) # TODO: what to do?
+        sizer.Show(self.indexField, self.temporalType == TemporalType.RELATIVE)
+        self.Layout()
+
+    def SetTemporalType(self, mode):
+        self.temporalType = mode
+        self._setTemporalType()
+
+    def _setFrames(self, timeLabels):
+        self.timeLabels = timeLabels
+        self.framesCount = len(timeLabels)
+        self.slider.SetRange(0, self.framesCount - 1)
+        self._setLabel()
+
+    def _setLabel(self):
+        if self.timeLabels:
+            if self.temporalType == TemporalType.ABSOLUTE:
+                start = self.timeLabels[0][0]
+                self.label1.SetLabel(start)
+                if self.timeLabels[-1][1]:
+                    end = self.timeLabels[-1][1]
+                else:
+                    end = self.timeLabels[-1][0]
+                self.label3.SetLabel(end)
+            else:
+                unit = self.timeLabels[0][2]
+                start = self.timeLabels[0][0]
+                self.label1.SetLabel(start)
+                if self.timeLabels[-1][1]:
+                    end = self.timeLabels[-1][1]
+                else:
+                    end = self.timeLabels[-1][0]
+                end = "%(end)s %(unit)s" % {'end': end, 'unit': unit}
+                self.label3.SetLabel(end)
+
+            self.label2Length = len(start)
+            self._updateFrameIndex(0)
+
+        else:
+            self.label1.SetLabel("")
+            self.label2.SetLabel("")
+            self.label3.SetLabel("")
+        self.Layout()
+
+    def _updateFrameIndex(self, index):
+        start = self.timeLabels[index][0]
+        if self.timeLabels[index][1]: # interval
+            if self.temporalType == TemporalType.ABSOLUTE:
+                label = _("%(from)s %(dash)s %(to)s") % {'from': start, 'dash': u"\u2013", 'to': self.timeLabels[index][1]}
+            else:
+                label = _("to %(to)s") % {'to': self.timeLabels[index][1]}
+        else:
+            if self.temporalType == TemporalType.ABSOLUTE:
+                label = start
+            else:
+                label = ''
+        self.label2.SetLabel(label)
+        if self.temporalType == TemporalType.RELATIVE:
+            self.indexField.SetValue(start)
+        if len(label) != self.label2Length:
+            self.label2Length = len(label)
+            self.Layout()
+
+def test():
+    import gettext
+    gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
+
+    import grass.script as grass
+
+    app = wx.PySimpleApp()
+    wx.InitAllImageHandlers()
+
+    frame = AnimationFrame(parent = None)
+    frame.SetAnimations(raster = None, strds = None)
+
+    frame.Show()
+    app.MainLoop()
+
+if __name__ == '__main__':
+
+    test()
\ No newline at end of file

Added: grass-addons/grass7/gui/wxpython/wx.animation/animation/mapwindow.py
===================================================================
--- grass-addons/grass7/gui/wxpython/wx.animation/animation/mapwindow.py	                        (rev 0)
+++ grass-addons/grass7/gui/wxpython/wx.animation/animation/mapwindow.py	2012-10-17 12:24:37 UTC (rev 53437)
@@ -0,0 +1,457 @@
+"""!
+ at package animation.mapwindow
+
+ at brief Animation window and bitmaps management
+
+Classes:
+ - mapwindow::BufferedWindow
+ - mapwindow::AnimationWindow
+ - mapwindow::BitmapProvider
+ - mapwindow::BitmapPool
+
+(C) 2012 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.
+
+ at author Anna Kratochvilova <kratochanna gmail.com>
+"""
+import os
+import wx
+
+import grass.script as grass
+from core.gcmd import RunCommand
+
+class BufferedWindow(wx.Window):
+    """
+    A Buffered window class (http://wiki.wxpython.org/DoubleBufferedDrawing).
+
+    To use it, subclass it and define a Draw(DC) method that takes a DC
+    to draw to. In that method, put the code needed to draw the picture
+    you want. The window will automatically be double buffered, and the
+    screen will be automatically updated when a Paint event is received.
+
+    When the drawing needs to change, you app needs to call the
+    UpdateDrawing() method. Since the drawing is stored in a bitmap, you
+    can also save the drawing to file by calling the
+    SaveToFile(self, file_name, file_type) method.
+
+    """
+    def __init__(self, *args, **kwargs):
+        # make sure the NO_FULL_REPAINT_ON_RESIZE style flag is set.
+        kwargs['style'] = kwargs.setdefault('style', wx.NO_FULL_REPAINT_ON_RESIZE) | wx.NO_FULL_REPAINT_ON_RESIZE
+        wx.Window.__init__(self, *args, **kwargs)
+
+        wx.EVT_PAINT(self, self.OnPaint)
+        wx.EVT_SIZE(self, self.OnSize)
+        self._Buffer = wx.EmptyBitmap(0, 0)
+        # OnSize called to make sure the buffer is initialized.
+        # This might result in OnSize getting called twice on some
+        # platforms at initialization, but little harm done.
+        self.OnSize(None)
+
+    def Draw(self, dc):
+        ## just here as a place holder.
+        ## This method should be over-ridden when subclassed
+        pass
+
+    def OnPaint(self, event):
+        # All that is needed here is to draw the buffer to screen
+        dc = wx.BufferedPaintDC(self, self._Buffer)
+
+    def OnSize(self, event):
+        # The Buffer init is done here, to make sure the buffer is always
+        # the same size as the Window
+        #Size  = self.GetClientSizeTuple()
+        size  = self.ClientSize
+
+        # Make new offscreen bitmap: this bitmap will always have the
+        # current drawing in it, so it can be used to save the image to
+        # a file, or whatever.
+        self._Buffer = wx.EmptyBitmap(*size)
+        self.UpdateDrawing()
+        # event.Skip()
+
+    def SaveToFile(self, FileName, FileType=wx.BITMAP_TYPE_PNG):
+        ## This will save the contents of the buffer
+        ## to the specified file. See the wxWindows docs for 
+        ## wx.Bitmap::SaveFile for the details
+        self._Buffer.SaveFile(FileName, FileType)
+
+    def UpdateDrawing(self):
+        """
+        This would get called if the drawing needed to change, for whatever reason.
+
+        The idea here is that the drawing is based on some data generated
+        elsewhere in the system. If that data changes, the drawing needs to
+        be updated.
+
+        This code re-draws the buffer, then calls Update, which forces a paint event.
+        """
+        dc = wx.MemoryDC()
+        dc.SelectObject(self._Buffer)
+        self.Draw(dc)
+        del dc # need to get rid of the MemoryDC before Update() is called.
+        self.Refresh()
+        self.Update()
+
+
+class AnimationWindow(BufferedWindow):
+    def __init__(self, parent, id = wx.ID_ANY, 
+                 style = wx.DEFAULT_FRAME_STYLE | wx.FULL_REPAINT_ON_RESIZE | wx.BORDER_RAISED):
+        self.bitmap = wx.EmptyBitmap(0, 0)
+        self.x = self.y = 0
+        self.text = ''
+        self.size = wx.Size()
+        self.rescaleNeeded = False
+        self.region = None
+        self.parent = parent
+
+        BufferedWindow.__init__(self, parent = parent, id = id, style = style)
+        self.SetBackgroundColour(wx.BLACK)
+        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
+        self.Bind(wx.EVT_SIZE, self.OnSize)
+
+    def Draw(self, dc):
+        """!Draws bitmap."""
+        dc.Clear() # make sure you clear the bitmap!
+        dc.DrawBitmap(self.bitmap, x = self.x, y = self.y)
+        dc.DrawText(self.text, 0, 0)
+
+    def OnSize(self, event):
+        self._computeBitmapCoordinates()
+
+        self.DrawBitmap(self.bitmap, self.text)
+        
+        BufferedWindow.OnSize(self, event)
+        if event:
+            event.Skip()
+        
+    def IsRescaled(self):
+        return self.rescaleNeeded
+
+    def _rescaleIfNeeded(self, bitmap):
+        """!If the bitmap has different size than the window, rescale it."""
+        bW, bH = bitmap.GetSize()
+        wW, wH = self.size
+        if abs(bW - wW) > 5 and abs(bH - wH) > 5:
+            self.rescaleNeeded = True
+            im = wx.ImageFromBitmap(bitmap)
+            im.Rescale(*self.size)
+            bitmap = wx.BitmapFromImage(im)
+        else:
+            self.rescaleNeeded = False
+        return bitmap
+        
+    def DrawBitmap(self, bitmap, text):
+        """!Draws bitmap.
+        Does not draw the bitmap if it is the same one as last time.
+        """
+        bmp = self._rescaleIfNeeded(bitmap)
+        if self.bitmap == bmp:
+            return
+
+        self.bitmap = bmp
+        self.text = text
+        self.UpdateDrawing()
+
+    def _computeBitmapCoordinates(self):
+        """!Computes where to place the bitmap
+        to be in the center of the window."""
+        if not self.region:
+            return
+
+        cols = self.region['cols']
+        rows = self.region['rows']
+        
+        ratioB = cols / float(rows)
+        ww, wh = self.GetClientSize()
+        try:
+            ratioW = ww / float(wh)
+        except ZeroDivisionError:
+            self.x = self.y = 0
+            self.size = (0, 0)
+            return
+        if ratioW > ratioB:
+            self.size = (wh * ratioB, wh)
+            self.y = 0
+            self.x = (ww - wh * ratioB) / 2
+        else:
+            self.size = (ww, ww / ratioB)
+            self.x = 0
+            self.y = (wh - ww / ratioB) / 2
+
+    def SetRegion(self, region):
+        """!Sets region for size computations.
+        Region is set from outside to avoid calling g.region multiple times.
+        """
+        self.region = region
+        self._computeBitmapCoordinates()
+
+class BitmapProvider(object):
+    """!Class responsible for loading data and providing bitmaps"""
+    def __init__(self, frame, bitmapPool):
+
+        self.datasource = None
+        self.dataNames = None
+        self.dataType = None
+        self.bitmapPool = bitmapPool
+        self.frame = frame
+        self.size = wx.Size()
+        self.loadSize = wx.Size()
+
+        self.suffix = ''
+
+    def GetDataNames(self):
+        return self.dataNames
+
+    def SetData(self, datasource, dataNames = None, dataType = 'rast', suffix = ''):
+        """!Sets data.
+
+        @param datasource data to load (raster maps, m.nviz.image commands)
+        @param dataNames data labels (keys)
+        @param dataType 'rast', 'nviz'
+        """
+        self.datasource = datasource
+        self.dataType = dataType
+        self.suffix = suffix
+        # add postfix _nviz, _image if needed
+        if dataNames:
+            self.dataNames = dataNames
+        else:
+            self.dataNames = datasource
+
+        self.dataNames = [name + self.suffix for name in self.dataNames]
+
+    def GetBitmap(self, dataId):
+        """!Returns bitmap with given key
+        or 'no data' bitmap if no such key exists.
+
+        @param dataId name of bitmap
+        """
+        dataId += self.suffix
+        try:
+            bitmap = self.bitmapPool[dataId]
+        except KeyError:
+            bitmap = self.bitmapPool[None]
+        return bitmap
+
+    def GetLoadSize(self):
+        return self.loadSize
+
+    def WindowSizeChanged(self, event):
+        """!Sets size when size of related window changes."""
+        self.size = event.GetSize()
+        event.Skip()
+
+    def _createNoDataBitmap(self, ncols, nrows):
+        """!Creates 'no data' bitmap.
+
+        Used when requested bitmap is not available (loading data was not successful) or 
+        we want to show 'no data' bitmap.
+        """
+        bitmap = wx.EmptyBitmap(ncols, nrows)
+        dc = wx.MemoryDC()
+        dc.SelectObject(bitmap)
+        dc.Clear()
+        text = _("No data")
+        dc.SetFont(wx.Font(pointSize = 40, family = wx.FONTFAMILY_SCRIPT,
+                           style = wx.FONTSTYLE_NORMAL, weight = wx.FONTWEIGHT_BOLD))
+        tw, th = dc.GetTextExtent(text)
+        dc.DrawText(text, (ncols-tw)/2,  (nrows-th)/2)
+        dc.SelectObject(wx.NullBitmap)
+        return bitmap
+
+    def Load(self, force = False):
+        """!Loads data.
+
+        Shows progress dialog.
+
+        @param force if True reload all data, otherwise only missing data
+        """
+        count, maxLength = self._dryLoad(rasters = self.datasource,
+                                         names = self.dataNames, force = force)
+        progress = None
+        if self.dataType == 'rast' and count > 5 or \
+            self.dataType == 'nviz':
+            progress = wx.ProgressDialog(title = "Loading data",
+                                         message = " " * (maxLength + 20), # ?
+                                         maximum = count,
+                                         parent = self.frame,
+                                         style = wx.PD_CAN_ABORT | wx.PD_APP_MODAL |
+                                                 wx.PD_AUTO_HIDE | wx.PD_SMOOTH)
+            updateFunction = progress.Update
+        else:
+            updateFunction = None
+
+        if self.dataType == 'rast':
+            size, scale = self._computeScale()
+            # loading ...
+            self._loadRasters(rasters = self.datasource, names = self.dataNames,
+                             size = size, scale = scale, force = force, updateFunction = updateFunction)
+        elif self.dataType == 'nviz':
+            self._load3D(commands = self.datasource, names = self.dataNames,
+                         force = force, updateFunction = updateFunction)
+        if progress:
+            progress.Destroy()
+
+    def Unload(self):
+        self.datasource = None
+        self.dataNames = None
+        self.dataType = None
+
+    def _computeScale(self):
+        """!Computes parameters for creating bitmaps."""
+        region = grass.region()
+        ncols, nrows = region['cols'], region['rows']
+        if nrows > ncols:
+            longdim = nrows
+            size = self.size[1]
+        else:
+            longdim = ncols
+            size = self.size[0]
+        scale = 1.0
+
+        if longdim > size:
+            scale = float(size) / longdim
+        elif longdim < size:
+            scale = float(size) / longdim
+        size = (int(ncols * scale), int(nrows * scale))
+
+        return (size, scale)
+
+    def _dryLoad(self, rasters, names, force):
+        """!Tries how many bitmaps will be loaded.
+        Used for progress dialog.
+
+        @param rasters raster maps to be loaded
+        @param names names used as keys for bitmaps
+        @param force load everything even though it is already there
+        """
+        count = 0
+        maxLength = 0
+        for raster, name in zip(rasters, names):
+            if not(name in self.bitmapPool and force is False):
+                count += 1
+                if len(raster) > maxLength:
+                    maxLength = len(raster)
+
+        return count, maxLength
+
+    def _loadRasters(self, rasters, names, size, scale, force, updateFunction):
+        """!Loads rasters (also rasters from temporal dataset).
+
+        Uses r.out.ppm.
+
+        @param rasters raster maps to be loaded
+        @param names names used as keys for bitmaps
+        @param size size of new bitmaps
+        @param scale used for adjustment of region resolution for r.out.ppm
+        @param force load everything even though it is already there
+        @param updateFunction function called for updating progress dialog
+        """
+        region = grass.region()
+        for key in ('rows', 'cols', 'cells'):
+            region.pop(key)
+        region['nsres'] /= scale
+        region['ewres'] /= scale
+        os.environ['GRASS_REGION'] = grass.region_env(**region)
+        ncols, nrows = size
+        self.loadSize = size
+        count = 0
+        # create no data bitmap
+        if None not in self.bitmapPool or force:
+            self.bitmapPool[None] = self._createNoDataBitmap(ncols, nrows)
+        for raster, name in zip(rasters, names):
+            if name in self.bitmapPool and force is False:
+                continue
+            count += 1
+            # RunCommand has problem with DecodeString
+            returncode, stdout, messages = read2_command('r.out.ppm', input = raster,
+                                                         flags = 'h', output = '-', quiet = True)
+            if returncode != 0:
+                self.bitmapPool[name] = wx.EmptyBitmap(ncols, nrows)
+                continue
+                
+            bitmap = wx.BitmapFromBuffer(ncols, nrows, stdout)
+            self.bitmapPool[name] = bitmap
+
+            if updateFunction:
+                keepGoing, skip = updateFunction(count, raster)
+                if not keepGoing:
+                    break
+
+        os.environ.pop('GRASS_REGION')
+
+    def _load3D(self, commands, names, force, updateFunction):
+        """!Load 3D view images using m.nviz.image.
+
+        @param commands 
+        @param names names used as keys for bitmaps
+        @param force load everything even though it is already there
+        @param updateFunction function called for updating progress dialog
+        """
+        ncols, nrows = self.size
+        self.loadSize = ncols, nrows
+        count = 0
+        format = 'ppm'
+        tempFile = grass.tempfile(False)
+        tempFileFormat = tempFile + '.' + format
+        # create no data bitmap
+        if None not in self.bitmapPool or force:
+            self.bitmapPool[None] = self._createNoDataBitmap(ncols, nrows)
+        for command, name in zip(commands, names):
+            if name in self.bitmapPool and force is False:
+                continue
+            count += 1
+            # set temporary file
+            command[1]['output'] = tempFile
+            # set size
+            command[1]['size'] = '%d,%d' % (ncols, nrows)
+            # set format
+            command[1]['format'] = format
+
+            returncode, messages = RunCommand(getErrorMsg = True, prog = command[0], **command[1])
+            if returncode != 0:
+                self.bitmapPool[name] = wx.EmptyBitmap(ncols, nrows)
+                continue
+                
+
+            self.bitmapPool[name] = wx.Bitmap(tempFileFormat)
+
+            if updateFunction:
+                keepGoing, skip = updateFunction(count, name)
+                if not keepGoing:
+                    break
+        grass.try_remove(tempFileFormat)
+
+class BitmapPool():
+    """!Class storing bitmaps (emulates dictionary)"""
+    def __init__(self):
+        self.bitmaps = {}
+
+    def __getitem__(self, key):
+        return self.bitmaps[key]
+
+    def __setitem__(self, key, bitmap):
+        self.bitmaps[key] = bitmap
+
+    def __contains__(self, key):
+        return key in self.bitmaps
+
+    def Clear(self, usedKeys):
+        """!Removes all bitmaps which are currentlu not used.
+
+        @param usedKeys keys which are currently used
+        """
+        for key in self.bitmaps.keys():
+            if key not in usedKeys and key is not None:
+                del self.bitmaps[key]
+
+
+def read2_command(*args, **kwargs):
+    kwargs['stdout'] = grass.PIPE
+    kwargs['stderr'] = grass.PIPE
+    ps = grass.start_command(*args, **kwargs)
+    stdout, stderr = ps.communicate()
+    return ps.returncode, stdout, stderr
\ No newline at end of file

Added: grass-addons/grass7/gui/wxpython/wx.animation/animation/nviztask.py
===================================================================
--- grass-addons/grass7/gui/wxpython/wx.animation/animation/nviztask.py	                        (rev 0)
+++ grass-addons/grass7/gui/wxpython/wx.animation/animation/nviztask.py	2012-10-17 12:24:37 UTC (rev 53437)
@@ -0,0 +1,209 @@
+"""!
+ at package animation.nviztask
+
+ at brief Conversion from workspace file to m.nviz.image command
+
+Classes:
+ - nviztask::NvizTask
+
+(C) 2012 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.
+
+ at author Anna Kratochvilova <kratochanna gmail.com>
+"""
+
+import os
+import sys
+try:
+    import xml.etree.ElementTree as etree
+except ImportError:
+    import elementtree.ElementTree as etree # Python <= 2.4
+from pprint import pprint
+
+if __name__ == '__main__':
+    sys.path.append(os.path.join(os.environ['GISBASE'], "etc", "gui", "wxpython"))
+
+from core.workspace import ProcessWorkspaceFile
+from core.gcmd import RunCommand, GException
+from core.utils import GetLayerNameFromCmd, CmdToTuple
+from grass.script import task as gtask
+
+class NvizTask:
+    def __init__(self):
+        self.task = None
+        self.filename = None
+
+    def Load(self, filename):
+        self.task = gtask.grassTask("m.nviz.image")
+        self.filename = filename
+        try:
+            gxwXml = ProcessWorkspaceFile(etree.parse(self.filename))
+        except Exception, e:
+            raise GException(_("Reading workspace file <%s> failed.\n"
+                               "Invalid file, unable to parse XML document.") % filename)
+        # for display in gxwXml.displays:
+            # pprint(display)
+        # for layer in gxwXml.layers:
+            # pprint(layer)
+        # pprint(gxwXml.nviz_state)
+
+        if not gxwXml.nviz_state:
+            raise GException(_("No 3d view information in workspace file <%s>.") % self.filename)
+            
+        # self.task.set_param('output', "/home/anna/testy/nviz/out.ppm")
+
+        self._processState(gxwXml.nviz_state)
+        self._processLayers(gxwXml.layers)
+
+    def _processLayers(self, layers):
+        for layer in layers:
+            if not layer['checked']:
+                continue
+
+            if not layer['nviz']:
+                continue
+            layerName, found = GetLayerNameFromCmd(layer['cmd'], fullyQualified = False,
+                                                   param = 'map')
+            if not found:
+                continue
+
+            if 'surface' in layer['nviz']:
+                self._processSurface(layer['nviz']['surface'], mapName = layerName)
+
+
+    def _processSurface(self, surface, mapName):
+        self._setMultiTaskParam('elevation_map', mapName)
+
+        # attributes like color, shine, transparency
+        attributes = ('color', 'shine', 'transp') # mask missing
+        parameters = (('color_map', 'color'),
+                     ('shininess_map', 'shininess_value'),
+                     ('transparency_map', 'transparency_value'))
+        for attr, params in zip(attributes, parameters):
+            mapname = None
+            const = None
+            if attr in surface['attribute']:
+                if surface['attribute'][attr]['map']:
+                    mapname = surface['attribute'][attr]['value']
+                else:
+                    const = surface['attribute'][attr]['value']
+            else:
+                if attr == 'transp':
+                    const = 0
+                elif attr == 'color':
+                    mapname = mapName
+
+            if mapname:
+                self._setMultiTaskParam(params[0], mapname)
+            else:
+                self._setMultiTaskParam(params[1], const)
+
+        # draw mode
+        for mode in ('mode', 'shading', 'style'):
+            value = surface['draw']['mode']['desc'][mode]
+            self._setMultiTaskParam(mode, value)
+        # wire color
+        value = surface['draw']['wire-color']['value']
+        self._setMultiTaskParam('wire_color', value)
+        # resolution
+        for mode1, mode2 in zip(('coarse', 'fine'), ('resolution_coarse', 'resolution_fine')):
+            value = surface['draw']['resolution'][mode1]
+            self._setMultiTaskParam(mode2, value)
+
+        # position
+        pos = []
+        for coor in ('x', 'y', 'z'):
+            pos.append(str(surface['position'][coor]))
+        value = ','.join(pos)
+        self._setMultiTaskParam('surface_position', value)
+
+    def _processState(self, state):
+        color = state['view']['background']['color']
+        self.task.set_param('bgcolor', self._join(color, delim = ':'))
+        self.task.set_param('position', self._join((state['view']['position']['x'],
+                                                    state['view']['position']['y'])))
+        self.task.set_param('height', state['iview']['height']['value'])
+        self.task.set_param('perspective', state['view']['persp']['value'])
+        self.task.set_param('twist', state['view']['twist']['value'])
+        # TODO: fix zexag
+        self.task.set_param('zexag', state['view']['z-exag']['value'])
+        self.task.set_param('focus', self._join((state['iview']['focus']['x'],
+                                                 state['iview']['focus']['y'],
+                                                 state['iview']['focus']['z'])))
+        self.task.set_param('light_position', self._join((state['light']['position']['x'],
+                                                          state['light']['position']['y'],
+                                                          state['light']['position']['z']/100.)))
+        color = state['light']['color'][:3]
+        self.task.set_param('light_color', self._join(color, delim = ':'))
+        self.task.set_param('light_brightness', int(state['light']['bright']))
+        self.task.set_param('light_ambient', state['light']['ambient'])
+
+
+        
+
+    def _setMultiTaskParam(self, param, value):
+        last = self.task.get_param(param)['value']
+        self.task.set_param(param, self._join((last, value)))
+
+    def _join(self, toJoin, delim = ','):
+        toJoin = filter(self._ignore, toJoin)
+        return delim.join(map(str, toJoin))
+
+    def _ignore(self, value):
+        if value == '' or value is None:
+            return False
+        else:
+            return True
+
+    def ListMapParameters(self):
+        # params = self.task.get_list_params()
+        # parameter with 'map' name
+        # params = filter(lambda x: 'map' in x, params)
+        return ('elevation_map', 'color_map', 'vline','vpoint')
+
+    def GetCommandSeries(self, series, paramName):
+        commands = []
+        if not self.task:
+            return commands
+
+        for value in series:
+            self.task.set_param(paramName, value)
+            self.task.set_flag('overwrite', True)
+            self.task.set_param('output', 'tobechanged')
+            cmd = self.task.get_cmd(ignoreErrors = False, ignoreRequired = False, ignoreDefault = True)
+            commands.append(CmdToTuple(cmd))
+
+        return commands
+
+    def GetCommand(self):
+        if not self.task:
+            return None
+        self.task.set_flag('overwrite', True)
+        self.task.set_param('output', 'tobechanged')
+        cmd = self.task.get_cmd(ignoreErrors = False, ignoreRequired = False, ignoreDefault = True)
+        return CmdToTuple(cmd)
+
+
+
+def test():
+    import gettext
+    gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
+
+    nviz = NvizTask('/home/anna/testy/nviz/t12.gxw')
+    # nviz = NvizState('/home/anna/testy/nviz/t3.gxw')
+    
+    # cmd = nviz.GetCommand()
+    cmds = nviz.GetCommandSeries(['aspect','elevation'], 'color_map')
+    for cmd in cmds:
+        print cmd
+        returncode, message = RunCommand(getErrorMsg = True, prog = cmd[0], **cmd[1])
+        print returncode, message
+
+
+if __name__ == '__main__':
+
+    test()
+
+

Added: grass-addons/grass7/gui/wxpython/wx.animation/animation/temporal_manager.py
===================================================================
--- grass-addons/grass7/gui/wxpython/wx.animation/animation/temporal_manager.py	                        (rev 0)
+++ grass-addons/grass7/gui/wxpython/wx.animation/animation/temporal_manager.py	2012-10-17 12:24:37 UTC (rev 53437)
@@ -0,0 +1,514 @@
+"""!
+ at package animation.temporal_manager
+
+ at brief Management of temporal datasets used in animation
+
+Classes:
+ - temporal_manager::DataMode
+ - temporal_manager::TemporalMapTime
+ - temporal_manager::TemporalManager
+
+
+(C) 2012 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.
+
+ at author Anna Kratochvilova <kratochanna gmail.com>
+"""
+
+import os
+import sys
+
+if __name__ == '__main__':
+    sys.path.append(os.path.join(os.environ['GISBASE'], "etc", "gui", "wxpython"))
+
+import grass.script as grass
+import grass.temporal as tgis
+from core.gcmd import GException
+from utils import validateTimeseriesName, TemporalType
+
+
+class DataMode:
+    SIMPLE = 1
+    MULTIPLE = 2
+
+class TemporalMapTime:
+    POINT = 1
+    INTERVAL = 2
+
+
+class TemporalManager(object):
+    """!Class for temporal data processing."""
+    def __init__(self):
+        self.timeseriesList = []
+        self.timeseriesInfo = {}
+
+        self.dataMode = None
+        self.temporalType = None
+        self.temporalMapTime = None
+
+        # Make sure the temporal database exists
+        tgis.create_temporal_database()
+
+    def GetTemporalType(self):
+        """!Get temporal type (TemporalType.ABSOLUTE, TemporalType.RELATIVE)"""
+        return self._temporalType
+
+    def SetTemporalType(self, ttype):
+        self._temporalType = ttype
+
+    temporalType = property(fget = GetTemporalType, fset = SetTemporalType)
+
+    def AddTimeSeries(self, timeseries, etype):
+        """!Add space time dataset
+        and collect basic information about it.
+
+        Raises GException (e.g. with invalid topology).
+
+        @param timeseries name of timeseries (with or without mapset)
+        @param etype element type (strds, stvds)
+        """
+        self._gatherInformation(timeseries, etype, self.timeseriesList, self.timeseriesInfo)
+
+
+    def EvaluateInputData(self):
+        """!Checks if all timeseries are compatible (raises GException).
+
+        Sets internal variables.
+        """
+        timeseriesCount = len(self.timeseriesList)
+
+        if timeseriesCount == 1:
+            self.dataMode = DataMode.SIMPLE
+        elif timeseriesCount > 1:
+            self.dataMode = DataMode.MULTIPLE
+        else:
+            self.dataMode = None
+
+        ret, message = self._setTemporalState()
+        if not ret:
+            raise GException(message)
+
+
+    def _setTemporalState(self):
+        # check for absolute x relative
+        absolute, relative = 0, 0
+        for infoDict in self.timeseriesInfo.values():
+            if infoDict['temporal_type'] == 'absolute':
+                absolute += 1
+            else:
+                relative += 1
+        if bool(absolute) == bool(relative):
+            message = _("It is not allowed to display data with different temporal types (absolute and relative).")
+            return False, message
+        if absolute:
+            self.temporalType = TemporalType.ABSOLUTE
+        else:
+            self.temporalType = TemporalType.RELATIVE
+            
+        # check for units for relative type
+        if relative:
+            units = set()
+            for infoDict in self.timeseriesInfo.values():
+                units.add(infoDict['unit'])
+            if len(units) > 1:
+                message = _("It is not allowed to display data with different units (%s).") % ','.join(units)
+                return False, message
+
+        # check for interval x point
+        interval, point = 0, 0
+        for infoDict in self.timeseriesInfo.values():
+            if infoDict['map_time'] == 'interval':
+                interval += 1
+            else:
+                point += 1
+        if bool(interval) == bool(point):
+            message = _("It is not allowed to display data with different temporal types of maps (interval and point).")
+            return False, message
+        if interval:
+            self.temporalMapTime = TemporalMapTime.INTERVAL
+        else:
+            self.temporalMapTime = TemporalMapTime.POINT
+
+        return True, None
+
+    def GetGranularity(self, original = True):
+        """!Returns temporal granularity of currently loaded timeseries."""
+        if self.dataMode == DataMode.SIMPLE:
+            gran = self.timeseriesInfo[self.timeseriesList[0]]['granularity']
+            if not original and 'unit' in self.timeseriesInfo[self.timeseriesList[0]]:
+                gran = "%(gran)s %(unit)s" % {'gran': gran,
+                                              'unit': self.timeseriesInfo[self.timeseriesList[0]]['unit']}
+            return gran
+
+        if self.dataMode == DataMode.MULTIPLE:
+            return self._getCommonGranularity(original)
+
+    def _getCommonGranularity(self, original = True):
+        allMaps = []
+        for dataset in self.timeseriesList:
+            maps = self.timeseriesInfo[dataset]['maps']
+            allMaps.extend(maps)
+
+        if self.temporalType == TemporalType.ABSOLUTE:
+            return tgis.compute_absolute_time_granularity(allMaps)
+        if self.temporalType == TemporalType.RELATIVE:
+            gran = tgis.compute_relative_time_granularity(allMaps)
+            if not original:
+                gran = "%(gran)s %(unit)s" % {'gran': gran,
+                                              'unit': self.timeseriesInfo[self.timeseriesList[0]]['unit']}
+            return gran
+
+
+    def GetLabelsAndMaps(self):
+        """!Returns time labels and map names.
+        """
+        mapLists = []
+        labelLists = []
+        for dataset in self.timeseriesList:
+            if self.temporalMapTime == TemporalMapTime.INTERVAL:
+                grassLabels, listOfMaps = self._getLabelsAndMapsInterval(dataset)
+            else:
+                grassLabels, listOfMaps = self._getLabelsAndMapsPoint(dataset)
+            mapLists.append(listOfMaps)
+            labelLists.append(grassLabels)
+
+        # choose longest time interval and fill missing maps with None
+        timestamps = max(labelLists, key = len)
+        newMapLists = []
+        for mapList, labelList in zip(mapLists, labelLists):
+            newMapList = [None] * len(timestamps)
+            i = 0
+            while timestamps[i] != labelList[0]:
+                i += 1
+            newMapList[i:i + len(mapList)] = mapList
+            newMapLists.append(newMapList)
+
+        mapDict = {}
+        for i, dataset in enumerate(self.timeseriesList):
+            mapDict[dataset] = newMapLists[i]
+
+        return timestamps, mapDict
+
+    def _getLabelsAndMapsInterval(self, timeseries):
+        """!Returns time labels and map names (done by sampling)
+        for interval data.
+        """
+        sp = tgis.dataset_factory(self.timeseriesInfo[timeseries]['etype'], timeseries)
+        # sp = tgis.SpaceTimeRasterDataset(ident = timeseries)
+        if sp.is_in_db() == False:
+            raise GException(_("Space time dataset <%s> not found.") % timeseries)
+        sp.select()
+
+        listOfMaps = []
+        timeLabels = []
+        gran = self.GetGranularity()
+        if self.temporalType == TemporalType.ABSOLUTE:
+            gran, unit = gran.split()
+            gran = '%(one)d %(unit)s' % {'one': 1, 'unit': unit}
+
+        unit = None
+        if self.temporalType == TemporalType.RELATIVE:
+            unit = self.timeseriesInfo[timeseries]['unit']
+        maps = sp.get_registered_maps_as_objects_by_granularity(gran = gran)
+        if maps is not None:
+            for map in maps:
+                if len(map) > 0:
+                    timeseries = map[0].get_id()
+                    start, end = map[0].get_valid_time()
+                    listOfMaps.append(timeseries)
+                    if end:
+                        end = str(end)
+                    timeLabels.append((str(start), end, unit))
+                else:
+                    continue
+
+        if self.temporalType == TemporalType.ABSOLUTE:
+            timeLabels = self._pretifyTimeLabels(timeLabels)
+
+        return timeLabels, listOfMaps
+
+    def _getLabelsAndMapsPoint(self, timeseries):
+        """!Returns time labels and map names (done by sampling)
+        for point data. 
+
+        Simplified sampling is done manually because we cannot sample point data.
+        """
+        sp = tgis.dataset_factory(self.timeseriesInfo[timeseries]['etype'], timeseries)
+        # sp = tgis.SpaceTimeRasterDataset(ident = timeseries)
+        if sp.is_in_db() == False:
+            raise GException(_("Space time dataset <%s> not found.") % timeseries)
+
+        sp.select()
+
+        listOfMaps = []
+        timeLabels = []
+        gran = self.GetGranularity()
+        if self.temporalType == TemporalType.ABSOLUTE:
+            gran, unit = gran.split()
+            gran = '%(one)d %(unit)s' % {'one': 1, 'unit': unit}
+
+        unit = None
+        if self.temporalType == TemporalType.RELATIVE:
+            unit = self.timeseriesInfo[timeseries]['unit']
+
+        start, end = sp.get_valid_time()
+
+        rows = sp.get_registered_maps(columns = "id,start_time", where = None, order = "start_time", dbif = None)
+        if not rows:
+            return timeLabels, listOfMaps
+
+        nextTime = start
+        while nextTime <= end:
+            timeLabels.append((str(nextTime), None, unit))
+            found = False
+            for row in rows:
+                timeseries, start_time = row
+                if start_time == nextTime:
+                    listOfMaps.append(timeseries)
+                    found = True
+                    break
+            if not found:
+                listOfMaps.append(timeseries) # here repeat last used map
+                # listOfMaps.append(None)
+
+            if sp.is_time_absolute():
+                nextTime = tgis.increment_datetime_by_string(nextTime, gran)
+            else:
+                nextTime = nextTime + gran
+                
+        if self.temporalType == TemporalType.ABSOLUTE:
+            timeLabels = self._pretifyTimeLabels(timeLabels)
+
+        return timeLabels, listOfMaps
+
+    def _pretifyTimeLabels(self, labels):
+        """!Convert absolute time labels to grass time and
+        leave only datum when time is 0.
+        """
+        grassLabels = []
+        isTime = False
+        for start, end, unit in labels:
+            start = tgis.string_to_datetime(start)
+            start = tgis.datetime_to_grass_datetime_string(start)
+            if end is not None:
+                end = tgis.string_to_datetime(end)
+                end = tgis.datetime_to_grass_datetime_string(end)
+            grassLabels.append((start, end, unit))
+            if '00:00:00' not in start or (end is not None and '00:00:00' not in end):
+                isTime = True
+        if not isTime:
+            for i, (start, end, unit) in enumerate(grassLabels):
+                start = start.replace('00:00:00', '').strip()
+                if end is not None:
+                    end = end.replace('00:00:00', '').strip()
+                grassLabels[i] = (start, end, unit)
+        return grassLabels
+
+    def _gatherInformation(self, timeseries, etype, timeseriesList, infoDict):
+        """!Get info about timeseries and check topology (raises GException)"""
+        id = validateTimeseriesName(timeseries, etype)
+        sp = tgis.dataset_factory(etype, id)
+        # sp = tgis.SpaceTimeRasterDataset(ident = id)
+
+        # Insert content from db
+        sp.select()
+        # Get ordered map list
+        maps = sp.get_registered_maps_as_objects()
+        # check topology
+        check = sp.check_temporal_topology(maps)
+        if not check:
+            raise GException(_("Topology of Space time dataset %s is invalid." % id))
+
+        timeseriesList.append(id)
+        infoDict[id] = {}
+        infoDict[id]['etype'] = etype
+        # compute granularity
+        infoDict[id]['temporal_type'] = sp.get_temporal_type()
+        if infoDict[id]['temporal_type'] == 'absolute':
+            gran = tgis.compute_absolute_time_granularity(maps)
+        else:
+            start, end, infoDict[id]['unit'] = sp.get_relative_time()
+            gran = tgis.compute_relative_time_granularity(maps)
+
+        infoDict[id]['granularity'] = gran
+
+        infoDict[id]['map_time'] = sp.get_map_time()
+        infoDict[id]['maps'] = maps
+
+def test():
+    import gettext
+    from pprint import pprint
+    gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
+
+
+
+    temp = TemporalManager()
+
+    timeseries1 = 'testRelInt1'
+    timeseries2 = 'testRelInt2'
+    createRelativeInterval(timeseries1, timeseries2)
+
+    # timeseries = 'testAbsPoint'
+    # createAbsolutePoint(timeseries)
+
+
+    temp.AddTimeSeries(timeseries1, 'strds')
+    temp.AddTimeSeries(timeseries2, 'strds')
+    # try:
+    #     temp._gatherInformation(timeseries, info)
+    # except GException, e:
+    #     print e
+    # pprint(info)
+    try:
+        temp.EvaluateInputData()
+    except GException, e:
+        print e
+        return
+    
+    print '///////////////////////////'
+    gran = temp.GetGranularity()
+    print "granularity: " + str(gran)
+    pprint (temp.GetLabelsAndMaps())
+    # maps = []
+    # labels = []
+    # try:
+    #     maps, labels = temp.GetLabelsAndMaps(timeseries)
+    # except GException, e:
+    #     print e
+    # print maps
+    # print labels
+    # pprint(temp.timeseriesInfo['timeseries'])
+
+
+
+def createAbsoluteInterval(name1, name2):
+    grass.run_command('g.region', s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10, flags = 'p3', quiet = True)
+
+    grass.mapcalc(exp="prec_1 = rand(0, 550)", overwrite = True)
+    grass.mapcalc(exp="prec_2 = rand(0, 450)", overwrite = True)
+    grass.mapcalc(exp="prec_3 = rand(0, 320)", overwrite = True)
+    grass.mapcalc(exp="prec_4 = rand(0, 510)", overwrite = True)
+    grass.mapcalc(exp="prec_5 = rand(0, 300)", overwrite = True)
+    grass.mapcalc(exp="prec_6 = rand(0, 650)", overwrite = True)
+
+    grass.mapcalc(exp="temp_1 = rand(0, 550)", overwrite = True)
+    grass.mapcalc(exp="temp_2 = rand(0, 450)", overwrite = True)
+    grass.mapcalc(exp="temp_3 = rand(0, 320)", overwrite = True)
+    grass.mapcalc(exp="temp_4 = rand(0, 510)", overwrite = True)
+    grass.mapcalc(exp="temp_5 = rand(0, 300)", overwrite = True)
+    grass.mapcalc(exp="temp_6 = rand(0, 650)", overwrite = True)
+
+    n1 = grass.read_command("g.tempfile", pid = 1, flags = 'd').strip()
+    fd = open(n1, 'w')
+    fd.write(
+       "prec_1|2001-01-01|2001-02-01\n"
+       "prec_2|2001-04-01|2001-05-01\n"
+       "prec_3|2001-05-01|2001-09-01\n"
+       "prec_4|2001-09-01|2002-01-01\n"
+       "prec_5|2002-01-01|2002-05-01\n"
+       "prec_6|2002-05-01|2002-07-01\n"
+       )
+    fd.close()
+
+    n2 = grass.read_command("g.tempfile", pid = 2, flags = 'd').strip()
+    fd = open(n2, 'w')
+    fd.write(
+       "temp_1|2000-10-01|2001-01-01\n"
+       "temp_2|2001-04-01|2001-05-01\n"
+       "temp_3|2001-05-01|2001-09-01\n"
+       "temp_4|2001-09-01|2002-01-01\n"
+       "temp_5|2002-01-01|2002-05-01\n"
+       "temp_6|2002-05-01|2002-07-01\n"
+       )
+    fd.close()
+    grass.run_command('t.unregister', type = 'rast',
+                       maps='prec_1,prec_2,prec_3,prec_4,prec_5,prec_6,temp_1,temp_2,temp_3,temp_4,temp_5,temp_6')
+    for name, fname in zip((name1, name2), (n1, n2)):
+        grass.run_command('t.create', overwrite = True, type='strds',
+                          temporaltype='absolute', output=name, 
+                          title="A test with input files", descr="A test with input files")
+        grass.run_command('t.register', flags = 'i', input=name, file=fname, overwrite = True)
+
+def createRelativeInterval(name1, name2):
+    grass.run_command('g.region', s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10, flags = 'p3', quiet = True)
+
+    grass.mapcalc(exp="prec_1 = rand(0, 550)", overwrite = True)
+    grass.mapcalc(exp="prec_2 = rand(0, 450)", overwrite = True)
+    grass.mapcalc(exp="prec_3 = rand(0, 320)", overwrite = True)
+    grass.mapcalc(exp="prec_4 = rand(0, 510)", overwrite = True)
+    grass.mapcalc(exp="prec_5 = rand(0, 300)", overwrite = True)
+    grass.mapcalc(exp="prec_6 = rand(0, 650)", overwrite = True)
+
+    grass.mapcalc(exp="temp_1 = rand(0, 550)", overwrite = True)
+    grass.mapcalc(exp="temp_2 = rand(0, 450)", overwrite = True)
+    grass.mapcalc(exp="temp_3 = rand(0, 320)", overwrite = True)
+    grass.mapcalc(exp="temp_4 = rand(0, 510)", overwrite = True)
+    grass.mapcalc(exp="temp_5 = rand(0, 300)", overwrite = True)
+    grass.mapcalc(exp="temp_6 = rand(0, 650)", overwrite = True)
+
+    n1 = grass.read_command("g.tempfile", pid = 1, flags = 'd').strip()
+    fd = open(n1, 'w')
+    fd.write(
+        "prec_1|1|4\n"
+        "prec_2|6|7\n"
+        "prec_3|7|10\n"
+        "prec_4|10|11\n"
+        "prec_5|11|14\n"
+        "prec_6|14|17\n"
+       )
+    fd.close()
+
+    n2 = grass.read_command("g.tempfile", pid = 2, flags = 'd').strip()
+    fd = open(n2, 'w')
+    fd.write(
+        "temp_1|1|4\n"
+        "temp_2|4|7\n"
+        "temp_3|7|10\n"
+        "temp_4|10|11\n"
+        "temp_5|11|14\n"
+        "temp_6|14|17\n"
+       )
+    fd.close()
+    grass.run_command('t.unregister', type = 'rast',
+                       maps='prec_1,prec_2,prec_3,prec_4,prec_5,prec_6,temp_1,temp_2,temp_3,temp_4,temp_5,temp_6')
+    for name, fname in zip((name1, name2), (n1, n2)):
+        grass.run_command('t.create', overwrite = True, type='strds',
+                          temporaltype='relative', output=name, 
+                          title="A test with input files", descr="A test with input files")
+        grass.run_command('t.register', flags = 'i', input = name, file = fname, unit = "years", overwrite = True)
+        
+def createAbsolutePoint(name):
+    grass.run_command('g.region', s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10, flags = 'p3', quiet = True)
+
+    grass.mapcalc(exp="prec_1 = rand(0, 550)", overwrite = True)
+    grass.mapcalc(exp="prec_2 = rand(0, 450)", overwrite = True)
+    grass.mapcalc(exp="prec_3 = rand(0, 320)", overwrite = True)
+    grass.mapcalc(exp="prec_4 = rand(0, 510)", overwrite = True)
+    grass.mapcalc(exp="prec_5 = rand(0, 300)", overwrite = True)
+    grass.mapcalc(exp="prec_6 = rand(0, 650)", overwrite = True)
+
+    n1 = grass.read_command("g.tempfile", pid = 1, flags = 'd').strip()
+    fd = open(n1, 'w')
+    fd.write(
+       "prec_1|2001-01-01\n"
+       "prec_2|2001-02-10\n"
+       "prec_3|2001-07-01\n"
+       "prec_4|2001-10-01\n"
+       "prec_5|2002-01-01\n"
+       "prec_6|2002-04-01\n"
+       )
+    fd.close()
+    grass.run_command('t.create', overwrite = True, type='strds',
+                      temporaltype='absolute', output=name, 
+                      title="A test with input files", descr="A test with input files")
+
+    grass.run_command('t.register', flags = 'i', input=name, file=n1, overwrite = True)
+
+if __name__ == '__main__':
+
+    test()
+
+
+

Added: grass-addons/grass7/gui/wxpython/wx.animation/animation/toolbars.py
===================================================================
--- grass-addons/grass7/gui/wxpython/wx.animation/animation/toolbars.py	                        (rev 0)
+++ grass-addons/grass7/gui/wxpython/wx.animation/animation/toolbars.py	2012-10-17 12:24:37 UTC (rev 53437)
@@ -0,0 +1,197 @@
+"""!
+ at package animation.toolbars
+
+ at brief Animation toolbars
+
+Classes:
+ - toolbars::MainToolbar(BaseToolbar):
+ - toolbars::AnimationToolbar(BaseToolbar):
+ - toolbars::MiscToolbar(BaseToolbar):
+
+
+(C) 2012 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.
+
+ at author Anna Kratochvilova <kratochanna gmail.com>
+"""
+import wx
+from gui_core.toolbars import BaseToolbar, BaseIcons
+from icons.icon import MetaIcon
+
+from animation import ReplayMode
+
+ganimIcons = {
+        'speed': MetaIcon(img = 'settings', label = _("Change animation speed")),
+        'playForward': MetaIcon(img = 'execute', label = _("Play forward")),
+        'playBack': MetaIcon(img = 'player-back', label = _("Play back")),
+        'stop': MetaIcon(img = 'player-stop', label = _("Stop")),
+        'pause': MetaIcon(img = 'player-pause', label = _("Pause")),
+        'oneDirectionReplay': MetaIcon(img = 'redraw', label = _("Repeat")),
+        'bothDirectionReplay': MetaIcon(img = 'player-repeat-back-forward',
+                                        label = _("Play back and forward")),
+        'addAnimation': MetaIcon(img = 'layer-add', label = _("Add new animation"),
+                                 desc = _("Add new animation")),
+        'editAnimation': MetaIcon(img = 'layer-more', label = _("Add, edit or remove animation"),
+                                  desc = _("Add, edit or remove animation")),
+        }
+
+class MainToolbar(BaseToolbar):
+    """!Main toolbar (data management)
+    """
+    def __init__(self, parent):
+        """!Main toolbar constructor
+        """
+        BaseToolbar.__init__(self, parent)
+        
+        self.InitToolbar(self._toolbarData())
+
+        # realize the toolbar
+        self.Realize()
+        
+    def _toolbarData(self):
+        """!Returns toolbar data (name, icon, handler)"""
+        # BaseIcons are a set of often used icons. It is possible
+        # to reuse icons in ./trunk/gui/icons/grass or add new ones there.
+        icons = ganimIcons
+        return self._getToolbarData((("addAnimation", icons["addAnimation"],
+                                      self.parent.OnAddAnimation),
+                                     ("editAnimation", icons["editAnimation"],
+                                      self.parent.OnEditAnimation),
+                                     ("reload", BaseIcons["render"],
+                                      self.parent.Reload)
+                                    ))
+class AnimationToolbar(BaseToolbar):
+    """!Animation toolbar (to control animation)
+    """
+    def __init__(self, parent):
+        """!Animation toolbar constructor
+        """
+        BaseToolbar.__init__(self, parent)
+        
+        self.InitToolbar(self._toolbarData())
+
+        # realize the toolbar
+        self.Realize()
+
+        self.isPlayingForward = True
+        
+        
+    def _toolbarData(self):
+        """!Returns toolbar data (name, icon, handler)"""
+        # BaseIcons are a set of often used icons. It is possible
+        # to reuse icons in ./trunk/gui/icons/grass or add new ones there.
+        icons = ganimIcons
+        return self._getToolbarData((("playBack", icons["playBack"],
+                                      self.OnPlayBack),
+                                     ("playForward", icons["playForward"],
+                                      self.OnPlayForward),
+                                     ("pause", icons["pause"],
+                                      self.OnPause,
+                                      wx.ITEM_CHECK),
+                                     ("stop", icons["stop"],
+                                      self.OnStop),
+                                     (None, ),
+                                     ("oneDirectionReplay", icons["oneDirectionReplay"],
+                                      self.OnOneDirectionReplay,
+                                      wx.ITEM_CHECK),
+                                     ("bothDirectionReplay", icons["bothDirectionReplay"],
+                                      self.OnBothDirectionReplay,
+                                      wx.ITEM_CHECK),
+                                     (None, ),
+                                     ("adjustSpeed", icons['speed'],
+                                       self.parent.OnAdjustSpeed)
+                                    ))
+    def OnPlayForward(self, event):
+        self.PlayForward()
+        self.parent.OnPlayForward(event)
+
+    def PlayForward(self):
+        self.EnableTool(self.playForward, False)
+        self.EnableTool(self.playBack, True)
+        self.EnableTool(self.pause, True)
+        self.EnableTool(self.stop, True)
+        self.ToggleTool(self.pause, False)
+        self.isPlayingForward = True
+
+    def OnPlayBack(self, event):
+        self.PlayBack()
+        self.parent.OnPlayBack(event)
+
+    def PlayBack(self):
+        self.EnableTool(self.playForward, True)
+        self.EnableTool(self.playBack, False)
+        self.EnableTool(self.pause, True)
+        self.EnableTool(self.stop, True)
+        self.ToggleTool(self.pause, False)
+        self.isPlayingForward = False
+        
+    def OnPause(self, event):
+        self.Pause()
+        self.parent.OnPause(event)
+
+    def Pause(self):
+        if self.GetToolState(self.pause):
+            self.EnableTool(self.playForward, True)
+            self.EnableTool(self.playBack, True)
+        else:
+            self.EnableTool(self.playForward, not self.isPlayingForward)
+            self.EnableTool(self.playBack, self.isPlayingForward)
+
+
+
+    def OnStop(self, event):
+        self.Stop()
+        self.parent.OnStop(event)
+
+    def Stop(self):
+        self.EnableTool(self.playForward, True)
+        self.EnableTool(self.playBack, True)
+        self.EnableTool(self.pause, False)
+        self.EnableTool(self.stop, False)
+        self.ToggleTool(self.pause, False)
+
+        # if not self.GetToolState(self.oneDirectionReplay) and \
+        #    not self.GetToolState(self.bothDirectionReplay):
+        #     self.EnableTool(self.playBack, False) # assuming that stop rewinds to the beginning
+
+    def OnOneDirectionReplay(self, event):
+        if event.IsChecked():
+            self.ToggleTool(self.bothDirectionReplay, False)
+        self.parent.OnOneDirectionReplay(event)
+
+    def OnBothDirectionReplay(self, event):
+        if event.IsChecked():
+            self.ToggleTool(self.oneDirectionReplay, False)
+        self.parent.OnBothDirectionReplay(event)
+
+    def SetReplayMode(self, mode):
+        one, both = False, False
+        if mode == ReplayMode.REPEAT:
+            one = True
+        elif mode == ReplayMode.REVERSE:
+            both = True
+
+        self.ToggleTool(self.oneDirectionReplay, one)
+        self.ToggleTool(self.bothDirectionReplay, both)
+
+class MiscToolbar(BaseToolbar):
+    """!Toolbar with miscellaneous tools related to app
+    """
+    def __init__(self, parent):
+        """!Toolbar constructor
+        """
+        BaseToolbar.__init__(self, parent)
+        
+        self.InitToolbar(self._toolbarData())
+        # realize the toolbar
+        self.Realize()
+        
+    def _toolbarData(self):
+        """!Toolbar data"""
+        return self._getToolbarData((("help", BaseIcons['help'],
+                                      self.parent.OnHelp),
+                                    ("quit", BaseIcons['quit'],
+                                      self.parent.OnCloseWindow),
+                                     ))
\ No newline at end of file

Added: grass-addons/grass7/gui/wxpython/wx.animation/animation/utils.py
===================================================================
--- grass-addons/grass7/gui/wxpython/wx.animation/animation/utils.py	                        (rev 0)
+++ grass-addons/grass7/gui/wxpython/wx.animation/animation/utils.py	2012-10-17 12:24:37 UTC (rev 53437)
@@ -0,0 +1,86 @@
+"""!
+ at package animation.utils
+
+ at brief Miscellaneous functions and enum classes
+
+Classes:
+ - utils::TemporalMode
+ - utils::TemporalType
+ - utils::Orientation
+ - utils::ReplayMode
+
+
+(C) 2012 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.
+
+ at author Anna Kratochvilova <kratochanna gmail.com>
+"""
+import grass.temporal as tgis
+import grass.script as grass
+
+from core.gcmd import GException
+
+class TemporalMode:
+    TEMPORAL = 1
+    NONTEMPORAL = 2
+
+class TemporalType:
+    ABSOLUTE = 1
+    RELATIVE = 2
+
+class Orientation:
+    FORWARD = 1
+    BACKWARD = 2
+
+class ReplayMode:
+    ONESHOT = 1
+    REVERSE = 2
+    REPEAT = 3
+
+def validateTimeseriesName(timeseries, etype = 'strds'):
+    """!Checks if space time dataset exists and completes missing mapset.
+
+    Raises GException if dataset doesn't exist.
+    """
+    trastDict = tgis.tlist_grouped(etype)
+    if timeseries.find("@") >= 0:
+        nameShort, mapset = timeseries.split('@', 1)
+        if nameShort in trastDict[mapset]:
+            return timeseries
+        else:
+            raise GException(_("Space time dataset <%s> not found.") % timeseries)
+
+
+    for mapset, names in trastDict.iteritems():
+        if timeseries in names:
+            return timeseries + "@" + mapset
+
+    raise GException(_("Space time dataset <%s> not found.") % timeseries)
+
+def validateMapNames(names, etype):
+    """!Checks if maps exist and completes missing mapset.
+
+    Input is list of map names.
+    Raises GException if map doesn't exist.
+    """
+    mapDict = grass.list_grouped(etype)
+
+    newNames = []
+    for name in names:
+        if name.find("@") >= 0:
+            nameShort, mapset = name.split('@', 1)
+            if nameShort in mapDict[mapset]:
+                newNames.append(name)
+            else:
+                raise GException(_("Map <%s> not found.") % name)
+        else:
+            found = False
+            for mapset, mapNames in mapDict.iteritems():
+                if name in mapNames:
+                    found = True
+                    newNames.append(name + "@" + mapset)
+            if not found:
+                raise GException(_("Map <%s> not found.") % name)
+    return newNames

Added: grass-addons/grass7/gui/wxpython/wx.animation/wxGUI.Animation.html
===================================================================
--- grass-addons/grass7/gui/wxpython/wx.animation/wxGUI.Animation.html	                        (rev 0)
+++ grass-addons/grass7/gui/wxpython/wx.animation/wxGUI.Animation.html	2012-10-17 12:24:37 UTC (rev 53437)
@@ -0,0 +1,60 @@
+<!-- meta page description: wxGUI Animation tool -->
+<!-- meta page index: wxGUI -->
+<h2>DESCRIPTION</h2>
+
+The <b>Animation Tool</b> is a <em><a href="wxGUI.html">wxGUI</a></em> component
+for animating a series of GRASS raster maps or a space time raster dataset (created by t.* modules).
+
+<p>
+Animation Tool allows to:
+
+<ul>
+  <li>display up to 4 synchronized animations</li>
+  <li>control the animation speed</li>
+  <li>interactively change active frame using a slider</li>
+  <li>visualize space time datasets with unequally spaced intervals</li>
+  <li>animate 3d view (partially implemented)</li>
+</ul>
+
+<p>
+3D view animation enables to animate raster (as an elevation map or a color map)
+or vector map (points, lines). Internally, module m.nviz.image is used.
+To display 3D view animation follow these steps:
+<ul>
+  <li>open GRASS GUI, load maps and start 3D view</li>
+  <li>set view, light and other parameters as you like</li>
+  <li>save workspace file</li>
+  <li>add new animation in Animation Tool, choose 3D view mode</li>
+  <li>choose data (series of maps or space time dataset) used for animation</li>
+  <li>set workspace file</li>
+  <li>choose parameter (parameter of m.nviz.image) to animate (e.g. color_map)</li>
+
+</ul>
+
+<center>
+<br>
+<img src="wxGUI_animation_tool.jpg" border="1" alt="Animation Tool screenshot">
+<br>
+<br>
+</center>
+
+<h2>TODO</h2>
+<ul>
+  <li>export animation</li>
+  <li>...</li>
+</ul>
+
+<h2>SEE ALSO</h2>
+
+<em>
+  <a href="wxGUI.html">wxGUI</a><br>
+  <a href="wxGUI.Components.html">wxGUI components</a>
+</em>
+
+<h2>AUTHOR</h2>
+
+Anna Kratochvilova,
+<a href="http://www.cvut.cz">Czech Technical University in Prague</a>, Czech Republic
+
+<p>
+<i>$Date: 2012-03-07 13:21:57 +0100 (Wed, 07 Mar 2012) $</i>

Added: grass-addons/grass7/gui/wxpython/wx.animation/wxGUI_animation_tool.jpg
===================================================================
(Binary files differ)


Property changes on: grass-addons/grass7/gui/wxpython/wx.animation/wxGUI_animation_tool.jpg
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream



More information about the grass-commit mailing list