frame.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  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. if enable and self.framesCount <= 1:
  280. enable = False # we don't want to enable it
  281. self.enable = enable
  282. self.slider.Enable(enable)
  283. self.indexField.Enable(enable)
  284. def OnSliderChanging(self, event):
  285. self.callbackSliderChanging(event.GetInt())
  286. def OnSliderChanged(self, event):
  287. self.callbackSliderChanged()
  288. def _onFrameIndexChanged(self, event):
  289. index = self.indexField.GetValue()
  290. index = self._validate(index)
  291. if index is not None:
  292. self.slider.SetValue(index)
  293. self.callbackFrameIndexChanged(index)
  294. def _validate(self, index):
  295. try:
  296. index = int(index)
  297. except ValueError:
  298. index = self.slider.GetValue()
  299. self.indexField.SetValue(str(index + 1))
  300. return None
  301. start, end = self.slider.GetRange()
  302. index -= 1
  303. if index > end:
  304. index = end
  305. self.indexField.SetValue(str(end + 1))
  306. elif index < start:
  307. index = start
  308. self.indexField.SetValue(str(start + 1))
  309. return index
  310. class SimpleAnimationSlider(AnimationSliderBase):
  311. def __init__(self, parent):
  312. AnimationSliderBase.__init__(self, parent)
  313. self._setLabel()
  314. self._doLayout()
  315. def _doLayout(self):
  316. hbox = wx.BoxSizer(wx.HORIZONTAL)
  317. hbox.Add(item = self.indexField, proportion = 0,
  318. flag = wx.ALIGN_CENTER | wx.LEFT, border = 5)
  319. hbox.Add(item = self.label1, proportion = 0,
  320. flag = wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT, border = 5)
  321. hbox.Add(item = self.slider, proportion = 1, flag = wx.ALIGN_CENTER| wx.EXPAND, border = 0)
  322. self.SetSizerAndFit(hbox)
  323. def _setFrames(self, count):
  324. self.framesCount = count
  325. if self.framesCount > 1:
  326. self.slider.SetRange(0, self.framesCount - 1)
  327. else:
  328. self.EnableSlider(False)
  329. self._setLabel()
  330. def _setLabel(self):
  331. label = "/ %(framesCount)s" % {'framesCount': self.framesCount}
  332. self.label1.SetLabel(label)
  333. self.Layout()
  334. def _updateFrameIndex(self, index):
  335. self.indexField.SetValue(str(index + 1))
  336. class TimeAnimationSlider(AnimationSliderBase):
  337. def __init__(self, parent):
  338. AnimationSliderBase.__init__(self, parent)
  339. self.timeLabels = []
  340. self.label2 = wx.StaticText(self, id = wx.ID_ANY)
  341. self.label3 = wx.StaticText(self, id = wx.ID_ANY)
  342. self.label2Length = 0
  343. self.temporalType = TemporalType.RELATIVE
  344. self._setLabel()
  345. self._doLayout()
  346. def _doLayout(self):
  347. vbox = wx.BoxSizer(wx.VERTICAL)
  348. hbox = wx.BoxSizer(wx.HORIZONTAL)
  349. hbox.Add(item = self.label1, proportion = 0,
  350. flag = wx.ALIGN_CENTER_VERTICAL, border = 0)
  351. hbox.AddStretchSpacer()
  352. hbox.Add(item = self.indexField, proportion = 0,
  353. flag = wx.ALIGN_CENTER_VERTICAL, border = 0)
  354. hbox.Add(item = self.label2, proportion = 0,
  355. flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border = 3)
  356. hbox.AddStretchSpacer()
  357. hbox.Add(item = self.label3, proportion = 0,
  358. flag = wx.ALIGN_CENTER_VERTICAL, border = 0)
  359. vbox.Add(item = hbox, proportion = 0, flag = wx.EXPAND, border = 0)
  360. hbox = wx.BoxSizer(wx.HORIZONTAL)
  361. hbox.Add(item = self.slider, proportion = 1, flag = wx.ALIGN_CENTER | wx.EXPAND, border = 0)
  362. vbox.Add(item = hbox, proportion = 0, flag = wx.EXPAND, border = 0)
  363. self._setTemporalType()
  364. self.SetSizerAndFit(vbox)
  365. def _setTemporalType(self):
  366. sizer = self.indexField.GetContainingSizer()
  367. # sizer.Show(self.indexField, False) # TODO: what to do?
  368. sizer.Show(self.indexField, self.temporalType == TemporalType.RELATIVE)
  369. self.Layout()
  370. def SetTemporalType(self, mode):
  371. self.temporalType = mode
  372. self._setTemporalType()
  373. def _setFrames(self, timeLabels):
  374. self.timeLabels = timeLabels
  375. self.framesCount = len(timeLabels)
  376. if self.framesCount > 1:
  377. self.slider.SetRange(0, self.framesCount - 1)
  378. else:
  379. self.EnableSlider(False)
  380. self._setLabel()
  381. # TODO: fix setting index values, until then:
  382. self.indexField.Disable()
  383. def _setLabel(self):
  384. if self.timeLabels:
  385. if self.temporalType == TemporalType.ABSOLUTE:
  386. start = self.timeLabels[0][0]
  387. self.label1.SetLabel(start)
  388. if self.timeLabels[-1][1]:
  389. end = self.timeLabels[-1][1]
  390. else:
  391. end = self.timeLabels[-1][0]
  392. self.label3.SetLabel(end)
  393. else:
  394. unit = self.timeLabels[0][2]
  395. start = self.timeLabels[0][0]
  396. self.label1.SetLabel(start)
  397. if self.timeLabels[-1][1]:
  398. end = self.timeLabels[-1][1]
  399. else:
  400. end = self.timeLabels[-1][0]
  401. end = "%(end)s %(unit)s" % {'end': end, 'unit': unit}
  402. self.label3.SetLabel(end)
  403. self.label2Length = len(start)
  404. self._updateFrameIndex(0)
  405. else:
  406. self.label1.SetLabel("")
  407. self.label2.SetLabel("")
  408. self.label3.SetLabel("")
  409. self.Layout()
  410. def _updateFrameIndex(self, index):
  411. start = self.timeLabels[index][0]
  412. if self.timeLabels[index][1]: # interval
  413. if self.temporalType == TemporalType.ABSOLUTE:
  414. label = _("%(from)s %(dash)s %(to)s") % {'from': start, 'dash': u"\u2013", 'to': self.timeLabels[index][1]}
  415. else:
  416. label = _("to %(to)s") % {'to': self.timeLabels[index][1]}
  417. else:
  418. if self.temporalType == TemporalType.ABSOLUTE:
  419. label = start
  420. else:
  421. label = ''
  422. self.label2.SetLabel(label)
  423. if self.temporalType == TemporalType.RELATIVE:
  424. self.indexField.SetValue(start)
  425. if len(label) != self.label2Length:
  426. self.label2Length = len(label)
  427. self.Layout()
  428. def test():
  429. import grass.script as grass
  430. app = wx.PySimpleApp()
  431. wx.InitAllImageHandlers()
  432. frame = AnimationFrame(parent=None)
  433. frame.SetAnimations(inputs=None, dataType=None)
  434. frame.Show()
  435. app.MainLoop()
  436. if __name__ == '__main__':
  437. test()