frame.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  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, EVT_SPEED_CHANGED
  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, prov = provider,
  54. sizeMethod = win.GetClientSize: prov.WindowSizeChanged(event, sizeMethod))
  55. self.InitStatusbar()
  56. self._mgr = wx.aui.AuiManager(self)
  57. # toolbars
  58. self.toolbars = {}
  59. tb = ['miscToolbar', 'animationToolbar', 'mainToolbar']
  60. if sys.platform == 'win32':
  61. tb.reverse()
  62. for toolb in tb:
  63. self._addToolbar(toolb)
  64. self._addPanes()
  65. self._mgr.Update()
  66. self.dialogs = dict()
  67. self.dialogs['speed'] = None
  68. self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
  69. def InitStatusbar(self):
  70. """!Init statusbar."""
  71. statusbar = self.CreateStatusBar(number = 2, style = 0)
  72. statusbar.SetStatusWidths([-3, -2])
  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.Bind(EVT_SPEED_CHANGED, self.OnChangeSpeed)
  183. win.Show()
  184. def OnChangeSpeed(self, event):
  185. self.ChangeSpeed(ms = event.ms)
  186. def ChangeSpeed(self, ms):
  187. self.controller.timeTick = ms
  188. def Reload(self, event):
  189. self.controller.Reload()
  190. def OnExportAnimation(self, event):
  191. self.controller.Export()
  192. def OnHelp(self, event):
  193. RunCommand('g.manual',
  194. quiet = True,
  195. entry = 'wxGUI.animation')
  196. def OnCloseWindow(self, event):
  197. self.Destroy()
  198. def __del__(self):
  199. if hasattr(self, 'controller') and hasattr(self.controller, 'timer'):
  200. if self.controller.timer.IsRunning():
  201. self.controller.timer.Stop()
  202. class AnimationsPanel(wx.Panel):
  203. def __init__(self, parent, windows, initialCount = 4):
  204. wx.Panel.__init__(self, parent, id = wx.ID_ANY, style = wx.NO_BORDER)
  205. self.shown = []
  206. self.count = initialCount
  207. self.mainSizer = wx.FlexGridSizer(rows = 2, hgap = 0, vgap = 0)
  208. for i in range(initialCount):
  209. w = AnimationWindow(self)
  210. windows.append(w)
  211. self.mainSizer.Add(item = w, proportion = 1, flag = wx.EXPAND)
  212. self.mainSizer.AddGrowableCol(0)
  213. self.mainSizer.AddGrowableCol(1)
  214. self.mainSizer.AddGrowableRow(0)
  215. self.mainSizer.AddGrowableRow(1)
  216. self.windows = windows
  217. self.SetSizerAndFit(self.mainSizer)
  218. for i in range(initialCount):
  219. self.mainSizer.Hide(windows[i])
  220. self.Layout()
  221. def AddWindow(self, index):
  222. if len(self.shown) == self.count:
  223. return
  224. self.mainSizer.Show(self.windows[index])
  225. self.shown.append(index)
  226. self.Layout()
  227. def RemoveWindow(self, index):
  228. if len(self.shown) == 0:
  229. return
  230. self.mainSizer.Hide(self.windows[index])
  231. self.shown.remove(index)
  232. self.Layout()
  233. def IsWindowShown(self, index):
  234. return self.mainSizer.IsShown(self.windows[index])
  235. class AnimationSliderBase(wx.Panel):
  236. def __init__(self, parent):
  237. wx.Panel.__init__(self, parent = parent, id = wx.ID_ANY)
  238. self.label1 = wx.StaticText(self, id = wx.ID_ANY)
  239. self.slider = wx.Slider(self, id = wx.ID_ANY, style = wx.SL_HORIZONTAL)
  240. self.indexField = wx.TextCtrl(self, id = wx.ID_ANY, size = (40, -1),
  241. style = wx.TE_PROCESS_ENTER | wx.TE_RIGHT,
  242. validator = IntegerValidator())
  243. self.callbackSliderChanging = None
  244. self.callbackSliderChanged = None
  245. self.callbackFrameIndexChanged = None
  246. self.framesCount = 0
  247. self.enable = True
  248. self.slider.Bind(wx.EVT_SPIN, self.OnSliderChanging)
  249. self.slider.Bind(wx.EVT_SCROLL_THUMBRELEASE, self.OnSliderChanged)
  250. self.indexField.Bind(wx.EVT_TEXT_ENTER, self.OnFrameIndexChanged)
  251. def UpdateFrame(self, index):
  252. self._updateFrameIndex(index)
  253. if not self.enable:
  254. return
  255. self.slider.SetValue(index)
  256. def _updateFrameIndex(self, index):
  257. raise NotImplementedError
  258. def OnFrameIndexChanged(self, event):
  259. self._onFrameIndexChanged(event)
  260. def SetFrames(self, frames):
  261. self._setFrames(frames)
  262. def _setFrames(self, frames):
  263. raise NotImplementedError
  264. def SetCallbackSliderChanging(self, callback):
  265. self.callbackSliderChanging = callback
  266. def SetCallbackSliderChanged(self, callback):
  267. self.callbackSliderChanged = callback
  268. def SetCallbackFrameIndexChanged(self, callback):
  269. self.callbackFrameIndexChanged = callback
  270. def EnableSlider(self, enable = True):
  271. self.enable = enable
  272. self.slider.Enable(enable)
  273. self.indexField.Enable(enable)
  274. def OnSliderChanging(self, event):
  275. self.callbackSliderChanging(event.GetInt())
  276. def OnSliderChanged(self, event):
  277. self.callbackSliderChanged()
  278. def _onFrameIndexChanged(self, event):
  279. index = self.indexField.GetValue()
  280. index = self._validate(index)
  281. if index is not None:
  282. self.slider.SetValue(index)
  283. self.callbackFrameIndexChanged(index)
  284. def _validate(self, index):
  285. try:
  286. index = int(index)
  287. except ValueError:
  288. index = self.slider.GetValue()
  289. self.indexField.SetValue(str(index + 1))
  290. return None
  291. start, end = self.slider.GetRange()
  292. index -= 1
  293. if index > end:
  294. index = end
  295. self.indexField.SetValue(str(end + 1))
  296. elif index < start:
  297. index = start
  298. self.indexField.SetValue(str(start + 1))
  299. return index
  300. class SimpleAnimationSlider(AnimationSliderBase):
  301. def __init__(self, parent):
  302. AnimationSliderBase.__init__(self, parent)
  303. self._setLabel()
  304. self._doLayout()
  305. def _doLayout(self):
  306. hbox = wx.BoxSizer(wx.HORIZONTAL)
  307. hbox.Add(item = self.indexField, proportion = 0,
  308. flag = wx.ALIGN_CENTER | wx.LEFT, border = 5)
  309. hbox.Add(item = self.label1, proportion = 0,
  310. flag = wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT, border = 5)
  311. hbox.Add(item = self.slider, proportion = 1, flag = wx.ALIGN_CENTER| wx.EXPAND, border = 0)
  312. self.SetSizerAndFit(hbox)
  313. def _setFrames(self, count):
  314. self.framesCount = count
  315. self.slider.SetRange(0, self.framesCount - 1)
  316. self._setLabel()
  317. def _setLabel(self):
  318. label = "/ %(framesCount)s" % {'framesCount': self.framesCount}
  319. self.label1.SetLabel(label)
  320. self.Layout()
  321. def _updateFrameIndex(self, index):
  322. self.indexField.SetValue(str(index + 1))
  323. class TimeAnimationSlider(AnimationSliderBase):
  324. def __init__(self, parent):
  325. AnimationSliderBase.__init__(self, parent)
  326. self.timeLabels = []
  327. self.label2 = wx.StaticText(self, id = wx.ID_ANY)
  328. self.label3 = wx.StaticText(self, id = wx.ID_ANY)
  329. self.label2Length = 0
  330. self.temporalType = TemporalType.RELATIVE
  331. self._setLabel()
  332. self._doLayout()
  333. def _doLayout(self):
  334. vbox = wx.BoxSizer(wx.VERTICAL)
  335. hbox = wx.BoxSizer(wx.HORIZONTAL)
  336. hbox.Add(item = self.label1, proportion = 0,
  337. flag = wx.ALIGN_CENTER_VERTICAL, border = 0)
  338. hbox.AddStretchSpacer()
  339. hbox.Add(item = self.indexField, proportion = 0,
  340. flag = wx.ALIGN_CENTER_VERTICAL, border = 0)
  341. hbox.Add(item = self.label2, proportion = 0,
  342. flag = wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border = 3)
  343. hbox.AddStretchSpacer()
  344. hbox.Add(item = self.label3, proportion = 0,
  345. flag = wx.ALIGN_CENTER_VERTICAL, border = 0)
  346. vbox.Add(item = hbox, proportion = 0, flag = wx.EXPAND, border = 0)
  347. hbox = wx.BoxSizer(wx.HORIZONTAL)
  348. hbox.Add(item = self.slider, proportion = 1, flag = wx.ALIGN_CENTER | wx.EXPAND, border = 0)
  349. vbox.Add(item = hbox, proportion = 0, flag = wx.EXPAND, border = 0)
  350. self._setTemporalType()
  351. self.SetSizerAndFit(vbox)
  352. def _setTemporalType(self):
  353. sizer = self.indexField.GetContainingSizer()
  354. # sizer.Show(self.indexField, False) # TODO: what to do?
  355. sizer.Show(self.indexField, self.temporalType == TemporalType.RELATIVE)
  356. self.Layout()
  357. def SetTemporalType(self, mode):
  358. self.temporalType = mode
  359. self._setTemporalType()
  360. def _setFrames(self, timeLabels):
  361. self.timeLabels = timeLabels
  362. self.framesCount = len(timeLabels)
  363. self.slider.SetRange(0, self.framesCount - 1)
  364. self._setLabel()
  365. def _setLabel(self):
  366. if self.timeLabels:
  367. if self.temporalType == TemporalType.ABSOLUTE:
  368. start = self.timeLabels[0][0]
  369. self.label1.SetLabel(start)
  370. if self.timeLabels[-1][1]:
  371. end = self.timeLabels[-1][1]
  372. else:
  373. end = self.timeLabels[-1][0]
  374. self.label3.SetLabel(end)
  375. else:
  376. unit = self.timeLabels[0][2]
  377. start = self.timeLabels[0][0]
  378. self.label1.SetLabel(start)
  379. if self.timeLabels[-1][1]:
  380. end = self.timeLabels[-1][1]
  381. else:
  382. end = self.timeLabels[-1][0]
  383. end = "%(end)s %(unit)s" % {'end': end, 'unit': unit}
  384. self.label3.SetLabel(end)
  385. self.label2Length = len(start)
  386. self._updateFrameIndex(0)
  387. else:
  388. self.label1.SetLabel("")
  389. self.label2.SetLabel("")
  390. self.label3.SetLabel("")
  391. self.Layout()
  392. def _updateFrameIndex(self, index):
  393. start = self.timeLabels[index][0]
  394. if self.timeLabels[index][1]: # interval
  395. if self.temporalType == TemporalType.ABSOLUTE:
  396. label = _("%(from)s %(dash)s %(to)s") % {'from': start, 'dash': u"\u2013", 'to': self.timeLabels[index][1]}
  397. else:
  398. label = _("to %(to)s") % {'to': self.timeLabels[index][1]}
  399. else:
  400. if self.temporalType == TemporalType.ABSOLUTE:
  401. label = start
  402. else:
  403. label = ''
  404. self.label2.SetLabel(label)
  405. if self.temporalType == TemporalType.RELATIVE:
  406. self.indexField.SetValue(start)
  407. if len(label) != self.label2Length:
  408. self.label2Length = len(label)
  409. self.Layout()
  410. def test():
  411. import gettext
  412. gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
  413. import grass.script as grass
  414. app = wx.PySimpleApp()
  415. wx.InitAllImageHandlers()
  416. frame = AnimationFrame(parent = None)
  417. frame.SetAnimations(raster = None, strds = None)
  418. frame.Show()
  419. app.MainLoop()
  420. if __name__ == '__main__':
  421. test()