"""! @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 """ 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 anim 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, sizeMethod = win.GetClientSize: prov.WindowSizeChanged(event, sizeMethod)) 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 OnExportAnimation(self, event): self.controller.Export() 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 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()