frame.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. """!
  2. @package animation.frame
  3. @brief Animation frame and different types of sliders
  4. Classes:
  5. - frame::SwipeMapDialog
  6. - frame::AnimationFrame
  7. - frame::AnimationsPanel
  8. - frame::AnimationSliderBase
  9. - frame::SimpleAnimationSlider
  10. - frame::TimeAnimationSlider
  11. (C) 2012 by the GRASS Development Team
  12. This program is free software under the GNU General Public License
  13. (>=v2). Read the file COPYING that comes with GRASS for details.
  14. @author Anna Kratochvilova <kratochanna gmail.com>
  15. """
  16. import os
  17. import sys
  18. import wx
  19. import wx.aui
  20. if __name__ == '__main__':
  21. sys.path.append(os.path.join(os.environ['GISBASE'], "etc", "gui", "wxpython"))
  22. from core import globalvar
  23. from gui_core.widgets import IntegerValidator
  24. from core.gcmd import RunCommand
  25. from core.utils import _
  26. from mapwindow import AnimationWindow, BitmapProvider, BitmapPool
  27. from controller import AnimationController
  28. from anim import Animation
  29. from toolbars import MainToolbar, AnimationToolbar, MiscToolbar
  30. from dialogs import SpeedDialog
  31. from utils import Orientation, ReplayMode, TemporalType
  32. MAX_COUNT = 4
  33. class AnimationFrame(wx.Frame):
  34. def __init__(self, parent = None, id = wx.ID_ANY, title = _("Animation tool"), rasters = None, timeseries = None):
  35. wx.Frame.__init__(self, parent, id, title = title, style = wx.DEFAULT_FRAME_STYLE, size = (800, 600))
  36. self.SetClientSize(self.GetSize())
  37. self.iconsize = (16, 16)
  38. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass_map.ico'), wx.BITMAP_TYPE_ICO))
  39. self.animations = [Animation() for i in range(MAX_COUNT)]
  40. self.windows = []
  41. self.animationPanel = AnimationsPanel(self, self.windows, initialCount = MAX_COUNT)
  42. bitmapPool = BitmapPool()
  43. self.providers = [BitmapProvider(frame = self, bitmapPool = bitmapPool) for i in range(MAX_COUNT)]
  44. self.animationSliders = {}
  45. self.animationSliders['nontemporal'] = SimpleAnimationSlider(self)
  46. self.animationSliders['temporal'] = TimeAnimationSlider(self)
  47. self.controller = AnimationController(frame = self,
  48. sliders = self.animationSliders,
  49. animations = self.animations,
  50. mapwindows = self.windows,
  51. providers = self.providers,
  52. bitmapPool = bitmapPool)
  53. for win, provider in zip(self.windows, self.providers):
  54. win.Bind(wx.EVT_SIZE, lambda event, provider=provider,
  55. sizeMethod=win.GetClientSize: self.FrameSizeChanged(event, provider, sizeMethod))
  56. provider.mapsLoaded.connect(lambda: self.SetStatusText(''))
  57. self.InitStatusbar()
  58. self._mgr = wx.aui.AuiManager(self)
  59. # toolbars
  60. self.toolbars = {}
  61. tb = ['miscToolbar', 'animationToolbar', 'mainToolbar']
  62. if sys.platform == 'win32':
  63. tb.reverse()
  64. for toolb in tb:
  65. self._addToolbar(toolb)
  66. self._addPanes()
  67. self._mgr.Update()
  68. self.dialogs = dict()
  69. self.dialogs['speed'] = None
  70. self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
  71. def InitStatusbar(self):
  72. """!Init statusbar."""
  73. self.CreateStatusBar(number = 1, style = 0)
  74. def _addPanes(self):
  75. self._mgr.AddPane(self.animationPanel, wx.aui.AuiPaneInfo().CentrePane().
  76. Name('animPanel').CentrePane().CaptionVisible(False).PaneBorder(False).
  77. Floatable(False).BestSize((-1,-1)).
  78. CloseButton(False).DestroyOnClose(True).Layer(0))
  79. for name, slider in self.animationSliders.iteritems():
  80. self._mgr.AddPane(slider, wx.aui.AuiPaneInfo().PaneBorder(False).Name('slider_' + name).
  81. Layer(1).CaptionVisible(False).BestSize(slider.GetBestSize()).
  82. DestroyOnClose(True).CloseButton(False).Bottom())
  83. self._mgr.GetPane('slider_' + name).Hide()
  84. def _addToolbar(self, name):
  85. """!Add defined toolbar to the window
  86. Currently known toolbars are:
  87. - 'mainToolbar' - data management
  88. - 'animationToolbar' - animation controls
  89. - 'miscToolbar' - help, close
  90. """
  91. if name == "mainToolbar":
  92. self.toolbars[name] = MainToolbar(self)
  93. self._mgr.AddPane(self.toolbars[name],
  94. wx.aui.AuiPaneInfo().
  95. Name('mainToolbar').Caption(_("Main Toolbar")).
  96. ToolbarPane().Top().
  97. LeftDockable(False).RightDockable(False).
  98. BottomDockable(True).TopDockable(True).
  99. CloseButton(False).Layer(2).Row(1).
  100. BestSize((self.toolbars['mainToolbar'].GetBestSize())))
  101. elif name == 'animationToolbar':
  102. self.toolbars[name] = AnimationToolbar(self)
  103. self._mgr.AddPane(self.toolbars[name],
  104. wx.aui.AuiPaneInfo().
  105. Name('animationToolbar').Caption(_("Animation Toolbar")).
  106. ToolbarPane().Top().
  107. LeftDockable(False).RightDockable(False).
  108. BottomDockable(True).TopDockable(True).
  109. CloseButton(False).Layer(2).Row(1).
  110. BestSize((self.toolbars['animationToolbar'].GetBestSize())))
  111. self.controller.SetAnimationToolbar(self.toolbars['animationToolbar'])
  112. elif name == 'miscToolbar':
  113. self.toolbars[name] = MiscToolbar(self)
  114. self._mgr.AddPane(self.toolbars[name],
  115. wx.aui.AuiPaneInfo().
  116. Name('miscToolbar').Caption(_("Misc Toolbar")).
  117. ToolbarPane().Top().
  118. LeftDockable(False).RightDockable(False).
  119. BottomDockable(True).TopDockable(True).
  120. CloseButton(False).Layer(2).Row(1).
  121. BestSize((self.toolbars['miscToolbar'].GetBestSize())))
  122. def SetAnimations(self, inputs=None, dataType=None):
  123. """!Set animation data
  124. @param inputs list of lists of raster maps or vector maps,
  125. or a space time raster or vector dataset
  126. @param dataType The type of the input data must be one of 'rast', 'vect', 'strds' or 'strds'
  127. """
  128. self.controller.SetAnimations(inputs, dataType)
  129. def OnAddAnimation(self, event):
  130. self.controller.AddAnimation()
  131. def AddWindow(self, index):
  132. self.animationPanel.AddWindow(index)
  133. def RemoveWindow(self, index):
  134. self.animationPanel.RemoveWindow(index)
  135. def IsWindowShown(self, index):
  136. return self.animationPanel.IsWindowShown(index)
  137. def OnEditAnimation(self, event):
  138. self.controller.EditAnimations()
  139. def SetSlider(self, name):
  140. if name == 'nontemporal':
  141. self._mgr.GetPane('slider_nontemporal').Show()
  142. self._mgr.GetPane('slider_temporal').Hide()
  143. elif name == 'temporal':
  144. self._mgr.GetPane('slider_temporal').Show()
  145. self._mgr.GetPane('slider_nontemporal').Hide()
  146. else:
  147. self._mgr.GetPane('slider_temporal').Hide()
  148. self._mgr.GetPane('slider_nontemporal').Hide()
  149. self._mgr.Update()
  150. def OnPlayForward(self, event):
  151. self.controller.SetOrientation(Orientation.FORWARD)
  152. self.controller.StartAnimation()
  153. def OnPlayBack(self, event):
  154. self.controller.SetOrientation(Orientation.BACKWARD)
  155. self.controller.StartAnimation()
  156. def OnPause(self, event):
  157. self.controller.PauseAnimation(paused = event.IsChecked())
  158. def OnStop(self, event):
  159. self.controller.EndAnimation()
  160. def OnOneDirectionReplay(self, event):
  161. if event.IsChecked():
  162. mode = ReplayMode.REPEAT
  163. else:
  164. mode = ReplayMode.ONESHOT
  165. self.controller.SetReplayMode(mode)
  166. def OnBothDirectionReplay(self, event):
  167. if event.IsChecked():
  168. mode = ReplayMode.REVERSE
  169. else:
  170. mode = ReplayMode.ONESHOT
  171. self.controller.SetReplayMode(mode)
  172. def OnAdjustSpeed(self, event):
  173. win = self.dialogs['speed']
  174. if win:
  175. win.SetTemporalMode(self.controller.GetTemporalMode())
  176. win.SetTimeGranularity(self.controller.GetTimeGranularity())
  177. win.InitTimeSpin(self.controller.GetTimeTick())
  178. if win.IsShown():
  179. win.SetFocus()
  180. else:
  181. win.Show()
  182. else: # start
  183. win = SpeedDialog(self, temporalMode = self.controller.GetTemporalMode(),
  184. timeGranularity = self.controller.GetTimeGranularity(),
  185. initialSpeed = self.controller.timeTick)
  186. self.dialogs['speed'] = win
  187. win.speedChanged.connect(self.ChangeSpeed)
  188. win.Show()
  189. def ChangeSpeed(self, ms):
  190. self.controller.timeTick = ms
  191. def Reload(self, event):
  192. self.controller.Reload()
  193. def OnExportAnimation(self, event):
  194. self.controller.Export()
  195. def FrameSizeChanged(self, event, provider, sizeMethod):
  196. provider.WindowSizeChanged(*sizeMethod())
  197. if self.animationPanel.shown:
  198. self.SetStatusText(_("Window size has changed, rerender maps if needed"))
  199. event.Skip()
  200. def OnHelp(self, event):
  201. RunCommand('g.manual',
  202. quiet = True,
  203. entry = 'wxGUI.animation')
  204. def OnCloseWindow(self, event):
  205. self.Destroy()
  206. def __del__(self):
  207. if hasattr(self, 'controller') and hasattr(self.controller, 'timer'):
  208. if self.controller.timer.IsRunning():
  209. self.controller.timer.Stop()
  210. class AnimationsPanel(wx.Panel):
  211. def __init__(self, parent, windows, initialCount = 4):
  212. wx.Panel.__init__(self, parent, id = wx.ID_ANY, style = wx.NO_BORDER)
  213. self.shown = []
  214. self.count = initialCount
  215. self.mainSizer = wx.FlexGridSizer(rows = 2, hgap = 0, vgap = 0)
  216. for i in range(initialCount):
  217. w = AnimationWindow(self)
  218. windows.append(w)
  219. self.mainSizer.Add(item = w, proportion = 1, flag = wx.EXPAND)
  220. self.mainSizer.AddGrowableCol(0)
  221. self.mainSizer.AddGrowableCol(1)
  222. self.mainSizer.AddGrowableRow(0)
  223. self.mainSizer.AddGrowableRow(1)
  224. self.windows = windows
  225. self.SetSizerAndFit(self.mainSizer)
  226. for i in range(initialCount):
  227. self.mainSizer.Hide(windows[i])
  228. self.Layout()
  229. def AddWindow(self, index):
  230. if len(self.shown) == self.count:
  231. return
  232. self.mainSizer.Show(self.windows[index])
  233. self.shown.append(index)
  234. self.Layout()
  235. def RemoveWindow(self, index):
  236. if len(self.shown) == 0:
  237. return
  238. self.mainSizer.Hide(self.windows[index])
  239. self.shown.remove(index)
  240. self.Layout()
  241. def IsWindowShown(self, index):
  242. return self.mainSizer.IsShown(self.windows[index])
  243. class AnimationSliderBase(wx.Panel):
  244. def __init__(self, parent):
  245. wx.Panel.__init__(self, parent = parent, id = wx.ID_ANY)
  246. self.label1 = wx.StaticText(self, id = wx.ID_ANY)
  247. self.slider = wx.Slider(self, id = wx.ID_ANY, style = wx.SL_HORIZONTAL)
  248. self.indexField = wx.TextCtrl(self, id = wx.ID_ANY, size = (40, -1),
  249. style = wx.TE_PROCESS_ENTER | wx.TE_RIGHT,
  250. validator = IntegerValidator())
  251. self.callbackSliderChanging = None
  252. self.callbackSliderChanged = None
  253. self.callbackFrameIndexChanged = None
  254. self.framesCount = 0
  255. self.enable = True
  256. self.slider.Bind(wx.EVT_SPIN, self.OnSliderChanging)
  257. self.slider.Bind(wx.EVT_SCROLL_THUMBRELEASE, self.OnSliderChanged)
  258. self.indexField.Bind(wx.EVT_TEXT_ENTER, self.OnFrameIndexChanged)
  259. def UpdateFrame(self, index):
  260. self._updateFrameIndex(index)
  261. if not self.enable:
  262. return
  263. self.slider.SetValue(index)
  264. def _updateFrameIndex(self, index):
  265. raise NotImplementedError
  266. def OnFrameIndexChanged(self, event):
  267. self._onFrameIndexChanged(event)
  268. def SetFrames(self, frames):
  269. self._setFrames(frames)
  270. def _setFrames(self, frames):
  271. raise NotImplementedError
  272. def SetCallbackSliderChanging(self, callback):
  273. self.callbackSliderChanging = callback
  274. def SetCallbackSliderChanged(self, callback):
  275. self.callbackSliderChanged = callback
  276. def SetCallbackFrameIndexChanged(self, callback):
  277. self.callbackFrameIndexChanged = callback
  278. def EnableSlider(self, enable = True):
  279. self.enable = enable
  280. self.slider.Enable(enable)
  281. self.indexField.Enable(enable)
  282. def OnSliderChanging(self, event):
  283. self.callbackSliderChanging(event.GetInt())
  284. def OnSliderChanged(self, event):
  285. self.callbackSliderChanged()
  286. def _onFrameIndexChanged(self, event):
  287. index = self.indexField.GetValue()
  288. index = self._validate(index)
  289. if index is not None:
  290. self.slider.SetValue(index)
  291. self.callbackFrameIndexChanged(index)
  292. def _validate(self, index):
  293. try:
  294. index = int(index)
  295. except ValueError:
  296. index = self.slider.GetValue()
  297. self.indexField.SetValue(str(index + 1))
  298. return None
  299. start, end = self.slider.GetRange()
  300. index -= 1
  301. if index > end:
  302. index = end
  303. self.indexField.SetValue(str(end + 1))
  304. elif index < start:
  305. index = start
  306. self.indexField.SetValue(str(start + 1))
  307. return index
  308. class SimpleAnimationSlider(AnimationSliderBase):
  309. def __init__(self, parent):
  310. AnimationSliderBase.__init__(self, parent)
  311. self._setLabel()
  312. self._doLayout()
  313. def _doLayout(self):
  314. hbox = wx.BoxSizer(wx.HORIZONTAL)
  315. hbox.Add(item = self.indexField, proportion = 0,
  316. flag = wx.ALIGN_CENTER | wx.LEFT, border = 5)
  317. hbox.Add(item = self.label1, proportion = 0,
  318. flag = wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT, border = 5)
  319. hbox.Add(item = self.slider, proportion = 1, flag = wx.ALIGN_CENTER| wx.EXPAND, border = 0)
  320. self.SetSizerAndFit(hbox)
  321. def _setFrames(self, count):
  322. self.framesCount = count
  323. self.slider.SetRange(0, self.framesCount - 1)
  324. self._setLabel()
  325. def _setLabel(self):
  326. label = "/ %(framesCount)s" % {'framesCount': self.framesCount}
  327. self.label1.SetLabel(label)
  328. self.Layout()
  329. def _updateFrameIndex(self, index):
  330. self.indexField.SetValue(str(index + 1))
  331. class TimeAnimationSlider(AnimationSliderBase):
  332. def __init__(self, parent):
  333. AnimationSliderBase.__init__(self, parent)
  334. self.timeLabels = []
  335. self.label2 = wx.StaticText(self, id = wx.ID_ANY)
  336. self.label3 = wx.StaticText(self, id = wx.ID_ANY)
  337. self.label2Length = 0
  338. self.temporalType = TemporalType.RELATIVE
  339. self._setLabel()
  340. self._doLayout()
  341. def _doLayout(self):
  342. vbox = wx.BoxSizer(wx.VERTICAL)
  343. hbox = wx.BoxSizer(wx.HORIZONTAL)
  344. hbox.Add(item = self.label1, proportion = 0,
  345. flag = wx.ALIGN_CENTER_VERTICAL, border = 0)
  346. hbox.AddStretchSpacer()
  347. hbox.Add(item = self.indexField, proportion = 0,
  348. flag = wx.ALIGN_CENTER_VERTICAL, border = 0)
  349. hbox.Add(item = self.label2, proportion = 0,
  350. flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border = 3)
  351. hbox.AddStretchSpacer()
  352. hbox.Add(item = self.label3, proportion = 0,
  353. flag = wx.ALIGN_CENTER_VERTICAL, border = 0)
  354. vbox.Add(item = hbox, proportion = 0, flag = wx.EXPAND, border = 0)
  355. hbox = wx.BoxSizer(wx.HORIZONTAL)
  356. hbox.Add(item = self.slider, proportion = 1, flag = wx.ALIGN_CENTER | wx.EXPAND, border = 0)
  357. vbox.Add(item = hbox, proportion = 0, flag = wx.EXPAND, border = 0)
  358. self._setTemporalType()
  359. self.SetSizerAndFit(vbox)
  360. def _setTemporalType(self):
  361. sizer = self.indexField.GetContainingSizer()
  362. # sizer.Show(self.indexField, False) # TODO: what to do?
  363. sizer.Show(self.indexField, self.temporalType == TemporalType.RELATIVE)
  364. self.Layout()
  365. def SetTemporalType(self, mode):
  366. self.temporalType = mode
  367. self._setTemporalType()
  368. def _setFrames(self, timeLabels):
  369. self.timeLabels = timeLabels
  370. self.framesCount = len(timeLabels)
  371. self.slider.SetRange(0, self.framesCount - 1)
  372. self._setLabel()
  373. def _setLabel(self):
  374. if self.timeLabels:
  375. if self.temporalType == TemporalType.ABSOLUTE:
  376. start = self.timeLabels[0][0]
  377. self.label1.SetLabel(start)
  378. if self.timeLabels[-1][1]:
  379. end = self.timeLabels[-1][1]
  380. else:
  381. end = self.timeLabels[-1][0]
  382. self.label3.SetLabel(end)
  383. else:
  384. unit = self.timeLabels[0][2]
  385. start = self.timeLabels[0][0]
  386. self.label1.SetLabel(start)
  387. if self.timeLabels[-1][1]:
  388. end = self.timeLabels[-1][1]
  389. else:
  390. end = self.timeLabels[-1][0]
  391. end = "%(end)s %(unit)s" % {'end': end, 'unit': unit}
  392. self.label3.SetLabel(end)
  393. self.label2Length = len(start)
  394. self._updateFrameIndex(0)
  395. else:
  396. self.label1.SetLabel("")
  397. self.label2.SetLabel("")
  398. self.label3.SetLabel("")
  399. self.Layout()
  400. def _updateFrameIndex(self, index):
  401. start = self.timeLabels[index][0]
  402. if self.timeLabels[index][1]: # interval
  403. if self.temporalType == TemporalType.ABSOLUTE:
  404. label = _("%(from)s %(dash)s %(to)s") % {'from': start, 'dash': u"\u2013", 'to': self.timeLabels[index][1]}
  405. else:
  406. label = _("to %(to)s") % {'to': self.timeLabels[index][1]}
  407. else:
  408. if self.temporalType == TemporalType.ABSOLUTE:
  409. label = start
  410. else:
  411. label = ''
  412. self.label2.SetLabel(label)
  413. if self.temporalType == TemporalType.RELATIVE:
  414. self.indexField.SetValue(start)
  415. if len(label) != self.label2Length:
  416. self.label2Length = len(label)
  417. self.Layout()
  418. def test():
  419. import grass.script as grass
  420. app = wx.PySimpleApp()
  421. wx.InitAllImageHandlers()
  422. frame = AnimationFrame(parent=None)
  423. frame.SetAnimations(inputs=None, dataType=None)
  424. frame.Show()
  425. app.MainLoop()
  426. if __name__ == '__main__':
  427. test()