123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620 |
- """
- @package animation.frame
- @brief Animation frame and different types of sliders
- Classes:
- - frame::AnimationFrame
- - frame::AnimationsPanel
- - frame::AnimationSliderBase
- - frame::SimpleAnimationSlider
- - frame::TimeAnimationSlider
- (C) 2013 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 Petrasova <kratochanna gmail.com>
- """
- import os
- import wx
- import wx.aui
- import six
- import grass.script as gcore
- import grass.temporal as tgis
- from grass.exceptions import FatalError
- from core import globalvar
- from gui_core.widgets import IntegerValidator
- from gui_core.wrap import StaticText, TextCtrl
- from core.gcmd import RunCommand, GWarning
- from animation.mapwindow import AnimationWindow
- from animation.provider import BitmapProvider, BitmapPool, \
- MapFilesPool, CleanUp
- from animation.controller import AnimationController
- from animation.anim import Animation
- from animation.toolbars import MainToolbar, AnimationToolbar, MiscToolbar
- from animation.dialogs import SpeedDialog, PreferencesDialog
- from animation.utils import Orientation, ReplayMode, TemporalType
- MAX_COUNT = 4
- TMP_DIR = None
- gcore.set_raise_on_error(True)
- class AnimationFrame(wx.Frame):
- def __init__(self, parent, giface, title=_("GRASS GIS Animation tool"),
- rasters=None, timeseries=None):
- wx.Frame.__init__(self, parent, title=title,
- style=wx.DEFAULT_FRAME_STYLE, size=(800, 600))
- self._giface = giface
- self.SetClientSize(self.GetSize())
- self.iconsize = (16, 16)
- self.SetIcon(
- wx.Icon(
- os.path.join(
- globalvar.ICONDIR,
- 'grass_map.ico'),
- wx.BITMAP_TYPE_ICO))
- # Make sure the temporal database exists
- try:
- tgis.init()
- except FatalError as e:
- GWarning(parent=self, message=str(e))
- # create temporal directory and ensure it's deleted after programs ends
- # (stored in MAPSET/.tmp/)
- global TMP_DIR
- TMP_DIR = gcore.tempdir()
- self.animations = [Animation() for i in range(MAX_COUNT)]
- self.windows = []
- self.animationPanel = AnimationsPanel(
- self, self.windows, initialCount=MAX_COUNT)
- bitmapPool = BitmapPool()
- mapFilesPool = MapFilesPool()
- self._progressDlg = None
- self._progressDlgMax = None
- self.provider = BitmapProvider(
- bitmapPool=bitmapPool,
- mapFilesPool=mapFilesPool,
- tempDir=TMP_DIR)
- 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,
- provider=self.provider,
- bitmapPool=bitmapPool,
- mapFilesPool=mapFilesPool)
- for win in self.windows:
- win.Bind(wx.EVT_SIZE, self.FrameSizeChanged)
- self.provider.mapsLoaded.connect(lambda: self.SetStatusText(''))
- self.provider.renderingStarted.connect(self._showRenderingProgress)
- self.provider.renderingContinues.connect(self._updateProgress)
- self.provider.renderingFinished.connect(self._closeProgress)
- self.provider.compositionStarted.connect(
- self._showRenderingProgress)
- self.provider.compositionContinues.connect(self._updateProgress)
- self.provider.compositionFinished.connect(self._closeProgress)
- self.InitStatusbar()
- self._mgr = wx.aui.AuiManager(self)
- # toolbars
- self.toolbars = {}
- self._addToolbars()
- self._addPanes()
- self._mgr.Update()
- self.dialogs = dict()
- self.dialogs['speed'] = None
- self.dialogs['preferences'] = 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 six.iteritems(self.animationSliders):
- 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 _addToolbars(self):
- """Add toolbars to the window
- Currently known toolbars are:
- - 'mainToolbar' - data management
- - 'animationToolbar' - animation controls
- - 'miscToolbar' - help, close
- """
- self.toolbars["mainToolbar"] = MainToolbar(self)
- self._mgr.AddPane(self.toolbars["mainToolbar"],
- wx.aui.AuiPaneInfo().
- Name('mainToolbar').Caption(_("Main Toolbar")).
- ToolbarPane().Top().
- LeftDockable(False).RightDockable(False).
- BottomDockable(True).TopDockable(True).
- CloseButton(False).Layer(2).Row(1).Position(0).
- BestSize((self.toolbars['mainToolbar'].GetBestSize())))
- self.toolbars['animationToolbar'] = AnimationToolbar(self)
- self._mgr.AddPane(self.toolbars['animationToolbar'],
- wx.aui.AuiPaneInfo().
- Name('animationToolbar').Caption(_("Animation Toolbar")).
- ToolbarPane().Top().
- LeftDockable(False).RightDockable(False).
- BottomDockable(True).TopDockable(True).
- CloseButton(False).Layer(2).Row(1).Position(1).
- BestSize((self.toolbars['animationToolbar'].GetBestSize())))
- self.controller.SetAnimationToolbar(
- self.toolbars['animationToolbar'])
- self.toolbars['miscToolbar'] = MiscToolbar(self)
- self._mgr.AddPane(self.toolbars['miscToolbar'],
- wx.aui.AuiPaneInfo().
- Name('miscToolbar').Caption(_("Misc Toolbar")).
- ToolbarPane().Top().
- LeftDockable(False).RightDockable(False).
- BottomDockable(True).TopDockable(True).
- CloseButton(False).Layer(2).Row(1).Position(2).
- BestSize((self.toolbars['miscToolbar'].GetBestSize())))
- def SetAnimations(self, layerLists):
- """Set animation data
- :param layerLists: list of layerLists
- """
- self.controller.SetAnimations(layerLists)
- 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)
- win.CenterOnParent()
- 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 _showRenderingProgress(self, count):
- # the message is not really visible, it's there for the initial dlg
- # size
- self._progressDlg = wx.ProgressDialog(
- title=_("Loading data"),
- message="Loading data started, please be patient.",
- maximum=count,
- parent=self,
- style=wx.PD_CAN_ABORT | wx.PD_APP_MODAL | wx.PD_AUTO_HIDE | wx.PD_SMOOTH)
- self._progressDlgMax = count
- def _updateProgress(self, current, text):
- text += _(" ({c} out of {p})").format(c=current,
- p=self._progressDlgMax)
- keepGoing, skip = self._progressDlg.Update(current, text)
- if not keepGoing:
- self.provider.RequestStopRendering()
- def _closeProgress(self):
- self._progressDlg.Destroy()
- self._progressDlg = None
- def OnExportAnimation(self, event):
- self.controller.Export()
- def FrameSizeChanged(self, event):
- maxWidth = maxHeight = 0
- for win in self.windows:
- w, h = win.GetClientSize()
- if w >= maxWidth and h >= maxHeight:
- maxWidth, maxHeight = w, h
- self.provider.WindowSizeChanged(maxWidth, maxHeight)
- event.Skip()
- def OnPreferences(self, event):
- if not self.dialogs['preferences']:
- dlg = PreferencesDialog(parent=self, giface=self._giface)
- self.dialogs['preferences'] = dlg
- dlg.formatChanged.connect(
- lambda: self.controller.UpdateAnimations())
- dlg.CenterOnParent()
- self.dialogs['preferences'].Show()
- def OnHelp(self, event):
- RunCommand('g.manual',
- quiet=True,
- entry='wxGUI.animation')
- def OnCloseWindow(self, event):
- if self.controller.timer.IsRunning():
- self.controller.timer.Stop()
- CleanUp(TMP_DIR)()
- self._mgr.UnInit()
- self.Destroy()
- def __del__(self):
- """It might not be called, therefore we try to clean it all in OnCloseWindow."""
- if hasattr(self, 'controller') and hasattr(self.controller, 'timer'):
- if self.controller.timer.IsRunning():
- self.controller.timer.Stop()
- CleanUp(TMP_DIR)()
- 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(cols=2, hgap=0, vgap=0)
- for i in range(initialCount):
- w = AnimationWindow(self)
- windows.append(w)
- self.mainSizer.Add(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 = StaticText(self, id=wx.ID_ANY)
- self.slider = wx.Slider(self, id=wx.ID_ANY, style=wx.SL_HORIZONTAL)
- self.indexField = 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):
- if enable and self.framesCount <= 1:
- enable = False # we don't want to enable it
- 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(self.indexField, proportion=0,
- flag=wx.ALIGN_CENTER | wx.LEFT, border=5)
- hbox.Add(self.label1, proportion=0,
- flag=wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT, border=5)
- hbox.Add(
- self.slider,
- proportion=1,
- flag=wx.EXPAND,
- border=0)
- self.SetSizerAndFit(hbox)
- def _setFrames(self, count):
- self.framesCount = count
- if self.framesCount > 1:
- self.slider.SetRange(0, self.framesCount - 1)
- self.EnableSlider(True)
- else:
- self.EnableSlider(False)
- 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 = StaticText(self, id=wx.ID_ANY)
- self.label3 = 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(self.label1, proportion=0,
- flag=wx.ALIGN_CENTER_VERTICAL, border=0)
- hbox.AddStretchSpacer()
- hbox.Add(self.indexField, proportion=0,
- flag=wx.ALIGN_CENTER_VERTICAL, border=0)
- hbox.Add(self.label2, proportion=0,
- flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=3)
- hbox.AddStretchSpacer()
- hbox.Add(self.label3, proportion=0,
- flag=wx.ALIGN_CENTER_VERTICAL, border=0)
- vbox.Add(hbox, proportion=0, flag=wx.EXPAND, border=0)
- hbox = wx.BoxSizer(wx.HORIZONTAL)
- hbox.Add(
- self.slider,
- proportion=1,
- flag=wx.EXPAND,
- border=0)
- vbox.Add(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)
- if self.framesCount > 1:
- self.slider.SetRange(0, self.framesCount - 1)
- self.EnableSlider(True)
- else:
- self.EnableSlider(False)
- self._setLabel()
- # TODO: fix setting index values, until then:
- self.indexField.Disable()
- 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()
|