frame.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. """
  2. @package animation.frame
  3. @brief Animation frame and different types of sliders
  4. Classes:
  5. - frame::AnimationFrame
  6. - frame::AnimationsPanel
  7. - frame::AnimationSliderBase
  8. - frame::SimpleAnimationSlider
  9. - frame::TimeAnimationSlider
  10. (C) 2013 by the GRASS Development Team
  11. This program is free software under the GNU General Public License
  12. (>=v2). Read the file COPYING that comes with GRASS for details.
  13. @author Anna Petrasova <kratochanna gmail.com>
  14. """
  15. import os
  16. import wx
  17. import wx.aui
  18. import six
  19. import grass.script as gcore
  20. import grass.temporal as tgis
  21. from grass.exceptions import FatalError
  22. from core import globalvar
  23. from gui_core.widgets import IntegerValidator
  24. from gui_core.wrap import StaticText, TextCtrl, Slider
  25. from core.gcmd import RunCommand, GWarning
  26. from animation.mapwindow import AnimationWindow
  27. from animation.provider import BitmapProvider, BitmapPool, MapFilesPool, CleanUp
  28. from animation.controller import AnimationController
  29. from animation.anim import Animation
  30. from animation.toolbars import MainToolbar, AnimationToolbar, MiscToolbar
  31. from animation.dialogs import SpeedDialog, PreferencesDialog
  32. from animation.utils import Orientation, ReplayMode, TemporalType
  33. MAX_COUNT = 4
  34. TMP_DIR = None
  35. gcore.set_raise_on_error(True)
  36. class AnimationFrame(wx.Frame):
  37. def __init__(
  38. self, parent, giface, title=_("Animation Tool"), rasters=None, timeseries=None
  39. ):
  40. wx.Frame.__init__(
  41. self, parent, title=title, style=wx.DEFAULT_FRAME_STYLE, size=(800, 600)
  42. )
  43. self._giface = giface
  44. self.SetClientSize(self.GetSize())
  45. self.iconsize = (16, 16)
  46. self.SetIcon(
  47. wx.Icon(
  48. os.path.join(globalvar.ICONDIR, "grass_map.ico"), wx.BITMAP_TYPE_ICO
  49. )
  50. )
  51. # Make sure the temporal database exists
  52. try:
  53. tgis.init()
  54. except FatalError as e:
  55. GWarning(parent=self, message=str(e))
  56. # create temporal directory and ensure it's deleted after programs ends
  57. # (stored in MAPSET/.tmp/)
  58. global TMP_DIR
  59. TMP_DIR = gcore.tempdir()
  60. self.animations = [Animation() for i in range(MAX_COUNT)]
  61. self.windows = []
  62. self.animationPanel = AnimationsPanel(
  63. self, self.windows, initialCount=MAX_COUNT
  64. )
  65. bitmapPool = BitmapPool()
  66. mapFilesPool = MapFilesPool()
  67. self._progressDlg = None
  68. self._progressDlgMax = None
  69. self.provider = BitmapProvider(
  70. bitmapPool=bitmapPool, mapFilesPool=mapFilesPool, tempDir=TMP_DIR
  71. )
  72. self.animationSliders = {}
  73. self.animationSliders["nontemporal"] = SimpleAnimationSlider(self)
  74. self.animationSliders["temporal"] = TimeAnimationSlider(self)
  75. self.controller = AnimationController(
  76. frame=self,
  77. sliders=self.animationSliders,
  78. animations=self.animations,
  79. mapwindows=self.windows,
  80. provider=self.provider,
  81. bitmapPool=bitmapPool,
  82. mapFilesPool=mapFilesPool,
  83. )
  84. for win in self.windows:
  85. win.Bind(wx.EVT_SIZE, self.FrameSizeChanged)
  86. self.provider.mapsLoaded.connect(lambda: self.SetStatusText(""))
  87. self.provider.renderingStarted.connect(self._showRenderingProgress)
  88. self.provider.renderingContinues.connect(self._updateProgress)
  89. self.provider.renderingFinished.connect(self._closeProgress)
  90. self.provider.compositionStarted.connect(self._showRenderingProgress)
  91. self.provider.compositionContinues.connect(self._updateProgress)
  92. self.provider.compositionFinished.connect(self._closeProgress)
  93. self.InitStatusbar()
  94. self._mgr = wx.aui.AuiManager(self)
  95. # toolbars
  96. self.toolbars = {}
  97. self._addToolbars()
  98. self._addPanes()
  99. self._mgr.Update()
  100. self.dialogs = dict()
  101. self.dialogs["speed"] = None
  102. self.dialogs["preferences"] = None
  103. self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
  104. def InitStatusbar(self):
  105. """Init statusbar."""
  106. self.CreateStatusBar(number=1, style=0)
  107. def _addPanes(self):
  108. self._mgr.AddPane(
  109. self.animationPanel,
  110. wx.aui.AuiPaneInfo()
  111. .CentrePane()
  112. .Name("animPanel")
  113. .CentrePane()
  114. .CaptionVisible(False)
  115. .PaneBorder(False)
  116. .Floatable(False)
  117. .BestSize((-1, -1))
  118. .CloseButton(False)
  119. .DestroyOnClose(True)
  120. .Layer(0),
  121. )
  122. for name, slider in six.iteritems(self.animationSliders):
  123. self._mgr.AddPane(
  124. slider,
  125. wx.aui.AuiPaneInfo()
  126. .PaneBorder(False)
  127. .Name("slider_" + name)
  128. .Layer(1)
  129. .CaptionVisible(False)
  130. .BestSize(slider.GetBestSize())
  131. .DestroyOnClose(True)
  132. .CloseButton(False)
  133. .Bottom(),
  134. )
  135. self._mgr.GetPane("slider_" + name).Hide()
  136. def _addToolbars(self):
  137. """Add toolbars to the window
  138. Currently known toolbars are:
  139. - 'mainToolbar' - data management
  140. - 'animationToolbar' - animation controls
  141. - 'miscToolbar' - help, close
  142. """
  143. self.toolbars["mainToolbar"] = MainToolbar(self)
  144. self._mgr.AddPane(
  145. self.toolbars["mainToolbar"],
  146. wx.aui.AuiPaneInfo()
  147. .Name("mainToolbar")
  148. .Caption(_("Main Toolbar"))
  149. .ToolbarPane()
  150. .Top()
  151. .LeftDockable(False)
  152. .RightDockable(False)
  153. .BottomDockable(True)
  154. .TopDockable(True)
  155. .CloseButton(False)
  156. .Layer(2)
  157. .Row(1)
  158. .Position(0)
  159. .BestSize((self.toolbars["mainToolbar"].GetBestSize())),
  160. )
  161. self.toolbars["animationToolbar"] = AnimationToolbar(self)
  162. self._mgr.AddPane(
  163. self.toolbars["animationToolbar"],
  164. wx.aui.AuiPaneInfo()
  165. .Name("animationToolbar")
  166. .Caption(_("Animation Toolbar"))
  167. .ToolbarPane()
  168. .Top()
  169. .LeftDockable(False)
  170. .RightDockable(False)
  171. .BottomDockable(True)
  172. .TopDockable(True)
  173. .CloseButton(False)
  174. .Layer(2)
  175. .Row(1)
  176. .Position(1)
  177. .BestSize((self.toolbars["animationToolbar"].GetBestSize())),
  178. )
  179. self.controller.SetAnimationToolbar(self.toolbars["animationToolbar"])
  180. self.toolbars["miscToolbar"] = MiscToolbar(self)
  181. self._mgr.AddPane(
  182. self.toolbars["miscToolbar"],
  183. wx.aui.AuiPaneInfo()
  184. .Name("miscToolbar")
  185. .Caption(_("Misc Toolbar"))
  186. .ToolbarPane()
  187. .Top()
  188. .LeftDockable(False)
  189. .RightDockable(False)
  190. .BottomDockable(True)
  191. .TopDockable(True)
  192. .CloseButton(False)
  193. .Layer(2)
  194. .Row(1)
  195. .Position(2)
  196. .BestSize((self.toolbars["miscToolbar"].GetBestSize())),
  197. )
  198. def SetAnimations(self, layerLists):
  199. """Set animation data
  200. :param layerLists: list of layerLists
  201. """
  202. self.controller.SetAnimations(layerLists)
  203. def OnAddAnimation(self, event):
  204. self.controller.AddAnimation()
  205. def AddWindow(self, index):
  206. self.animationPanel.AddWindow(index)
  207. def RemoveWindow(self, index):
  208. self.animationPanel.RemoveWindow(index)
  209. def IsWindowShown(self, index):
  210. return self.animationPanel.IsWindowShown(index)
  211. def OnEditAnimation(self, event):
  212. self.controller.EditAnimations()
  213. def SetSlider(self, name):
  214. if name == "nontemporal":
  215. self._mgr.GetPane("slider_nontemporal").Show()
  216. self._mgr.GetPane("slider_temporal").Hide()
  217. elif name == "temporal":
  218. self._mgr.GetPane("slider_temporal").Show()
  219. self._mgr.GetPane("slider_nontemporal").Hide()
  220. else:
  221. self._mgr.GetPane("slider_temporal").Hide()
  222. self._mgr.GetPane("slider_nontemporal").Hide()
  223. self._mgr.Update()
  224. def OnPlayForward(self, event):
  225. self.controller.SetOrientation(Orientation.FORWARD)
  226. self.controller.StartAnimation()
  227. def OnPlayBack(self, event):
  228. self.controller.SetOrientation(Orientation.BACKWARD)
  229. self.controller.StartAnimation()
  230. def OnPause(self, event):
  231. self.controller.PauseAnimation(paused=event.IsChecked())
  232. def OnStop(self, event):
  233. self.controller.EndAnimation()
  234. def OnOneDirectionReplay(self, event):
  235. if event.IsChecked():
  236. mode = ReplayMode.REPEAT
  237. else:
  238. mode = ReplayMode.ONESHOT
  239. self.controller.SetReplayMode(mode)
  240. def OnBothDirectionReplay(self, event):
  241. if event.IsChecked():
  242. mode = ReplayMode.REVERSE
  243. else:
  244. mode = ReplayMode.ONESHOT
  245. self.controller.SetReplayMode(mode)
  246. def OnAdjustSpeed(self, event):
  247. win = self.dialogs["speed"]
  248. if win:
  249. win.SetTemporalMode(self.controller.GetTemporalMode())
  250. win.SetTimeGranularity(self.controller.GetTimeGranularity())
  251. win.InitTimeSpin(self.controller.GetTimeTick())
  252. if win.IsShown():
  253. win.SetFocus()
  254. else:
  255. win.Show()
  256. else: # start
  257. win = SpeedDialog(
  258. self,
  259. temporalMode=self.controller.GetTemporalMode(),
  260. timeGranularity=self.controller.GetTimeGranularity(),
  261. initialSpeed=self.controller.timeTick,
  262. )
  263. win.CenterOnParent()
  264. self.dialogs["speed"] = win
  265. win.speedChanged.connect(self.ChangeSpeed)
  266. win.Show()
  267. def ChangeSpeed(self, ms):
  268. self.controller.timeTick = ms
  269. def Reload(self, event):
  270. self.controller.Reload()
  271. def _showRenderingProgress(self, count):
  272. # the message is not really visible, it's there for the initial dlg
  273. # size
  274. self._progressDlg = wx.ProgressDialog(
  275. title=_("Loading data"),
  276. message="Loading data started, please be patient.",
  277. maximum=count,
  278. parent=self,
  279. style=wx.PD_CAN_ABORT | wx.PD_APP_MODAL | wx.PD_AUTO_HIDE | wx.PD_SMOOTH,
  280. )
  281. self._progressDlgMax = count
  282. def _updateProgress(self, current, text):
  283. text += _(" ({c} out of {p})").format(c=current, p=self._progressDlgMax)
  284. keepGoing, skip = self._progressDlg.Update(current, text)
  285. if not keepGoing:
  286. self.provider.RequestStopRendering()
  287. def _closeProgress(self):
  288. self._progressDlg.Destroy()
  289. self._progressDlg = None
  290. def OnExportAnimation(self, event):
  291. self.controller.Export()
  292. def FrameSizeChanged(self, event):
  293. maxWidth = maxHeight = 0
  294. for win in self.windows:
  295. w, h = win.GetClientSize()
  296. if w >= maxWidth and h >= maxHeight:
  297. maxWidth, maxHeight = w, h
  298. self.provider.WindowSizeChanged(maxWidth, maxHeight)
  299. event.Skip()
  300. def OnPreferences(self, event):
  301. if not self.dialogs["preferences"]:
  302. dlg = PreferencesDialog(parent=self, giface=self._giface)
  303. self.dialogs["preferences"] = dlg
  304. dlg.formatChanged.connect(lambda: self.controller.UpdateAnimations())
  305. dlg.CenterOnParent()
  306. self.dialogs["preferences"].Show()
  307. def OnHelp(self, event):
  308. RunCommand("g.manual", quiet=True, entry="wxGUI.animation")
  309. def OnCloseWindow(self, event):
  310. if self.controller.timer.IsRunning():
  311. self.controller.timer.Stop()
  312. CleanUp(TMP_DIR)()
  313. self._mgr.UnInit()
  314. self.Destroy()
  315. def __del__(self):
  316. """It might not be called, therefore we try to clean it all in OnCloseWindow."""
  317. if hasattr(self, "controller") and hasattr(self.controller, "timer"):
  318. if self.controller.timer.IsRunning():
  319. self.controller.timer.Stop()
  320. CleanUp(TMP_DIR)()
  321. class AnimationsPanel(wx.Panel):
  322. def __init__(self, parent, windows, initialCount=4):
  323. wx.Panel.__init__(self, parent, id=wx.ID_ANY, style=wx.NO_BORDER)
  324. self.shown = []
  325. self.count = initialCount
  326. self.mainSizer = wx.FlexGridSizer(cols=2, hgap=0, vgap=0)
  327. for i in range(initialCount):
  328. w = AnimationWindow(self)
  329. windows.append(w)
  330. self.mainSizer.Add(w, proportion=1, flag=wx.EXPAND)
  331. self.mainSizer.AddGrowableCol(0)
  332. self.mainSizer.AddGrowableCol(1)
  333. self.mainSizer.AddGrowableRow(0)
  334. self.mainSizer.AddGrowableRow(1)
  335. self.windows = windows
  336. self.SetSizerAndFit(self.mainSizer)
  337. for i in range(initialCount):
  338. self.mainSizer.Hide(windows[i])
  339. self.Layout()
  340. def AddWindow(self, index):
  341. if len(self.shown) == self.count:
  342. return
  343. self.mainSizer.Show(self.windows[index])
  344. self.shown.append(index)
  345. self.Layout()
  346. def RemoveWindow(self, index):
  347. if len(self.shown) == 0:
  348. return
  349. self.mainSizer.Hide(self.windows[index])
  350. self.shown.remove(index)
  351. self.Layout()
  352. def IsWindowShown(self, index):
  353. return self.mainSizer.IsShown(self.windows[index])
  354. class AnimationSliderBase(wx.Panel):
  355. def __init__(self, parent):
  356. wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
  357. self.label1 = StaticText(self, id=wx.ID_ANY)
  358. self.slider = Slider(self, id=wx.ID_ANY, style=wx.SL_HORIZONTAL)
  359. self.indexField = TextCtrl(
  360. self,
  361. id=wx.ID_ANY,
  362. size=(40, -1),
  363. style=wx.TE_PROCESS_ENTER | wx.TE_RIGHT,
  364. validator=IntegerValidator(),
  365. )
  366. self.callbackSliderChanging = None
  367. self.callbackSliderChanged = None
  368. self.callbackFrameIndexChanged = None
  369. self.framesCount = 0
  370. self.enable = True
  371. self.slider.Bind(wx.EVT_SPIN, self.OnSliderChanging)
  372. self.slider.Bind(wx.EVT_SCROLL_THUMBRELEASE, self.OnSliderChanged)
  373. self.indexField.Bind(wx.EVT_TEXT_ENTER, self.OnFrameIndexChanged)
  374. def UpdateFrame(self, index):
  375. self._updateFrameIndex(index)
  376. if not self.enable:
  377. return
  378. self.slider.SetValue(index)
  379. def _updateFrameIndex(self, index):
  380. raise NotImplementedError
  381. def OnFrameIndexChanged(self, event):
  382. self._onFrameIndexChanged(event)
  383. def SetFrames(self, frames):
  384. self._setFrames(frames)
  385. def _setFrames(self, frames):
  386. raise NotImplementedError
  387. def SetCallbackSliderChanging(self, callback):
  388. self.callbackSliderChanging = callback
  389. def SetCallbackSliderChanged(self, callback):
  390. self.callbackSliderChanged = callback
  391. def SetCallbackFrameIndexChanged(self, callback):
  392. self.callbackFrameIndexChanged = callback
  393. def EnableSlider(self, enable=True):
  394. if enable and self.framesCount <= 1:
  395. enable = False # we don't want to enable it
  396. self.enable = enable
  397. self.slider.Enable(enable)
  398. self.indexField.Enable(enable)
  399. def OnSliderChanging(self, event):
  400. self.callbackSliderChanging(event.GetInt())
  401. def OnSliderChanged(self, event):
  402. self.callbackSliderChanged()
  403. def _onFrameIndexChanged(self, event):
  404. index = self.indexField.GetValue()
  405. index = self._validate(index)
  406. if index is not None:
  407. self.slider.SetValue(index)
  408. self.callbackFrameIndexChanged(index)
  409. def _validate(self, index):
  410. try:
  411. index = int(index)
  412. except ValueError:
  413. index = self.slider.GetValue()
  414. self.indexField.SetValue(str(index + 1))
  415. return None
  416. start, end = self.slider.GetRange()
  417. index -= 1
  418. if index > end:
  419. index = end
  420. self.indexField.SetValue(str(end + 1))
  421. elif index < start:
  422. index = start
  423. self.indexField.SetValue(str(start + 1))
  424. return index
  425. class SimpleAnimationSlider(AnimationSliderBase):
  426. def __init__(self, parent):
  427. AnimationSliderBase.__init__(self, parent)
  428. self._setLabel()
  429. self._doLayout()
  430. def _doLayout(self):
  431. hbox = wx.BoxSizer(wx.HORIZONTAL)
  432. hbox.Add(
  433. self.indexField, proportion=0, flag=wx.ALIGN_CENTER | wx.LEFT, border=5
  434. )
  435. hbox.Add(
  436. self.label1,
  437. proportion=0,
  438. flag=wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT,
  439. border=5,
  440. )
  441. hbox.Add(self.slider, proportion=1, flag=wx.EXPAND, border=0)
  442. self.SetSizerAndFit(hbox)
  443. def _setFrames(self, count):
  444. self.framesCount = count
  445. if self.framesCount > 1:
  446. self.slider.SetRange(0, self.framesCount - 1)
  447. self.EnableSlider(True)
  448. else:
  449. self.EnableSlider(False)
  450. self._setLabel()
  451. def _setLabel(self):
  452. label = "/ %(framesCount)s" % {"framesCount": self.framesCount}
  453. self.label1.SetLabel(label)
  454. self.Layout()
  455. def _updateFrameIndex(self, index):
  456. self.indexField.SetValue(str(index + 1))
  457. class TimeAnimationSlider(AnimationSliderBase):
  458. def __init__(self, parent):
  459. AnimationSliderBase.__init__(self, parent)
  460. self.timeLabels = []
  461. self.label2 = StaticText(self, id=wx.ID_ANY)
  462. self.label3 = StaticText(self, id=wx.ID_ANY)
  463. self.label2Length = 0
  464. self.temporalType = TemporalType.RELATIVE
  465. self._setLabel()
  466. self._doLayout()
  467. def _doLayout(self):
  468. vbox = wx.BoxSizer(wx.VERTICAL)
  469. hbox = wx.BoxSizer(wx.HORIZONTAL)
  470. hbox.Add(self.label1, proportion=0, flag=wx.ALIGN_CENTER_VERTICAL, border=0)
  471. hbox.AddStretchSpacer()
  472. hbox.Add(self.indexField, proportion=0, flag=wx.ALIGN_CENTER_VERTICAL, border=0)
  473. hbox.Add(
  474. self.label2, proportion=0, flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=3
  475. )
  476. hbox.AddStretchSpacer()
  477. hbox.Add(self.label3, proportion=0, flag=wx.ALIGN_CENTER_VERTICAL, border=0)
  478. vbox.Add(hbox, proportion=0, flag=wx.EXPAND, border=0)
  479. hbox = wx.BoxSizer(wx.HORIZONTAL)
  480. hbox.Add(self.slider, proportion=1, flag=wx.EXPAND, border=0)
  481. vbox.Add(hbox, proportion=0, flag=wx.EXPAND, border=0)
  482. self._setTemporalType()
  483. self.SetSizerAndFit(vbox)
  484. def _setTemporalType(self):
  485. sizer = self.indexField.GetContainingSizer()
  486. # sizer.Show(self.indexField, False) # TODO: what to do?
  487. sizer.Show(self.indexField, self.temporalType == TemporalType.RELATIVE)
  488. self.Layout()
  489. def SetTemporalType(self, mode):
  490. self.temporalType = mode
  491. self._setTemporalType()
  492. def _setFrames(self, timeLabels):
  493. self.timeLabels = timeLabels
  494. self.framesCount = len(timeLabels)
  495. if self.framesCount > 1:
  496. self.slider.SetRange(0, self.framesCount - 1)
  497. self.EnableSlider(True)
  498. else:
  499. self.EnableSlider(False)
  500. self._setLabel()
  501. # TODO: fix setting index values, until then:
  502. self.indexField.Disable()
  503. def _setLabel(self):
  504. if self.timeLabels:
  505. if self.temporalType == TemporalType.ABSOLUTE:
  506. start = self.timeLabels[0][0]
  507. self.label1.SetLabel(start)
  508. if self.timeLabels[-1][1]:
  509. end = self.timeLabels[-1][1]
  510. else:
  511. end = self.timeLabels[-1][0]
  512. self.label3.SetLabel(end)
  513. else:
  514. unit = self.timeLabels[0][2]
  515. start = self.timeLabels[0][0]
  516. self.label1.SetLabel(start)
  517. if self.timeLabels[-1][1]:
  518. end = self.timeLabels[-1][1]
  519. else:
  520. end = self.timeLabels[-1][0]
  521. end = "%(end)s %(unit)s" % {"end": end, "unit": unit}
  522. self.label3.SetLabel(end)
  523. self.label2Length = len(start)
  524. self._updateFrameIndex(0)
  525. else:
  526. self.label1.SetLabel("")
  527. self.label2.SetLabel("")
  528. self.label3.SetLabel("")
  529. self.Layout()
  530. def _updateFrameIndex(self, index):
  531. start = self.timeLabels[index][0]
  532. if self.timeLabels[index][1]: # interval
  533. if self.temporalType == TemporalType.ABSOLUTE:
  534. label = _("%(from)s %(dash)s %(to)s") % {
  535. "from": start,
  536. "dash": "\u2013",
  537. "to": self.timeLabels[index][1],
  538. }
  539. else:
  540. label = _("to %(to)s") % {"to": self.timeLabels[index][1]}
  541. else:
  542. if self.temporalType == TemporalType.ABSOLUTE:
  543. label = start
  544. else:
  545. label = ""
  546. self.label2.SetLabel(label)
  547. if self.temporalType == TemporalType.RELATIVE:
  548. self.indexField.SetValue(start)
  549. if len(label) != self.label2Length:
  550. self.label2Length = len(label)
  551. self.Layout()