frame.py 18 KB

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