123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531 |
- """!
- @package animation.frame
- @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.
- @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 core.utils import _
- from mapwindow import AnimationWindow, BitmapProvider, BitmapPool
- from controller import AnimationController
- from anim import Animation
- from toolbars import MainToolbar, AnimationToolbar, MiscToolbar
- from dialogs import SpeedDialog
- 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, provider=provider,
- sizeMethod=win.GetClientSize: self.FrameSizeChanged(event, provider, sizeMethod))
- provider.mapsLoaded.connect(lambda: self.SetStatusText(''))
- 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."""
- self.CreateStatusBar(number = 1, style = 0)
- 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())))
- def SetAnimations(self, inputs=None, dataType=None):
- """!Set animation data
-
- @param inputs list of lists of raster maps or vector maps,
- or a space time raster or vector dataset
- @param dataType The type of the input data must be one of 'rast', 'vect', 'strds' or 'strds'
- """
- self.controller.SetAnimations(inputs, dataType)
- 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.speedChanged.connect(self.ChangeSpeed)
- win.Show()
- def ChangeSpeed(self, ms):
- self.controller.timeTick = ms
- def Reload(self, event):
- self.controller.Reload()
- def OnExportAnimation(self, event):
- self.controller.Export()
- def FrameSizeChanged(self, event, provider, sizeMethod):
- provider.WindowSizeChanged(*sizeMethod())
- if self.animationPanel.shown:
- self.SetStatusText(_("Window size has changed, rerender maps if needed"))
- event.Skip()
-
-
- 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):
- self._updateFrameIndex(index)
- if not self.enable:
- return
- self.slider.SetValue(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 grass.script as grass
- app = wx.PySimpleApp()
- wx.InitAllImageHandlers()
- frame = AnimationFrame(parent=None)
- frame.SetAnimations(inputs=None, dataType=None)
- frame.Show()
- app.MainLoop()
- if __name__ == '__main__':
- test()
|