frame.py 19 KB

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