1"""
2@package animation.dialogs
3
4@brief Dialogs for animation management, changing speed of animation
5
6Classes:
7 - dialogs::SpeedDialog
8 - dialogs::InputDialog
9 - dialogs::EditDialog
10 - dialogs::ExportDialog
11 - dialogs::AnimSimpleLayerManager
12 - dialogs::AddTemporalLayerDialog
13
14
15(C) 2013 by the GRASS Development Team
16
17This program is free software under the GNU General Public License
18(>=v2). Read the file COPYING that comes with GRASS for details.
19
20@author Anna Petrasova <kratochanna gmail.com>
21"""
22
23from __future__ import print_function
24
25import os
26import wx
27import copy
28import datetime
29import wx.lib.filebrowsebutton as filebrowse
30import wx.lib.scrolledpanel as SP
31import wx.lib.colourselect as csel
32try:
33    from wx.adv import HyperlinkCtrl
34except ImportError:
35    from wx import HyperlinkCtrl
36
37from core.gcmd import GMessage, GError, GException
38from core import globalvar
39from gui_core.dialogs import MapLayersDialog, GetImageHandlers
40from gui_core.preferences import PreferencesBaseDialog
41from gui_core.forms import GUI
42from core.settings import UserSettings
43from gui_core.gselect import Select
44from gui_core.widgets import FloatValidator
45from gui_core.wrap import BitmapButton, Button, CheckBox, Choice, \
46    ComboBox, EmptyImage, RadioButton, SpinCtrl, StaticBox, StaticText, TextCtrl
47
48from animation.utils import TemporalMode, getRegisteredMaps, getNameAndLayer, getCpuCount
49from animation.data import AnimationData, AnimLayer
50from animation.toolbars import AnimSimpleLmgrToolbar, SIMPLE_LMGR_STDS
51from gui_core.simplelmgr import SimpleLayerManager, \
52    SIMPLE_LMGR_RASTER, SIMPLE_LMGR_VECTOR, SIMPLE_LMGR_TB_TOP
53
54from grass.pydispatch.signal import Signal
55import grass.script.core as gcore
56
57
58class SpeedDialog(wx.Dialog):
59
60    def __init__(self, parent, title=_("Adjust speed of animation"),
61                 temporalMode=None, minimumDuration=0, timeGranularity=None,
62                 initialSpeed=200):
63        wx.Dialog.__init__(self, parent=parent, id=wx.ID_ANY, title=title,
64                           style=wx.DEFAULT_DIALOG_STYLE)
65        # signal emitted when speed has changed; has attribute 'ms'
66        self.speedChanged = Signal('SpeedDialog.speedChanged')
67        self.minimumDuration = minimumDuration
68        # self.framesCount = framesCount
69        self.defaultSpeed = initialSpeed
70        self.lastAppliedValue = self.defaultSpeed
71        self.lastAppliedValueTemp = self.defaultSpeed
72
73        self._layout()
74
75        self.temporalMode = temporalMode
76        self.timeGranularity = timeGranularity
77
78        self._fillUnitChoice(self.choiceUnits)
79        self.InitTimeSpin(self.defaultSpeed)
80
81    def SetTimeGranularity(self, gran):
82        self._timeGranularity = gran
83
84    def GetTimeGranularity(self):
85        return self._timeGranularity
86
87    timeGranularity = property(
88        fset=SetTimeGranularity,
89        fget=GetTimeGranularity)
90
91    def SetTemporalMode(self, mode):
92        self._temporalMode = mode
93        self._setTemporalMode()
94
95    def GetTemporalMode(self):
96        return self._temporalMode
97
98    temporalMode = property(fset=SetTemporalMode, fget=GetTemporalMode)
99
100    def _layout(self):
101        """Layout window"""
102        mainSizer = wx.BoxSizer(wx.VERTICAL)
103        #
104        # simple mode
105        #
106        self.nontemporalBox = StaticBox(parent=self, id=wx.ID_ANY,
107                                        label=' %s ' % _("Simple mode"))
108        box = wx.StaticBoxSizer(self.nontemporalBox, wx.VERTICAL)
109        gridSizer = wx.GridBagSizer(hgap=5, vgap=5)
110
111        labelDuration = StaticText(
112            self, id=wx.ID_ANY, label=_("Frame duration:"))
113        labelUnits = StaticText(self, id=wx.ID_ANY, label=_("ms"))
114        self.spinDuration = SpinCtrl(
115            self,
116            id=wx.ID_ANY,
117            min=self.minimumDuration,
118            max=10000,
119            initial=self.defaultSpeed)
120        # TODO total time
121
122        gridSizer.Add(
123            labelDuration, pos=(0, 0),
124            flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
125        gridSizer.Add(self.spinDuration, pos=(0, 1), flag=wx.ALIGN_CENTER)
126        gridSizer.Add(
127            labelUnits, pos=(0, 2),
128            flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
129        gridSizer.AddGrowableCol(0)
130
131        box.Add(
132            gridSizer,
133            proportion=1,
134            border=5,
135            flag=wx.ALL | wx.EXPAND)
136        self.nontemporalSizer = gridSizer
137        mainSizer.Add(
138            box,
139            proportion=0,
140            flag=wx.EXPAND | wx.ALL,
141            border=5)
142        #
143        # temporal mode
144        #
145        self.temporalBox = StaticBox(parent=self, id=wx.ID_ANY,
146                                     label=' %s ' % _("Temporal mode"))
147        box = wx.StaticBoxSizer(self.temporalBox, wx.VERTICAL)
148        gridSizer = wx.GridBagSizer(hgap=5, vgap=5)
149
150        labelTimeUnit = StaticText(
151            self, id=wx.ID_ANY, label=_("Time unit:"))
152        labelDuration = StaticText(
153            self, id=wx.ID_ANY, label=_("Duration of time unit:"))
154        labelUnits = StaticText(self, id=wx.ID_ANY, label=_("ms"))
155        self.spinDurationTemp = SpinCtrl(
156            self, id=wx.ID_ANY, min=self.minimumDuration, max=10000,
157            initial=self.defaultSpeed)
158        self.choiceUnits = wx.Choice(self, id=wx.ID_ANY)
159
160        # TODO total time
161
162        gridSizer.Add(
163            labelTimeUnit, pos=(0, 0),
164            flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
165        gridSizer.Add(self.choiceUnits, pos=(0, 1),
166                      flag=wx.ALIGN_CENTER | wx.EXPAND)
167        gridSizer.Add(
168            labelDuration, pos=(1, 0),
169            flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
170        gridSizer.Add(
171            self.spinDurationTemp, pos=(
172                1, 1), flag=wx.ALIGN_CENTER | wx.EXPAND)
173        gridSizer.Add(
174            labelUnits, pos=(1, 2),
175            flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
176        gridSizer.AddGrowableCol(1)
177
178        self.temporalSizer = gridSizer
179        box.Add(
180            gridSizer,
181            proportion=1,
182            border=5,
183            flag=wx.ALL | wx.EXPAND)
184        mainSizer.Add(
185            box,
186            proportion=0,
187            flag=wx.EXPAND | wx.ALL,
188            border=5)
189
190        self.btnOk = Button(self, wx.ID_OK)
191        self.btnApply = Button(self, wx.ID_APPLY)
192        self.btnCancel = Button(self, wx.ID_CANCEL)
193        self.btnOk.SetDefault()
194
195        self.btnOk.Bind(wx.EVT_BUTTON, self.OnOk)
196        self.btnApply.Bind(wx.EVT_BUTTON, self.OnApply)
197        self.btnCancel.Bind(wx.EVT_BUTTON, self.OnCancel)
198        self.Bind(wx.EVT_CLOSE, self.OnCancel)
199        # button sizer
200        btnStdSizer = wx.StdDialogButtonSizer()
201        btnStdSizer.AddButton(self.btnOk)
202        btnStdSizer.AddButton(self.btnApply)
203        btnStdSizer.AddButton(self.btnCancel)
204        btnStdSizer.Realize()
205
206        mainSizer.Add(btnStdSizer, proportion=0,
207                      flag=wx.EXPAND | wx.ALL, border=5)
208
209        self.SetSizer(mainSizer)
210        mainSizer.Fit(self)
211
212    def _setTemporalMode(self):
213        self.nontemporalBox.Enable(
214            self.temporalMode == TemporalMode.NONTEMPORAL)
215        self.temporalBox.Enable(self.temporalMode == TemporalMode.TEMPORAL)
216        for child in self.temporalSizer.GetChildren():
217            child.GetWindow().Enable(self.temporalMode == TemporalMode.TEMPORAL)
218        for child in self.nontemporalSizer.GetChildren():
219            child.GetWindow().Enable(self.temporalMode == TemporalMode.NONTEMPORAL)
220
221        self.Layout()
222
223    def _fillUnitChoice(self, choiceWidget):
224        timeUnitsChoice = [
225            _("year"),
226            _("month"),
227            _("day"),
228            _("hour"),
229            _("minute"),
230            _("second")]
231        timeUnits = ["years", "months", "days", "hours", "minutes", "seconds"]
232        for item, cdata in zip(timeUnitsChoice, timeUnits):
233            choiceWidget.Append(item, cdata)
234
235        if self.temporalMode == TemporalMode.TEMPORAL:
236            unit = self.timeGranularity[1]
237            index = 0
238            for i, timeUnit in enumerate(timeUnits):
239                if timeUnit.startswith(unit):
240                    index = i
241                    break
242            choiceWidget.SetSelection(index)
243        else:
244            choiceWidget.SetSelection(0)
245
246    def OnOk(self, event):
247        self._apply()
248        self.OnCancel(None)
249
250    def OnApply(self, event):
251        self._apply()
252
253    def OnCancel(self, event):
254        self.spinDuration.SetValue(self.lastAppliedValue)
255        self.spinDurationTemp.SetValue(self.lastAppliedValueTemp)
256        self.Hide()
257
258    def InitTimeSpin(self, timeTick):
259        if self.temporalMode == TemporalMode.TEMPORAL:
260            index = self.choiceUnits.GetSelection()
261            unit = self.choiceUnits.GetClientData(index)
262            delta = self._timedelta(unit=unit, number=1)
263            seconds1 = self._total_seconds(delta)
264
265            number, unit = self.timeGranularity
266            number = float(number)
267            delta = self._timedelta(unit=unit, number=number)
268            seconds2 = self._total_seconds(delta)
269            value = timeTick
270            ms = value * seconds1 / float(seconds2)
271            self.spinDurationTemp.SetValue(ms)
272        else:
273            self.spinDuration.SetValue(timeTick)
274
275    def _apply(self):
276        if self.temporalMode == TemporalMode.NONTEMPORAL:
277            ms = self.spinDuration.GetValue()
278            self.lastAppliedValue = self.spinDuration.GetValue()
279        elif self.temporalMode == TemporalMode.TEMPORAL:
280            index = self.choiceUnits.GetSelection()
281            unit = self.choiceUnits.GetClientData(index)
282            delta = self._timedelta(unit=unit, number=1)
283            seconds1 = self._total_seconds(delta)
284
285            number, unit = self.timeGranularity
286            number = float(number)
287            delta = self._timedelta(unit=unit, number=number)
288            seconds2 = self._total_seconds(delta)
289
290            value = self.spinDurationTemp.GetValue()
291            ms = value * seconds2 / float(seconds1)
292            # minimumDuration set to 0, too restrictive
293            if ms < self.minimumDuration:
294                GMessage(parent=self, message=_(
295                    "Animation speed is too high."))
296                return
297            self.lastAppliedValueTemp = self.spinDurationTemp.GetValue()
298        else:
299            return
300
301        self.speedChanged.emit(ms=ms)
302
303    def _timedelta(self, unit, number):
304        if unit in "years":
305            delta = datetime.timedelta(days=365.25 * number)
306        elif unit in "months":
307            delta = datetime.timedelta(days=30.4375 * number)  # 365.25/12
308        elif unit in "days":
309            delta = datetime.timedelta(days=1 * number)
310        elif unit in "hours":
311            delta = datetime.timedelta(hours=1 * number)
312        elif unit in "minutes":
313            delta = datetime.timedelta(minutes=1 * number)
314        elif unit in "seconds":
315            delta = datetime.timedelta(seconds=1 * number)
316
317        return delta
318
319    def _total_seconds(self, delta):
320        """timedelta.total_seconds is new in version 2.7.
321        """
322        return delta.seconds + delta.days * 24 * 3600
323
324
325class InputDialog(wx.Dialog):
326
327    def __init__(self, parent, mode, animationData):
328        wx.Dialog.__init__(self, parent=parent, id=wx.ID_ANY,
329                           style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
330        if mode == 'add':
331            self.SetTitle(_("Add new animation"))
332        elif mode == 'edit':
333            self.SetTitle(_("Edit animation"))
334
335        self.animationData = animationData
336        self._tmpLegendCmd = None
337
338        self._layout()
339        self.OnViewMode(event=None)
340
341    def _layout(self):
342        self.notebook = wx.Notebook(parent=self, style=wx.BK_DEFAULT)
343        sizer = wx.BoxSizer(wx.VERTICAL)
344        self.notebook.AddPage(
345            self._createGeneralPage(
346                self.notebook),
347            _("General"))
348        self.notebook.AddPage(
349            self._createAdvancedPage(
350                self.notebook),
351            _("Advanced"))
352        sizer.Add(
353            self.notebook,
354            proportion=1,
355            flag=wx.ALL | wx.EXPAND,
356            border=3)
357
358        # buttons
359        self.btnOk = Button(self, wx.ID_OK)
360        self.btnCancel = Button(self, wx.ID_CANCEL)
361        self.btnOk.SetDefault()
362        self.btnOk.Bind(wx.EVT_BUTTON, self.OnOk)
363        # button sizer
364        btnStdSizer = wx.StdDialogButtonSizer()
365        btnStdSizer.AddButton(self.btnOk)
366        btnStdSizer.AddButton(self.btnCancel)
367        btnStdSizer.Realize()
368
369        sizer.Add(btnStdSizer, proportion=0,
370                  flag=wx.EXPAND | wx.ALL, border=5)
371        self.SetSizer(sizer)
372        sizer.Fit(self)
373
374    def _createGeneralPage(self, parent):
375        panel = wx.Panel(parent=parent)
376        mainSizer = wx.BoxSizer(wx.VERTICAL)
377
378        self.windowChoice = wx.Choice(
379            panel,
380            id=wx.ID_ANY,
381            choices=[
382                _("top left"),
383                _("top right"),
384                _("bottom left"),
385                _("bottom right")])
386        self.windowChoice.SetSelection(self.animationData.windowIndex)
387
388        self.nameCtrl = TextCtrl(
389            panel, id=wx.ID_ANY, value=self.animationData.name)
390
391        self.nDChoice = Choice(panel, id=wx.ID_ANY)
392        mode = self.animationData.viewMode
393        index = 0
394        for i, (viewMode, viewModeName) in enumerate(
395                self.animationData.viewModes):
396            self.nDChoice.Append(viewModeName, clientData=viewMode)
397            if mode == viewMode:
398                index = i
399
400        self.nDChoice.SetSelection(index)
401        self.nDChoice.SetToolTip(_("Select 2D or 3D view"))
402        self.nDChoice.Bind(wx.EVT_CHOICE, self.OnViewMode)
403
404        gridSizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=5)
405        gridSizer.Add(
406            StaticText(
407                panel,
408                id=wx.ID_ANY,
409                label=_("Name:")),
410            flag=wx.ALIGN_CENTER_VERTICAL)
411        gridSizer.Add(self.nameCtrl, proportion=1, flag=wx.EXPAND)
412        gridSizer.Add(
413            StaticText(
414                panel,
415                id=wx.ID_ANY,
416                label=_("Window position:")),
417            flag=wx.ALIGN_CENTER_VERTICAL)
418        gridSizer.Add(
419            self.windowChoice,
420            proportion=1,
421            flag=wx.ALIGN_RIGHT)
422        gridSizer.Add(
423            StaticText(
424                panel,
425                id=wx.ID_ANY,
426                label=_("View mode:")),
427            flag=wx.ALIGN_CENTER_VERTICAL)
428        gridSizer.Add(self.nDChoice, proportion=1, flag=wx.ALIGN_RIGHT)
429        gridSizer.AddGrowableCol(0, 1)
430        gridSizer.AddGrowableCol(1, 1)
431        mainSizer.Add(
432            gridSizer,
433            proportion=0,
434            flag=wx.ALL | wx.EXPAND,
435            border=5)
436        label = _(
437            "For 3D animation, please select only one space-time dataset\n"
438            "or one series of map layers.")
439        self.warning3DLayers = StaticText(panel, label=label)
440        self.warning3DLayers.SetForegroundColour(
441            wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
442        mainSizer.Add(
443            self.warning3DLayers,
444            proportion=0,
445            flag=wx.EXPAND | wx.LEFT,
446            border=5)
447
448        self.dataPanel = self._createDataPanel(panel)
449        self.threeDPanel = self._create3DPanel(panel)
450        mainSizer.Add(
451            self.dataPanel,
452            proportion=1,
453            flag=wx.EXPAND | wx.ALL,
454            border=3)
455        mainSizer.Add(
456            self.threeDPanel,
457            proportion=0,
458            flag=wx.EXPAND | wx.ALL,
459            border=3)
460
461        panel.SetSizer(mainSizer)
462        mainSizer.Fit(panel)
463
464        return panel
465
466    def _createDataPanel(self, parent):
467        panel = wx.Panel(parent)
468        slmgrSizer = wx.BoxSizer(wx.VERTICAL)
469        self._layerList = copy.deepcopy(self.animationData.layerList)
470        self.simpleLmgr = AnimSimpleLayerManager(parent=panel,
471                                                 layerList=self._layerList,
472                                                 modal=True)
473        self.simpleLmgr.SetMinSize((globalvar.DIALOG_GSELECT_SIZE[0], 80))
474        slmgrSizer.Add(
475            self.simpleLmgr,
476            proportion=1,
477            flag=wx.EXPAND | wx.ALL,
478            border=5)
479
480        self.legend = wx.CheckBox(panel, label=_("Show raster legend"))
481        self.legend.SetValue(bool(self.animationData.legendCmd))
482        self.legendBtn = Button(panel, label=_("Set options"))
483        self.legend.Bind(wx.EVT_CHECKBOX, self.OnLegend)
484        self.legendBtn.Bind(wx.EVT_BUTTON, self.OnLegendProperties)
485
486        hbox = wx.BoxSizer(wx.HORIZONTAL)
487        hbox.Add(self.legend, proportion=1, flag=wx.ALIGN_CENTER_VERTICAL)
488        hbox.Add(self.legendBtn, proportion=0, flag=wx.LEFT, border=5)
489        slmgrSizer.Add(
490            hbox,
491            proportion=0,
492            flag=wx.EXPAND | wx.ALL,
493            border=3)
494
495        panel.SetSizerAndFit(slmgrSizer)
496        panel.SetAutoLayout(True)
497
498        return panel
499
500    def _create3DPanel(self, parent):
501        panel = wx.Panel(parent, id=wx.ID_ANY)
502        dataStBox = StaticBox(parent=panel, id=wx.ID_ANY,
503                              label=' %s ' % _("3D view parameters"))
504        dataBoxSizer = wx.StaticBoxSizer(dataStBox, wx.VERTICAL)
505
506        # workspace file
507        self.fileSelector = filebrowse.FileBrowseButton(
508            parent=panel,
509            id=wx.ID_ANY,
510            size=globalvar.DIALOG_GSELECT_SIZE,
511            labelText=_("Workspace file:"),
512            dialogTitle=_(
513                "Choose workspace file to "
514                "import 3D view parameters"),
515            buttonText=_('Browse'),
516            startDirectory=os.getcwd(),
517            fileMode=0,
518            fileMask="GRASS Workspace File (*.gxw)|*.gxw")
519        if self.animationData.workspaceFile:
520            self.fileSelector.SetValue(self.animationData.workspaceFile)
521        self.paramLabel = StaticText(
522            panel, wx.ID_ANY, label=_("Parameter for animation:"))
523        self.paramChoice = wx.Choice(
524            panel, id=wx.ID_ANY, choices=self.animationData.nvizParameters)
525        self.paramChoice.SetStringSelection(self.animationData.nvizParameter)
526
527        hbox = wx.BoxSizer(wx.HORIZONTAL)
528        hbox.Add(
529            self.fileSelector,
530            proportion=1,
531            flag=wx.EXPAND)
532        dataBoxSizer.Add(
533            hbox,
534            proportion=0,
535            flag=wx.EXPAND | wx.ALL,
536            border=3)
537
538        hbox = wx.BoxSizer(wx.HORIZONTAL)
539        hbox.Add(
540            self.paramLabel,
541            proportion=1,
542            flag=wx.ALIGN_CENTER_VERTICAL)
543        hbox.Add(self.paramChoice, proportion=1, flag=wx.EXPAND)
544        dataBoxSizer.Add(
545            hbox,
546            proportion=0,
547            flag=wx.EXPAND | wx.ALL,
548            border=3)
549
550        panel.SetSizerAndFit(dataBoxSizer)
551        panel.SetAutoLayout(True)
552
553        return panel
554
555    def _createAdvancedPage(self, parent):
556        panel = wx.Panel(parent=parent)
557
558        mainSizer = wx.BoxSizer(wx.VERTICAL)
559        box = StaticBox(
560            parent=panel, label=" %s " %
561            _("Animate region change (2D view only)"))
562        sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
563
564        gridSizer = wx.GridBagSizer(hgap=3, vgap=3)
565        gridSizer.Add(StaticText(panel, label=_("Start region:")),
566                      pos=(0, 0), flag=wx.ALIGN_CENTER_VERTICAL)
567        self.stRegion = Select(parent=panel, type='region', size=(200, -1))
568        if self.animationData.startRegion:
569            self.stRegion.SetValue(self.animationData.startRegion)
570        gridSizer.Add(
571            self.stRegion, pos=(0, 1),
572            flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
573
574        self.endRegRadio = RadioButton(
575            panel, label=_("End region:"), style=wx.RB_GROUP)
576        gridSizer.Add(self.endRegRadio, pos=(1, 0), flag=wx.EXPAND)
577        self.endRegion = Select(parent=panel, type='region', size=(200, -1))
578        gridSizer.Add(
579            self.endRegion, pos=(1, 1),
580            flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
581        self.zoomRadio = RadioButton(panel, label=_("Zoom value:"))
582        self.zoomRadio.SetToolTip(_("N-S/E-W distances in map units used to "
583                                    "gradually reduce region."))
584        gridSizer.Add(self.zoomRadio, pos=(2, 0), flag=wx.EXPAND)
585
586        zoomSizer = wx.BoxSizer(wx.HORIZONTAL)
587        self.zoomNS = TextCtrl(panel, validator=FloatValidator())
588        self.zoomEW = TextCtrl(panel, validator=FloatValidator())
589        zoomSizer.Add(StaticText(panel, label=_("N-S:")), proportion=0,
590                      flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=3)
591        zoomSizer.Add(self.zoomNS, proportion=1, flag=wx.LEFT, border=3)
592        zoomSizer.Add(StaticText(panel, label=_("E-W:")), proportion=0,
593                      flag=wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=3)
594        zoomSizer.Add(self.zoomEW, proportion=1, flag=wx.LEFT, border=3)
595        gridSizer.Add(
596            zoomSizer, pos=(2, 1),
597            flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
598        if self.animationData.endRegion:
599            self.endRegRadio.SetValue(True)
600            self.zoomRadio.SetValue(False)
601            self.endRegion.SetValue(self.animationData.endRegion)
602        if self.animationData.zoomRegionValue:
603            self.endRegRadio.SetValue(False)
604            self.zoomRadio.SetValue(True)
605            zoom = self.animationData.zoomRegionValue
606            self.zoomNS.SetValue(str(zoom[0]))
607            self.zoomEW.SetValue(str(zoom[1]))
608
609        self.endRegRadio.Bind(
610            wx.EVT_RADIOBUTTON,
611            lambda evt: self._enableRegionWidgets())
612        self.zoomRadio.Bind(
613            wx.EVT_RADIOBUTTON,
614            lambda evt: self._enableRegionWidgets())
615        self._enableRegionWidgets()
616
617        gridSizer.AddGrowableCol(1)
618        sizer.Add(gridSizer, proportion=0, flag=wx.EXPAND | wx.ALL, border=3)
619        mainSizer.Add(sizer, proportion=0, flag=wx.EXPAND | wx.ALL, border=3)
620
621        panel.SetSizer(mainSizer)
622        mainSizer.Fit(panel)
623
624        return panel
625
626    def _enableRegionWidgets(self):
627        """Enables/disables region widgets
628        according to which radiobutton is active."""
629        endReg = self.endRegRadio.GetValue()
630        self.endRegion.Enable(endReg)
631        self.zoomNS.Enable(not endReg)
632        self.zoomEW.Enable(not endReg)
633
634    def OnViewMode(self, event):
635        mode = self.nDChoice.GetSelection()
636        self.Freeze()
637        self.simpleLmgr.Activate3D(mode == 1)
638        self.warning3DLayers.Show(mode == 1)
639
640        # disable region widgets for 3d
641        regSizer = self.stRegion.GetContainingSizer()
642        for child in regSizer.GetChildren():
643            if child.IsSizer():
644                for child_ in child.GetSizer().GetChildren():
645                    child_.GetWindow().Enable(mode != 1)
646            elif child.IsWindow():
647                child.GetWindow().Enable(mode != 1)
648        self._enableRegionWidgets()
649
650        # update layout
651        sizer = self.threeDPanel.GetContainingSizer()
652        sizer.Show(self.threeDPanel, mode == 1, True)
653        sizer.Layout()
654        self.Thaw()
655
656    def OnLegend(self, event):
657        if not self.legend.IsChecked():
658            return
659        if self._tmpLegendCmd or self.animationData.legendCmd:
660            return
661        cmd = ['d.legend', 'at=5,50,2,5']
662        GUI(parent=self, modal=True).ParseCommand(
663            cmd=cmd, completed=(self.GetOptData, '', ''))
664
665    def OnLegendProperties(self, event):
666        """Set options for legend"""
667        if self._tmpLegendCmd:
668            cmd = self._tmpLegendCmd
669        elif self.animationData.legendCmd:
670            cmd = self.animationData.legendCmd
671        else:
672            cmd = ['d.legend', 'at=5,50,2,5']
673
674        GUI(parent=self, modal=True).ParseCommand(
675            cmd=cmd, completed=(self.GetOptData, '', ''))
676
677    def GetOptData(self, dcmd, layer, params, propwin):
678        """Process decoration layer data"""
679        if dcmd:
680            self._tmpLegendCmd = dcmd
681
682            if not self.legend.IsChecked():
683                self.legend.SetValue(True)
684        else:
685            if not self._tmpLegendCmd and not self.animationData.legendCmd:
686                self.legend.SetValue(False)
687
688    def _update(self):
689        if self.nDChoice.GetSelection() == 1 and len(self._layerList) > 1:
690            raise GException(_("Only one series or space-time "
691                               "dataset is accepted for 3D mode."))
692        hasSeries = False
693        for layer in self._layerList:
694            if layer.active and hasattr(layer, 'maps'):
695                hasSeries = True
696                break
697        if not hasSeries:
698            raise GException(_("No map series or space-time dataset added."))
699
700        self.animationData.layerList = self._layerList
701        self.animationData.name = self.nameCtrl.GetValue()
702        self.animationData.windowIndex = self.windowChoice.GetSelection()
703
704        sel = self.nDChoice.GetSelection()
705        self.animationData.viewMode = self.nDChoice.GetClientData(sel)
706        self.animationData.legendCmd = None
707        if self._tmpLegendCmd:
708            if self.legend.IsChecked():
709                self.animationData.legendCmd = self._tmpLegendCmd
710
711        if self.threeDPanel.IsShown():
712            self.animationData.workspaceFile = self.fileSelector.GetValue()
713        if self.threeDPanel.IsShown():
714            self.animationData.nvizParameter = self.paramChoice.GetStringSelection()
715        # region (2d only)
716        if self.animationData.viewMode == '3d':
717            self.animationData.startRegion = None
718            self.animationData.endRegion = None
719            self.animationData.zoomRegionValue = None
720            return
721        isEnd = self.endRegRadio.GetValue() and self.endRegion.GetValue()
722        isZoom = self.zoomRadio.GetValue() and self.zoomNS.GetValue() and self.zoomEW.GetValue()
723        isStart = self.stRegion.GetValue()
724        condition = bool(isStart) + bool(isZoom) + bool(isEnd)
725        if condition == 1:
726            raise GException(_("Region information is not complete"))
727        elif condition == 2:
728            self.animationData.startRegion = isStart
729            if isEnd:
730                self.animationData.endRegion = self.endRegion.GetValue()
731                self.animationData.zoomRegionValue = None
732            else:
733                self.animationData.zoomRegionValue = (
734                    float(self.zoomNS.GetValue()),
735                    float(self.zoomEW.GetValue()))
736                self.animationData.endRegion = None
737        else:
738            self.animationData.startRegion = None
739            self.animationData.endRegion = None
740            self.animationData.zoomRegionValue = None
741
742    def UnInit(self):
743        self.simpleLmgr.UnInit()
744
745    def OnOk(self, event):
746        try:
747            self._update()
748            self.UnInit()
749            self.EndModal(wx.ID_OK)
750        except (GException, ValueError, IOError) as e:
751            GError(
752                message=str(e),
753                showTraceback=False,
754                caption=_("Invalid input"))
755
756
757class EditDialog(wx.Dialog):
758
759    def __init__(self, parent, evalFunction, animationData, maxAnimations):
760        wx.Dialog.__init__(self, parent=parent, id=wx.ID_ANY,
761                           style=wx.DEFAULT_DIALOG_STYLE)
762        self.animationData = copy.deepcopy(animationData)
763        self.eval = evalFunction
764        self.SetTitle(_("Add, edit or remove animations"))
765        self._layout()
766        self.SetSize((300, -1))
767        self.maxAnimations = maxAnimations
768        self.result = None
769
770    def _layout(self):
771        mainSizer = wx.BoxSizer(wx.VERTICAL)
772        box = StaticBox(
773            parent=self,
774            id=wx.ID_ANY,
775            label=" %s " %
776            _("List of animations"))
777        sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
778        gridBagSizer = wx.GridBagSizer(hgap=5, vgap=5)
779        gridBagSizer.AddGrowableCol(0)
780        # gridBagSizer.AddGrowableCol(1,1)
781
782        self.listbox = wx.ListBox(
783            self, id=wx.ID_ANY, choices=[],
784            style=wx.LB_SINGLE | wx.LB_NEEDED_SB)
785        self.listbox.Bind(wx.EVT_LISTBOX_DCLICK, self.OnEdit)
786
787        self.addButton = Button(self, id=wx.ID_ANY, label=_("Add"))
788        self.addButton.Bind(wx.EVT_BUTTON, self.OnAdd)
789        self.editButton = Button(self, id=wx.ID_ANY, label=_("Edit"))
790        self.editButton.Bind(wx.EVT_BUTTON, self.OnEdit)
791        self.removeButton = Button(self, id=wx.ID_ANY, label=_("Remove"))
792        self.removeButton.Bind(wx.EVT_BUTTON, self.OnRemove)
793
794        self._updateListBox()
795
796        gridBagSizer.Add(self.listbox, pos=(0, 0), span=(3, 1),
797                         flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, border=0)
798        gridBagSizer.Add(self.addButton, pos=(0, 1),
799                         flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, border=0)
800        gridBagSizer.Add(self.editButton, pos=(1, 1),
801                         flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, border=0)
802        gridBagSizer.Add(self.removeButton, pos=(2, 1),
803                         flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, border=0)
804        sizer.Add(
805            gridBagSizer,
806            proportion=0,
807            flag=wx.ALL | wx.EXPAND,
808            border=5)
809        mainSizer.Add(sizer, proportion=0,
810                      flag=wx.EXPAND | wx.ALL, border=5)
811
812        # buttons
813        self.btnOk = Button(self, wx.ID_OK)
814        self.btnCancel = Button(self, wx.ID_CANCEL)
815        self.btnOk.SetDefault()
816        self.btnOk.Bind(wx.EVT_BUTTON, self.OnOk)
817        # button sizer
818        btnStdSizer = wx.StdDialogButtonSizer()
819        btnStdSizer.AddButton(self.btnOk)
820        btnStdSizer.AddButton(self.btnCancel)
821        btnStdSizer.Realize()
822
823        mainSizer.Add(btnStdSizer, proportion=0,
824                      flag=wx.EXPAND | wx.ALL, border=5)
825
826        self.SetSizer(mainSizer)
827        mainSizer.Fit(self)
828
829    def _updateListBox(self):
830        self.listbox.Clear()
831        for anim in self.animationData:
832            self.listbox.Append(anim.name, clientData=anim)
833        if self.animationData:
834            self.listbox.SetSelection(0)
835
836    def _getNextIndex(self):
837        indices = [anim.windowIndex for anim in self.animationData]
838        for i in range(self.maxAnimations):
839            if i not in indices:
840                return i
841        return None
842
843    def OnAdd(self, event):
844        windowIndex = self._getNextIndex()
845        if windowIndex is None:
846            GMessage(
847                self,
848                message=_("Maximum number of animations is %d.") %
849                self.maxAnimations)
850            return
851        animData = AnimationData()
852        # number of active animations
853        animationIndex = len(self.animationData)
854        animData.SetDefaultValues(windowIndex, animationIndex)
855        dlg = InputDialog(parent=self, mode='add', animationData=animData)
856        dlg.CenterOnParent()
857        if dlg.ShowModal() == wx.ID_CANCEL:
858            dlg.UnInit()
859            dlg.Destroy()
860            return
861        dlg.Destroy()
862        self.animationData.append(animData)
863
864        self._updateListBox()
865
866    def OnEdit(self, event):
867        index = self.listbox.GetSelection()
868        if index == wx.NOT_FOUND:
869            return
870
871        animData = self.listbox.GetClientData(index)
872        dlg = InputDialog(parent=self, mode='edit', animationData=animData)
873        dlg.CenterOnParent()
874        if dlg.ShowModal() == wx.ID_CANCEL:
875            dlg.UnInit()
876            dlg.Destroy()
877            return
878        dlg.Destroy()
879
880        self._updateListBox()
881
882    def OnRemove(self, event):
883        index = self.listbox.GetSelection()
884        if index == wx.NOT_FOUND:
885            return
886
887        animData = self.listbox.GetClientData(index)
888        self.animationData.remove(animData)
889
890        self._updateListBox()
891
892    def GetResult(self):
893        return self.result
894
895    def OnOk(self, event):
896        indices = set([anim.windowIndex for anim in self.animationData])
897        if len(indices) != len(self.animationData):
898            GError(
899                parent=self, message=_(
900                    "More animations are using one window."
901                    " Please select different window for each animation."))
902            return
903        try:
904            temporalMode, tempManager = self.eval(self.animationData)
905        except GException as e:
906            GError(parent=self, message=e.value, showTraceback=False)
907            return
908        self.result = (self.animationData, temporalMode, tempManager)
909
910        self.EndModal(wx.ID_OK)
911
912
913class ExportDialog(wx.Dialog):
914
915    def __init__(self, parent, temporal, timeTick):
916        wx.Dialog.__init__(
917            self,
918            parent=parent,
919            id=wx.ID_ANY,
920            title=_("Export animation"),
921            style=wx.DEFAULT_DIALOG_STYLE)
922        self.decorations = []
923
924        self.temporal = temporal
925        self.timeTick = timeTick
926        self._layout()
927
928        # export animation
929        self.doExport = Signal('ExportDialog::doExport')
930
931        wx.CallAfter(self._hideAll)
932
933    def _layout(self):
934        notebook = wx.Notebook(self, id=wx.ID_ANY)
935        mainSizer = wx.BoxSizer(wx.VERTICAL)
936
937        notebook.AddPage(
938            page=self._createExportFormatPanel(notebook),
939            text=_("Format"))
940        notebook.AddPage(
941            page=self._createDecorationsPanel(notebook),
942            text=_("Decorations"))
943        mainSizer.Add(notebook, proportion=0,
944                      flag=wx.EXPAND | wx.ALL, border=5)
945
946        self.btnExport = Button(self, wx.ID_OK)
947        self.btnExport.SetLabel(_("Export"))
948        self.btnCancel = Button(self, wx.ID_CANCEL)
949        self.btnExport.SetDefault()
950
951        self.btnExport.Bind(wx.EVT_BUTTON, self.OnExport)
952
953        # button sizer
954        btnStdSizer = wx.StdDialogButtonSizer()
955        btnStdSizer.AddButton(self.btnExport)
956        btnStdSizer.AddButton(self.btnCancel)
957        btnStdSizer.Realize()
958
959        mainSizer.Add(btnStdSizer, proportion=0,
960                      flag=wx.EXPAND | wx.ALL, border=5)
961        self.SetSizer(mainSizer)
962
963        # set the longest option to fit
964        self.hidevbox.Show(self.fontBox, True)
965        self.hidevbox.Show(self.imageBox, False)
966        self.hidevbox.Show(self.textBox, True)
967        self.hidevbox.Show(self.posBox, True)
968        self.hidevbox.Show(self.informBox, False)
969        mainSizer.Fit(self)
970
971    def _createDecorationsPanel(self, notebook):
972        panel = wx.Panel(notebook, id=wx.ID_ANY)
973        sizer = wx.BoxSizer(wx.VERTICAL)
974        sizer.Add(
975            self._createDecorationsList(panel),
976            proportion=0,
977            flag=wx.ALL | wx.EXPAND,
978            border=10)
979        sizer.Add(
980            self._createDecorationsProperties(panel),
981            proportion=0,
982            flag=wx.ALL | wx.EXPAND,
983            border=10)
984        panel.SetSizer(sizer)
985        sizer.Fit(panel)
986        return panel
987
988    def _createDecorationsList(self, panel):
989        gridBagSizer = wx.GridBagSizer(hgap=5, vgap=5)
990
991        gridBagSizer.AddGrowableCol(0)
992
993        self.listbox = wx.ListBox(panel, id=wx.ID_ANY, choices=[],
994                                  style=wx.LB_SINGLE | wx.LB_NEEDED_SB)
995        self.listbox.Bind(wx.EVT_LISTBOX, self.OnSelectionChanged)
996
997        gridBagSizer.Add(self.listbox, pos=(0, 0), span=(4, 1),
998                         flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, border=0)
999
1000        buttonNames = ['time', 'image', 'text']
1001        buttonLabels = [_("Add time stamp"), _("Add image"), _("Add text")]
1002        i = 0
1003        for buttonName, buttonLabel in zip(buttonNames, buttonLabels):
1004            if buttonName == 'time' and self.temporal == TemporalMode.NONTEMPORAL:
1005                continue
1006            btn = Button(
1007                panel,
1008                id=wx.ID_ANY,
1009                name=buttonName,
1010                label=buttonLabel)
1011            btn.Bind(
1012                wx.EVT_BUTTON,
1013                lambda evt,
1014                temp=buttonName: self.OnAddDecoration(
1015                    evt,
1016                    temp))
1017            gridBagSizer.Add(
1018                btn,
1019                pos=(
1020                    i,
1021                    1),
1022                flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
1023                border=0)
1024            i += 1
1025        removeButton = Button(panel, id=wx.ID_ANY, label=_("Remove"))
1026        removeButton.Bind(wx.EVT_BUTTON, self.OnRemove)
1027        gridBagSizer.Add(
1028            removeButton,
1029            pos=(
1030                i,
1031                1),
1032            flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
1033            border=0)
1034
1035        return gridBagSizer
1036
1037    def _createDecorationsProperties(self, panel):
1038        self.hidevbox = wx.BoxSizer(wx.VERTICAL)
1039        # inform label
1040        self.informBox = wx.BoxSizer(wx.HORIZONTAL)
1041        if self.temporal == TemporalMode.TEMPORAL:
1042            label = _(
1043                "Add time stamp, image or text decoration by one of the buttons above.")
1044        else:
1045            label = _("Add image or text decoration by one of the buttons above.")
1046
1047        label = StaticText(panel, id=wx.ID_ANY, label=label)
1048        label.Wrap(400)
1049        self.informBox.Add(
1050            label,
1051            proportion=1,
1052            flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT,
1053            border=5)
1054        self.hidevbox.Add(
1055            self.informBox,
1056            proportion=0,
1057            flag=wx.EXPAND | wx.BOTTOM,
1058            border=5)
1059
1060        # font
1061        self.fontBox = wx.BoxSizer(wx.HORIZONTAL)
1062        self.fontBox.Add(
1063            StaticText(
1064                panel,
1065                id=wx.ID_ANY,
1066                label=_("Font settings:")),
1067            proportion=0,
1068            flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT,
1069            border=5)
1070        self.sampleLabel = StaticText(
1071            panel, id=wx.ID_ANY, label=_("Sample text"))
1072        self.fontBox.Add(self.sampleLabel, proportion=1,
1073                         flag=wx.ALIGN_CENTER | wx.RIGHT | wx.LEFT, border=5)
1074        fontButton = Button(panel, id=wx.ID_ANY, label=_("Set font"))
1075        fontButton.Bind(wx.EVT_BUTTON, self.OnFont)
1076        self.fontBox.Add(
1077            fontButton,
1078            proportion=0,
1079            flag=wx.ALIGN_CENTER_VERTICAL)
1080        self.hidevbox.Add(
1081            self.fontBox,
1082            proportion=0,
1083            flag=wx.EXPAND | wx.BOTTOM,
1084            border=5)
1085
1086        # image
1087        self.imageBox = wx.BoxSizer(wx.HORIZONTAL)
1088        filetype, ltype = GetImageHandlers(EmptyImage(10, 10))
1089        self.browse = filebrowse.FileBrowseButton(
1090            parent=panel, id=wx.ID_ANY, fileMask=filetype,
1091            labelText=_("Image file:"),
1092            dialogTitle=_('Choose image file'),
1093            buttonText=_('Browse'),
1094            startDirectory=os.getcwd(),
1095            fileMode=wx.FD_OPEN, changeCallback=self.OnSetImage)
1096        self.imageBox.Add(self.browse, proportion=1, flag=wx.EXPAND)
1097        self.hidevbox.Add(
1098            self.imageBox,
1099            proportion=0,
1100            flag=wx.EXPAND | wx.BOTTOM,
1101            border=5)
1102        # text
1103        self.textBox = wx.BoxSizer(wx.HORIZONTAL)
1104        self.textBox.Add(
1105            StaticText(
1106                panel,
1107                id=wx.ID_ANY,
1108                label=_("Text:")),
1109            proportion=0,
1110            flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT,
1111            border=5)
1112        self.textCtrl = TextCtrl(panel, id=wx.ID_ANY)
1113        self.textCtrl.Bind(wx.EVT_TEXT, self.OnText)
1114        self.textBox.Add(self.textCtrl, proportion=1, flag=wx.EXPAND)
1115        self.hidevbox.Add(self.textBox, proportion=0, flag=wx.EXPAND)
1116
1117        self.posBox = self._positionWidget(panel)
1118        self.hidevbox.Add(
1119            self.posBox,
1120            proportion=0,
1121            flag=wx.EXPAND | wx.TOP,
1122            border=5)
1123        return self.hidevbox
1124
1125    def _positionWidget(self, panel):
1126        grid = wx.GridBagSizer(vgap=5, hgap=5)
1127        label = StaticText(
1128            panel, id=wx.ID_ANY, label=_(
1129                "Placement as percentage of"
1130                " screen coordinates (X: 0, Y: 0 is top left):"))
1131        label.Wrap(400)
1132        self.spinX = SpinCtrl(
1133            panel, id=wx.ID_ANY, min=0, max=100, initial=10)
1134        self.spinY = SpinCtrl(
1135            panel, id=wx.ID_ANY, min=0, max=100, initial=10)
1136        self.spinX.Bind(
1137            wx.EVT_SPINCTRL,
1138            lambda evt,
1139            temp='X': self.OnPosition(
1140                evt,
1141                temp))
1142        self.spinY.Bind(
1143            wx.EVT_SPINCTRL,
1144            lambda evt,
1145            temp='Y': self.OnPosition(
1146                evt,
1147                temp))
1148
1149        grid.Add(label, pos=(0, 0), span=(1, 4), flag=wx.EXPAND)
1150        grid.Add(StaticText(panel, id=wx.ID_ANY, label=_("X:")), pos=(1, 0),
1151                 flag=wx.ALIGN_CENTER_VERTICAL)
1152        grid.Add(StaticText(panel, id=wx.ID_ANY, label=_("Y:")), pos=(1, 2),
1153                 flag=wx.ALIGN_CENTER_VERTICAL)
1154        grid.Add(self.spinX, pos=(1, 1))
1155        grid.Add(self.spinY, pos=(1, 3))
1156
1157        return grid
1158
1159    def _createExportFormatPanel(self, notebook):
1160        panel = wx.Panel(notebook, id=wx.ID_ANY)
1161        borderSizer = wx.BoxSizer(wx.VERTICAL)
1162
1163        hSizer = wx.BoxSizer(wx.HORIZONTAL)
1164        choices = [_("image sequence"), _("animated GIF"), _("SWF"), _("AVI")]
1165        self.formatChoice = wx.Choice(parent=panel, id=wx.ID_ANY,
1166                                      choices=choices)
1167        self.formatChoice.SetSelection(0)
1168        self.formatChoice.Bind(
1169            wx.EVT_CHOICE,
1170            lambda event: self.ChangeFormat(
1171                event.GetSelection()))
1172        hSizer.Add(
1173            StaticText(
1174                panel,
1175                id=wx.ID_ANY,
1176                label=_("Export to:")),
1177            proportion=0,
1178            flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL,
1179            border=2)
1180        hSizer.Add(
1181            self.formatChoice,
1182            proportion=1,
1183            flag=wx.EXPAND | wx.ALL,
1184            border=2)
1185        borderSizer.Add(
1186            hSizer,
1187            proportion=0,
1188            flag=wx.EXPAND | wx.ALL,
1189            border=3)
1190
1191        helpSizer = wx.BoxSizer(wx.HORIZONTAL)
1192        helpSizer.AddStretchSpacer(1)
1193        self.formatPanelSizer = wx.BoxSizer(wx.VERTICAL)
1194        helpSizer.Add(self.formatPanelSizer, proportion=5, flag=wx.EXPAND)
1195        borderSizer.Add(helpSizer, proportion=1, flag=wx.EXPAND)
1196        self.formatPanels = []
1197
1198        # panel for image sequence
1199        imSeqPanel = wx.Panel(parent=panel, id=wx.ID_ANY)
1200        prefixLabel = StaticText(
1201            imSeqPanel, id=wx.ID_ANY, label=_("File prefix:"))
1202        self.prefixCtrl = TextCtrl(
1203            imSeqPanel, id=wx.ID_ANY, value=_("animation_"))
1204        formatLabel = StaticText(
1205            imSeqPanel, id=wx.ID_ANY, label=_("File format:"))
1206        imageTypes = ['PNG', 'JPEG', 'GIF', 'TIFF', 'PPM', 'BMP']
1207        self.imSeqFormatChoice = wx.Choice(imSeqPanel, choices=imageTypes)
1208        self.imSeqFormatChoice.SetSelection(0)
1209        self.dirBrowse = filebrowse.DirBrowseButton(
1210            parent=imSeqPanel, id=wx.ID_ANY, labelText=_("Directory:"),
1211            dialogTitle=_("Choose directory for export"),
1212            buttonText=_("Browse"),
1213            startDirectory=os.getcwd())
1214
1215        dirGridSizer = wx.GridBagSizer(hgap=5, vgap=5)
1216        dirGridSizer.Add(
1217            prefixLabel, pos=(0, 0),
1218            flag=wx.ALIGN_CENTER_VERTICAL)
1219        dirGridSizer.Add(self.prefixCtrl, pos=(0, 1), flag=wx.EXPAND)
1220        dirGridSizer.Add(
1221            formatLabel, pos=(1, 0),
1222            flag=wx.ALIGN_CENTER_VERTICAL)
1223        dirGridSizer.Add(self.imSeqFormatChoice, pos=(1, 1), flag=wx.EXPAND)
1224        dirGridSizer.Add(
1225            self.dirBrowse, pos=(
1226                2, 0), flag=wx.EXPAND, span=(
1227                1, 2))
1228        dirGridSizer.AddGrowableCol(1)
1229        imSeqPanel.SetSizer(dirGridSizer)
1230        dirGridSizer.Fit(imSeqPanel)
1231
1232        self.formatPanelSizer.Add(
1233            imSeqPanel,
1234            proportion=1,
1235            flag=wx.EXPAND | wx.ALL,
1236            border=5)
1237        self.formatPanels.append(imSeqPanel)
1238
1239        # panel for gif
1240        gifPanel = wx.Panel(parent=panel, id=wx.ID_ANY)
1241
1242        self.gifBrowse = filebrowse.FileBrowseButton(
1243            parent=gifPanel,
1244            id=wx.ID_ANY,
1245            fileMask="GIF file (*.gif)|*.gif",
1246            labelText=_("GIF file:"),
1247            dialogTitle=_("Choose file to save animation"),
1248            buttonText=_("Browse"),
1249            startDirectory=os.getcwd(),
1250            fileMode=wx.FD_SAVE)
1251        gifGridSizer = wx.GridBagSizer(hgap=5, vgap=5)
1252        gifGridSizer.AddGrowableCol(0)
1253        gifGridSizer.Add(self.gifBrowse, pos=(0, 0), flag=wx.EXPAND)
1254        gifPanel.SetSizer(gifGridSizer)
1255        gifGridSizer.Fit(gifPanel)
1256
1257        self.formatPanelSizer.Add(
1258            gifPanel,
1259            proportion=1,
1260            flag=wx.EXPAND | wx.ALL,
1261            border=5)
1262        self.formatPanels.append(gifPanel)
1263
1264        # panel for swf
1265        swfPanel = wx.Panel(parent=panel, id=wx.ID_ANY)
1266        self.swfBrowse = filebrowse.FileBrowseButton(
1267            parent=swfPanel,
1268            id=wx.ID_ANY,
1269            fileMask="SWF file (*.swf)|*.swf",
1270            labelText=_("SWF file:"),
1271            dialogTitle=_("Choose file to save animation"),
1272            buttonText=_("Browse"),
1273            startDirectory=os.getcwd(),
1274            fileMode=wx.FD_SAVE)
1275        swfGridSizer = wx.GridBagSizer(hgap=5, vgap=5)
1276        swfGridSizer.AddGrowableCol(0)
1277        swfGridSizer.Add(self.swfBrowse, pos=(0, 0), flag=wx.EXPAND)
1278        swfPanel.SetSizer(swfGridSizer)
1279        swfGridSizer.Fit(swfPanel)
1280
1281        self.formatPanelSizer.Add(
1282            swfPanel,
1283            proportion=1,
1284            flag=wx.EXPAND | wx.ALL,
1285            border=5)
1286        self.formatPanels.append(swfPanel)
1287
1288        # panel for avi
1289        aviPanel = wx.Panel(parent=panel, id=wx.ID_ANY)
1290        ffmpeg = gcore.find_program('ffmpeg', '--help')
1291        if not ffmpeg:
1292            warning = _(
1293                "Program 'ffmpeg' was not found.\nPlease install it first "
1294                "and make sure\nit's in the PATH variable.")
1295            warningLabel = StaticText(parent=aviPanel, label=warning)
1296            warningLabel.SetForegroundColour(wx.RED)
1297        self.aviBrowse = filebrowse.FileBrowseButton(
1298            parent=aviPanel,
1299            id=wx.ID_ANY,
1300            fileMask="AVI file (*.avi)|*.avi",
1301            labelText=_("AVI file:"),
1302            dialogTitle=_("Choose file to save animation"),
1303            buttonText=_("Browse"),
1304            startDirectory=os.getcwd(),
1305            fileMode=wx.FD_SAVE)
1306        encodingLabel = StaticText(
1307            parent=aviPanel,
1308            id=wx.ID_ANY,
1309            label=_("Video codec:"))
1310        self.encodingText = TextCtrl(
1311            parent=aviPanel, id=wx.ID_ANY, value='mpeg4')
1312        optionsLabel = StaticText(
1313            parent=aviPanel, label=_("Additional options:"))
1314        self.optionsText = TextCtrl(parent=aviPanel)
1315        self.optionsText.SetToolTip(
1316            _(
1317                "Consider adding '-sameq' or '-qscale 1' "
1318                "if not satisfied with video quality. "
1319                "Options depend on ffmpeg version."))
1320        aviGridSizer = wx.GridBagSizer(hgap=5, vgap=5)
1321        aviGridSizer.Add(
1322            self.aviBrowse, pos=(
1323                0, 0), span=(
1324                1, 2), flag=wx.EXPAND)
1325        aviGridSizer.Add(
1326            encodingLabel, pos=(1, 0),
1327            flag=wx.ALIGN_CENTER_VERTICAL)
1328        aviGridSizer.Add(self.encodingText, pos=(1, 1), flag=wx.EXPAND)
1329        aviGridSizer.Add(
1330            optionsLabel, pos=(2, 0),
1331            flag=wx.ALIGN_CENTER_VERTICAL)
1332        aviGridSizer.Add(self.optionsText, pos=(2, 1), flag=wx.EXPAND)
1333        if not ffmpeg:
1334            aviGridSizer.Add(warningLabel, pos=(3, 0), span=(1, 2),
1335                             flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
1336
1337        aviGridSizer.AddGrowableCol(1)
1338        aviPanel.SetSizer(aviGridSizer)
1339        aviGridSizer.Fit(aviPanel)
1340
1341        self.formatPanelSizer.Add(
1342            aviPanel,
1343            proportion=1,
1344            flag=wx.EXPAND | wx.ALL,
1345            border=5)
1346        self.formatPanels.append(aviPanel)
1347
1348        fpsSizer = wx.BoxSizer(wx.HORIZONTAL)
1349        fps = 1000 / self.timeTick
1350        fpsSizer.Add(
1351            StaticText(
1352                panel,
1353                id=wx.ID_ANY,
1354                label=_("Current frame rate: %.2f fps") %
1355                fps),
1356            proportion=1,
1357            flag=wx.EXPAND)
1358        borderSizer.Add(
1359            fpsSizer,
1360            proportion=0,
1361            flag=wx.ALL,
1362            border=5)
1363
1364        panel.SetSizer(borderSizer)
1365        borderSizer.Fit(panel)
1366        self.ChangeFormat(index=0)
1367
1368        return panel
1369
1370    def ChangeFormat(self, index):
1371        for i, panel in enumerate(self.formatPanels):
1372            self.formatPanelSizer.Show(window=panel, show=(i == index))
1373        self.formatPanelSizer.Layout()
1374
1375    def OnFont(self, event):
1376        index = self.listbox.GetSelection()
1377        # should not happen
1378        if index == wx.NOT_FOUND:
1379            return
1380        cdata = self.listbox.GetClientData(index)
1381        font = cdata['font']
1382
1383        fontdata = wx.FontData()
1384        fontdata.EnableEffects(True)
1385        fontdata.SetColour('black')
1386        fontdata.SetInitialFont(font)
1387
1388        dlg = wx.FontDialog(self, fontdata)
1389        dlg.CenterOnParent()
1390        if dlg.ShowModal() == wx.ID_OK:
1391            newfontdata = dlg.GetFontData()
1392            font = newfontdata.GetChosenFont()
1393            self.sampleLabel.SetFont(font)
1394            cdata['font'] = font
1395            self.Layout()
1396
1397    def OnPosition(self, event, coord):
1398        index = self.listbox.GetSelection()
1399        # should not happen
1400        if index == wx.NOT_FOUND:
1401            return
1402        cdata = self.listbox.GetClientData(index)
1403        cdata['pos'][coord == 'Y'] = event.GetInt()
1404
1405    def OnSetImage(self, event):
1406        index = self.listbox.GetSelection()
1407        # should not happen
1408        if index == wx.NOT_FOUND:
1409            return
1410        cdata = self.listbox.GetClientData(index)
1411        cdata['file'] = event.GetString()
1412
1413    def OnAddDecoration(self, event, name):
1414        if name == 'time':
1415            timeInfo = {'name': name, 'font': self.GetFont(), 'pos': [10, 10]}
1416            self.decorations.append(timeInfo)
1417        elif name == 'image':
1418            imageInfo = {'name': name, 'file': '', 'pos': [10, 10]}
1419            self.decorations.append(imageInfo)
1420        elif name == 'text':
1421            textInfo = {
1422                'name': name,
1423                'font': self.GetFont(),
1424                'text': '',
1425                'pos': [
1426                    10,
1427                    10]}
1428            self.decorations.append(textInfo)
1429
1430        self._updateListBox()
1431        self.listbox.SetSelection(self.listbox.GetCount() - 1)
1432        self.OnSelectionChanged(event=None)
1433
1434    def OnSelectionChanged(self, event):
1435        index = self.listbox.GetSelection()
1436        if index == wx.NOT_FOUND:
1437            self._hideAll()
1438            return
1439        cdata = self.listbox.GetClientData(index)
1440        self.hidevbox.Show(self.fontBox, (cdata['name'] in ('time', 'text')))
1441        self.hidevbox.Show(self.imageBox, (cdata['name'] == 'image'))
1442        self.hidevbox.Show(self.textBox, (cdata['name'] == 'text'))
1443        self.hidevbox.Show(self.posBox, True)
1444        self.hidevbox.Show(self.informBox, False)
1445
1446        self.spinX.SetValue(cdata['pos'][0])
1447        self.spinY.SetValue(cdata['pos'][1])
1448        if cdata['name'] == 'image':
1449            self.browse.SetValue(cdata['file'])
1450        elif cdata['name'] in ('time', 'text'):
1451            self.sampleLabel.SetFont(cdata['font'])
1452            if cdata['name'] == 'text':
1453                self.textCtrl.SetValue(cdata['text'])
1454
1455        self.hidevbox.Layout()
1456        # self.Layout()
1457
1458    def OnText(self, event):
1459        index = self.listbox.GetSelection()
1460        # should not happen
1461        if index == wx.NOT_FOUND:
1462            return
1463        cdata = self.listbox.GetClientData(index)
1464        cdata['text'] = event.GetString()
1465
1466    def OnRemove(self, event):
1467        index = self.listbox.GetSelection()
1468        if index == wx.NOT_FOUND:
1469            return
1470
1471        decData = self.listbox.GetClientData(index)
1472        self.decorations.remove(decData)
1473
1474        self._updateListBox()
1475        if self.listbox.GetCount():
1476            self.listbox.SetSelection(0)
1477            self.OnSelectionChanged(event=None)
1478
1479    def OnExport(self, event):
1480        for decor in self.decorations:
1481            if decor['name'] == 'image':
1482                if not os.path.exists(decor['file']):
1483                    if decor['file']:
1484                        GError(
1485                            parent=self,
1486                            message=_("File %s not found.") %
1487                            decor['file'])
1488                    else:
1489                        GError(parent=self,
1490                               message=_("Decoration image file is missing."))
1491                    return
1492
1493        if self.formatChoice.GetSelection() == 0:
1494            name = self.dirBrowse.GetValue()
1495            if not os.path.exists(name):
1496                if name:
1497                    GError(
1498                        parent=self,
1499                        message=_("Directory %s not found.") %
1500                        name)
1501                else:
1502                    GError(parent=self, message=_(
1503                        "Export directory is missing."))
1504                return
1505        elif self.formatChoice.GetSelection() == 1:
1506            if not self._export_file_validation(
1507                    filebrowsebtn=self.gifBrowse,
1508                    file_path=self.gifBrowse.GetValue(),
1509                    file_postfix='.gif',
1510            ):
1511                return
1512        elif self.formatChoice.GetSelection() == 2:
1513            if not self._export_file_validation(
1514                    filebrowsebtn=self.swfBrowse,
1515                    file_path=self.swfBrowse.GetValue(),
1516                    file_postfix='.swf',
1517            ):
1518                return
1519        elif self.formatChoice.GetSelection() == 3:
1520            if not self._export_file_validation(
1521                    filebrowsebtn=self.aviBrowse,
1522                    file_path=self.aviBrowse.GetValue(),
1523                    file_postfix='.avi',
1524            ):
1525                return
1526
1527        # hide only to keep previous values
1528        self.Hide()
1529        self.doExport.emit(exportInfo=self.GetExportInformation(),
1530                           decorations=self.GetDecorations())
1531
1532    def GetDecorations(self):
1533        return self.decorations
1534
1535    def GetExportInformation(self):
1536        info = {}
1537        if self.formatChoice.GetSelection() == 0:
1538            info['method'] = 'sequence'
1539            info['directory'] = self.dirBrowse.GetValue()
1540            info['prefix'] = self.prefixCtrl.GetValue()
1541            info['format'] = self.imSeqFormatChoice.GetStringSelection()
1542
1543        elif self.formatChoice.GetSelection() == 1:
1544            info['method'] = 'gif'
1545            info['file'] = self.gifBrowse.GetValue()
1546
1547        elif self.formatChoice.GetSelection() == 2:
1548            info['method'] = 'swf'
1549            info['file'] = self.swfBrowse.GetValue()
1550
1551        elif self.formatChoice.GetSelection() == 3:
1552            info['method'] = 'avi'
1553            info['file'] = self.aviBrowse.GetValue()
1554            info['encoding'] = self.encodingText.GetValue()
1555            info['options'] = self.optionsText.GetValue()
1556
1557        return info
1558
1559    def _updateListBox(self):
1560        self.listbox.Clear()
1561        names = {
1562            'time': _("Time stamp"),
1563            'image': _("Image"),
1564            'text': _("Text")}
1565        for decor in self.decorations:
1566            self.listbox.Append(names[decor['name']], clientData=decor)
1567
1568    def _hideAll(self):
1569        self.hidevbox.Show(self.fontBox, False)
1570        self.hidevbox.Show(self.imageBox, False)
1571        self.hidevbox.Show(self.textBox, False)
1572        self.hidevbox.Show(self.posBox, False)
1573        self.hidevbox.Show(self.informBox, True)
1574        self.hidevbox.Layout()
1575
1576    def _export_file_validation(self, filebrowsebtn, file_path,
1577                                file_postfix):
1578        """File validation before export
1579
1580        :param obj filebrowsebutton: filebrowsebutton widget
1581        :param str file_path: exported file path
1582        :param str file_postfix: exported file postfix
1583        (.gif, .swf, .avi)
1584
1585        :return bool: True if validation is ok
1586        """
1587
1588        file_path_does_not_exist_err_message = _(
1589            "Exported file directory '{base_dir}' "
1590            "does not exist."
1591        )
1592        if not file_path:
1593            GError(parent=self, message=_("Export file is missing."))
1594            return False
1595        else:
1596            if not file_path.endswith(file_postfix):
1597                filebrowsebtn.SetValue(file_path + file_postfix)
1598                file_path += file_postfix
1599
1600            base_dir = os.path.dirname(file_path)
1601            if not os.path.exists(base_dir):
1602                GError(
1603                    parent=self,
1604                    message=file_path_does_not_exist_err_message.format(
1605                        base_dir=base_dir,
1606                    ),
1607                )
1608                return False
1609
1610            if os.path.exists(file_path):
1611                overwrite_dlg = wx.MessageDialog(
1612                    self.GetParent(),
1613                    message=_(
1614                        "Exported animation file <{file}> exists. "
1615                        "Do you want to overwrite it?".format(
1616                            file=file_path,
1617                        ),
1618                    ),
1619                    caption=_("Overwrite?"),
1620                    style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION,
1621                )
1622                if not overwrite_dlg.ShowModal() == wx.ID_YES:
1623                    overwrite_dlg.Destroy()
1624                    return False
1625                overwrite_dlg.Destroy()
1626
1627        return True
1628
1629
1630class AnimSimpleLayerManager(SimpleLayerManager):
1631    """Simple layer manager for animation tool.
1632    Allows adding space-time dataset or series of maps.
1633    """
1634
1635    def __init__(self, parent, layerList,
1636                 lmgrStyle=SIMPLE_LMGR_RASTER | SIMPLE_LMGR_VECTOR |
1637                 SIMPLE_LMGR_TB_TOP | SIMPLE_LMGR_STDS,
1638                 toolbarCls=AnimSimpleLmgrToolbar, modal=True):
1639        SimpleLayerManager.__init__(
1640            self, parent, layerList, lmgrStyle, toolbarCls, modal)
1641        self._3dActivated = False
1642
1643    def OnAddStds(self, event):
1644        """Opens dialog for specifying temporal dataset.
1645        Dummy layer is added first."""
1646        layer = AnimLayer()
1647        layer.hidden = True
1648        self._layerList.AddLayer(layer)
1649        self.SetStdsProperties(layer)
1650        event.Skip()
1651
1652    def SetStdsProperties(self, layer):
1653        dlg = AddTemporalLayerDialog(
1654            parent=self, layer=layer, volume=self._3dActivated)
1655        # first get hidden property, it's altered afterwards
1656        hidden = layer.hidden
1657        dlg.CenterOnParent()
1658        if dlg.ShowModal() == wx.ID_OK:
1659            layer = dlg.GetLayer()
1660            if hidden:
1661                signal = self.layerAdded
1662            else:
1663                signal = self.cmdChanged
1664            signal.emit(
1665                index=self._layerList.GetLayerIndex(layer),
1666                layer=layer)
1667        else:
1668            if hidden:
1669                self._layerList.RemoveLayer(layer)
1670        dlg.Destroy()
1671        self._update()
1672        self.anyChange.emit()
1673
1674    def _layerChangeProperties(self, layer):
1675        """Opens new module dialog or recycles it."""
1676        if not hasattr(layer, 'maps'):
1677            GUI(parent=self, giface=None, modal=self._modal).ParseCommand(
1678                cmd=layer.cmd, completed=(self.GetOptData, layer, ''))
1679        else:
1680            self.SetStdsProperties(layer)
1681
1682    def Activate3D(self, activate=True):
1683        """Activates/deactivates certain tool depending on 2D/3D view."""
1684        self._toolbar.EnableTools(['addRaster', 'addVector',
1685                                   'opacity', 'up', 'down'], not activate)
1686        self._3dActivated = activate
1687
1688
1689class AddTemporalLayerDialog(wx.Dialog):
1690    """Dialog for adding space-time dataset/ map series."""
1691
1692    def __init__(self, parent, layer, volume=False,
1693                 title=_("Add space-time dataset layer")):
1694        wx.Dialog.__init__(self, parent=parent, title=title)
1695
1696        self.layer = layer
1697        self._mapType = None
1698        self._name = None
1699        self._cmd = None
1700
1701        self.tselect = Select(parent=self, type='strds')
1702        iconTheme = UserSettings.Get(
1703            group='appearance',
1704            key='iconTheme',
1705            subkey='type')
1706        bitmapPath = os.path.join(
1707            globalvar.ICONDIR,
1708            iconTheme,
1709            'layer-open.png')
1710        if os.path.isfile(bitmapPath) and os.path.getsize(bitmapPath):
1711            bitmap = wx.Bitmap(name=bitmapPath)
1712        else:
1713            bitmap = wx.ArtProvider.GetBitmap(
1714                id=wx.ART_MISSING_IMAGE, client=wx.ART_TOOLBAR)
1715        self.addManyMapsButton = BitmapButton(self, bitmap=bitmap)
1716        self.addManyMapsButton.Bind(wx.EVT_BUTTON, self._onAddMaps)
1717
1718        types = [('raster', _("Multiple raster maps")),
1719                 ('vector', _("Multiple vector maps")),
1720                 ('raster_3d', _("Multiple 3D raster maps")),
1721                 ('strds', _("Space time raster dataset")),
1722                 ('stvds', _("Space time vector dataset")),
1723                 ('str3ds', _("Space time 3D raster dataset"))]
1724        if not volume:
1725            del types[5]
1726            del types[2]
1727        self._types = dict(types)
1728
1729        self.tchoice = wx.Choice(parent=self)
1730        for type_, text in types:
1731            self.tchoice.Append(text, clientData=type_)
1732
1733        self.editBtn = Button(parent=self, label='Set properties')
1734
1735        self.okBtn = Button(parent=self, id=wx.ID_OK)
1736        self.cancelBtn = Button(parent=self, id=wx.ID_CANCEL)
1737
1738        self.okBtn.Bind(wx.EVT_BUTTON, self._onOK)
1739        self.editBtn.Bind(wx.EVT_BUTTON, self._onProperties)
1740        self.tchoice.Bind(wx.EVT_CHOICE,
1741                          lambda evt: self._setType())
1742        self.tselect.Bind(wx.EVT_TEXT,
1743                          lambda evt: self._datasetChanged())
1744
1745        if self.layer.mapType:
1746            self._setType(self.layer.mapType)
1747        else:
1748            self._setType('raster')
1749        if self.layer.name:
1750            self.tselect.SetValue(self.layer.name)
1751        if self.layer.cmd:
1752            self._cmd = self.layer.cmd
1753
1754        self._layout()
1755        self.SetSize(self.GetBestSize())
1756
1757    def _layout(self):
1758        mainSizer = wx.BoxSizer(wx.VERTICAL)
1759        bodySizer = wx.BoxSizer(wx.VERTICAL)
1760        typeSizer = wx.BoxSizer(wx.HORIZONTAL)
1761        selectSizer = wx.BoxSizer(wx.HORIZONTAL)
1762        typeSizer.Add(StaticText(self, label=_("Input data type:")),
1763                      flag=wx.ALIGN_CENTER_VERTICAL)
1764        typeSizer.AddStretchSpacer()
1765        typeSizer.Add(self.tchoice)
1766        bodySizer.Add(typeSizer, flag=wx.EXPAND | wx.BOTTOM, border=5)
1767
1768        selectSizer.Add(self.tselect, flag=wx.RIGHT |
1769                        wx.ALIGN_CENTER_VERTICAL, border=5)
1770        selectSizer.Add(self.addManyMapsButton, flag=wx.EXPAND)
1771        bodySizer.Add(selectSizer, flag=wx.BOTTOM, border=5)
1772        bodySizer.Add(self.editBtn, flag=wx.BOTTOM, border=5)
1773        mainSizer.Add(
1774            bodySizer,
1775            proportion=1,
1776            flag=wx.EXPAND | wx.ALL,
1777            border=10)
1778
1779        btnSizer = wx.StdDialogButtonSizer()
1780        btnSizer.AddButton(self.okBtn)
1781        btnSizer.AddButton(self.cancelBtn)
1782        btnSizer.Realize()
1783
1784        mainSizer.Add(btnSizer, proportion=0,
1785                      flag=wx.EXPAND | wx.ALL, border=10)
1786
1787        self.SetSizer(mainSizer)
1788        mainSizer.Fit(self)
1789
1790    def _datasetChanged(self):
1791        if self._name != self.tselect.GetValue():
1792            self._name = self.tselect.GetValue()
1793            self._cmd = None
1794
1795    def _setType(self, typeName=None):
1796        if typeName:
1797            self.tchoice.SetStringSelection(self._types[typeName])
1798            self.tselect.SetType(typeName)
1799            if typeName in ('strds', 'stvds', 'str3ds'):
1800                self.tselect.SetType(typeName, multiple=False)
1801                self.addManyMapsButton.Disable()
1802            else:
1803                self.tselect.SetType(typeName, multiple=True)
1804                self.addManyMapsButton.Enable()
1805            self._mapType = typeName
1806            self.tselect.SetValue('')
1807        else:
1808            typeName = self.tchoice.GetClientData(self.tchoice.GetSelection())
1809            if typeName in ('strds', 'stvds', 'str3ds'):
1810                self.tselect.SetType(typeName, multiple=False)
1811                self.addManyMapsButton.Disable()
1812            else:
1813                self.tselect.SetType(typeName, multiple=True)
1814                self.addManyMapsButton.Enable()
1815            if typeName != self._mapType:
1816                self._cmd = None
1817                self._mapType = typeName
1818                self.tselect.SetValue('')
1819
1820    def _createDefaultCommand(self):
1821        cmd = []
1822        if self._mapType in ('raster', 'strds'):
1823            cmd.append('d.rast')
1824        elif self._mapType in ('vector', 'stvds'):
1825            cmd.append('d.vect')
1826        elif self._mapType in ('raster_3d', 'str3ds'):
1827            cmd.append('d.rast3d')
1828        if self._name:
1829            if self._mapType in ('raster', 'vector', 'raster_3d'):
1830                cmd.append('map={name}'.format(name=self._name.split(',')[0]))
1831            else:
1832                try:
1833                    maps = getRegisteredMaps(self._name, etype=self._mapType)
1834                    if maps:
1835                        mapName, mapLayer = getNameAndLayer(maps[0])
1836                        cmd.append('map={name}'.format(name=mapName))
1837                except gcore.ScriptError as e:
1838                    GError(parent=self, message=str(e), showTraceback=False)
1839                    return None
1840        return cmd
1841
1842    def _onAddMaps(self, event):
1843        dlg = MapLayersDialog(self, title=_("Select raster/vector maps."))
1844        dlg.applyAddingMapLayers.connect(
1845            lambda mapLayers: self.tselect.SetValue(
1846                ','.join(mapLayers)))
1847        if self._mapType == 'raster':
1848            index = 0
1849        elif self._mapType == 'vector':
1850            index = 2
1851        else:  # rast3d
1852            index = 1
1853
1854        dlg.layerType.SetSelection(index)
1855        dlg.LoadMapLayers(dlg.GetLayerType(cmd=True),
1856                          dlg.mapset.GetStringSelection())
1857        dlg.CenterOnParent()
1858        if dlg.ShowModal() == wx.ID_OK:
1859            self.tselect.SetValue(','.join(dlg.GetMapLayers()))
1860
1861        dlg.Destroy()
1862
1863    def _onProperties(self, event):
1864        self._checkInput()
1865        if self._cmd:
1866            GUI(parent=self, show=True, modal=True).ParseCommand(
1867                cmd=self._cmd, completed=(self._getOptData, '', ''))
1868
1869    def _checkInput(self):
1870        if not self.tselect.GetValue():
1871            GMessage(parent=self, message=_(
1872                "Please select maps or dataset first."))
1873            return
1874
1875        if not self._cmd:
1876            self._cmd = self._createDefaultCommand()
1877
1878    def _getOptData(self, dcmd, layer, params, propwin):
1879        if dcmd:
1880            self._cmd = dcmd
1881
1882    def _onOK(self, event):
1883        self._checkInput()
1884        if self._cmd:
1885            try:
1886                self.layer.hidden = False
1887                self.layer.mapType = self._mapType
1888                self.layer.name = self._name
1889                self.layer.cmd = self._cmd
1890                event.Skip()
1891            except (GException, gcore.ScriptError) as e:
1892                GError(parent=self, message=str(e))
1893
1894    def GetLayer(self):
1895        return self.layer
1896
1897
1898class PreferencesDialog(PreferencesBaseDialog):
1899    """Animation preferences dialog"""
1900
1901    def __init__(self, parent, giface, title=_("Animation Tool settings"),
1902                 settings=UserSettings):
1903        PreferencesBaseDialog.__init__(
1904            self, parent=parent, giface=giface, title=title, settings=settings,
1905            size=(-1, 270))
1906        self.formatChanged = Signal('PreferencesDialog.formatChanged')
1907
1908        self._timeFormats = ['%Y-%m-%d %H:%M:%S',  # 2013-12-29 11:16:26
1909                             '%Y-%m-%d',  # 2013-12-29
1910                             '%c',
1911                             # Sun Dec 29 11:16:26 2013 (locale-dependent)
1912                             '%x',  # 12/29/13 (locale-dependent)
1913                             '%X',  # 11:16:26 (locale-dependent)
1914                             '%b %d, %Y',  # Dec 29, 2013
1915                             '%B %d, %Y',  # December 29, 2013
1916                             '%B, %Y',  # December 2013
1917                             '%I:%M %p',  # 11:16 AM
1918                             '%I %p',  # 11 AM
1919                             ]
1920        self._format = None
1921        self._initFormat = self.settings.Get(group='animation', key='temporal',
1922                                             subkey='format')
1923        # create notebook pages
1924        self._createGeneralPage(self.notebook)
1925        self._createTemporalPage(self.notebook)
1926
1927        self.SetMinSize(self.GetBestSize())
1928        self.SetSize(self.size)
1929
1930    def _createGeneralPage(self, notebook):
1931        """Create notebook page for general settings"""
1932        panel = SP.ScrolledPanel(parent=notebook)
1933        panel.SetupScrolling(scroll_x=False, scroll_y=True)
1934        notebook.AddPage(page=panel, text=_("General"))
1935
1936        border = wx.BoxSizer(wx.VERTICAL)
1937        sizer = wx.BoxSizer(wx.VERTICAL)
1938        gridSizer = wx.GridBagSizer(hgap=3, vgap=3)
1939
1940        row = 0
1941        gridSizer.Add(
1942            StaticText(
1943                parent=panel,
1944                label=_("Background color:")),
1945            flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL,
1946            pos=(
1947                row,
1948                0))
1949        color = csel.ColourSelect(
1950            parent=panel,
1951            colour=UserSettings.Get(
1952                group='animation',
1953                key='bgcolor',
1954                subkey='color'),
1955            size=globalvar.DIALOG_COLOR_SIZE)
1956        color.SetName('GetColour')
1957        self.winId['animation:bgcolor:color'] = color.GetId()
1958
1959        gridSizer.Add(color, pos=(row, 1), flag=wx.ALIGN_RIGHT)
1960
1961        row += 1
1962        gridSizer.Add(
1963            StaticText(
1964                parent=panel,
1965                label=_("Number of parallel processes:")),
1966            flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL,
1967            pos=(
1968                row,
1969                0))
1970        # when running for the first time, set nprocs based on the number of
1971        # processes
1972        if UserSettings.Get(group='animation', key='nprocs',
1973                            subkey='value') == -1:
1974            UserSettings.Set(
1975                group='animation',
1976                key='nprocs',
1977                subkey='value',
1978                value=getCpuCount())
1979        nprocs = SpinCtrl(
1980            parent=panel,
1981            initial=UserSettings.Get(
1982                group='animation',
1983                key='nprocs',
1984                subkey='value'))
1985        nprocs.SetName('GetValue')
1986        self.winId['animation:nprocs:value'] = nprocs.GetId()
1987
1988        gridSizer.Add(nprocs, pos=(row, 1), flag=wx.ALIGN_RIGHT)
1989
1990        row += 1
1991        gridSizer.Add(
1992            StaticText(
1993                parent=panel,
1994                label=_("Text foreground color:")),
1995            flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL,
1996            pos=(
1997                row,
1998                0))
1999        color = csel.ColourSelect(
2000            parent=panel,
2001            colour=UserSettings.Get(
2002                group='animation',
2003                key='font',
2004                subkey='fgcolor'),
2005            size=globalvar.DIALOG_COLOR_SIZE)
2006        color.SetName('GetColour')
2007        self.winId['animation:font:fgcolor'] = color.GetId()
2008
2009        gridSizer.Add(color, pos=(row, 1), flag=wx.ALIGN_RIGHT)
2010
2011        row += 1
2012        gridSizer.Add(
2013            StaticText(
2014                parent=panel,
2015                label=_("Text background color:")),
2016            flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL,
2017            pos=(
2018                row,
2019                0))
2020        color = csel.ColourSelect(
2021            parent=panel,
2022            colour=UserSettings.Get(
2023                group='animation',
2024                key='font',
2025                subkey='bgcolor'),
2026            size=globalvar.DIALOG_COLOR_SIZE)
2027        color.SetName('GetColour')
2028        self.winId['animation:font:bgcolor'] = color.GetId()
2029
2030        gridSizer.Add(color, pos=(row, 1), flag=wx.ALIGN_RIGHT)
2031
2032        gridSizer.AddGrowableCol(1)
2033        sizer.Add(
2034            gridSizer,
2035            proportion=1,
2036            flag=wx.ALL | wx.EXPAND,
2037            border=3)
2038        border.Add(sizer, proportion=0, flag=wx.ALL | wx.EXPAND, border=3)
2039        panel.SetSizer(border)
2040
2041        return panel
2042
2043    def _createTemporalPage(self, notebook):
2044        """Create notebook page for temporal settings"""
2045        panel = SP.ScrolledPanel(parent=notebook)
2046        panel.SetupScrolling(scroll_x=False, scroll_y=True)
2047        notebook.AddPage(page=panel, text=_("Time"))
2048
2049        border = wx.BoxSizer(wx.VERTICAL)
2050        sizer = wx.BoxSizer(wx.VERTICAL)
2051        gridSizer = wx.GridBagSizer(hgap=5, vgap=5)
2052
2053        row = 0
2054        gridSizer.Add(
2055            StaticText(
2056                parent=panel,
2057                label=_("Absolute time format:")),
2058            flag=wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL,
2059            pos=(
2060                row,
2061                0))
2062        self.tempFormat = ComboBox(parent=panel, name='GetValue')
2063        self.tempFormat.SetItems(self._timeFormats)
2064        self.tempFormat.SetValue(self._initFormat)
2065        self.winId['animation:temporal:format'] = self.tempFormat.GetId()
2066        gridSizer.Add(self.tempFormat, pos=(row, 1), flag=wx.ALIGN_RIGHT)
2067        self.infoTimeLabel = StaticText(parent=panel)
2068        self.tempFormat.Bind(
2069            wx.EVT_COMBOBOX,
2070            lambda evt: self._setTimeFormat(
2071                self.tempFormat.GetValue()))
2072        self.tempFormat.Bind(
2073            wx.EVT_TEXT, lambda evt: self._setTimeFormat(
2074                self.tempFormat.GetValue()))
2075        self.tempFormat.SetToolTip(
2076            _(
2077                "Click and then press key up or down to preview "
2078                "different date and time formats. "
2079                "Type custom format string."))
2080        row += 1
2081        gridSizer.Add(self.infoTimeLabel, pos=(row, 0), span=(1, 2),
2082                      flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
2083        self._setTimeFormat(self.tempFormat.GetValue())
2084
2085        row += 1
2086        link = HyperlinkCtrl(
2087            panel, id=wx.ID_ANY,
2088            label=_("Learn more about formatting options"),
2089            url="http://docs.python.org/2/library/datetime.html#"
2090            "strftime-and-strptime-behavior")
2091        link.SetNormalColour(
2092            wx.SystemSettings.GetColour(
2093                wx.SYS_COLOUR_GRAYTEXT))
2094        link.SetVisitedColour(
2095            wx.SystemSettings.GetColour(
2096                wx.SYS_COLOUR_GRAYTEXT))
2097        gridSizer.Add(link, pos=(row, 0), span=(1, 2),
2098                      flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
2099
2100        row += 2
2101        noDataCheck = CheckBox(
2102            panel, label=_("Display instances with no data"))
2103        noDataCheck.SetToolTip(
2104            _(
2105                "When animating instant-based data which have irregular timestamps "
2106                "you can display 'no data frame' (checked option) or "
2107                "keep last frame."))
2108        noDataCheck.SetValue(
2109            self.settings.Get(
2110                group='animation',
2111                key='temporal',
2112                subkey=[
2113                    'nodata',
2114                    'enable']))
2115        self.winId['animation:temporal:nodata:enable'] = noDataCheck.GetId()
2116        gridSizer.Add(noDataCheck, pos=(row, 0), span=(1, 2),
2117                      flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT)
2118
2119        gridSizer.AddGrowableCol(1)
2120        sizer.Add(
2121            gridSizer,
2122            proportion=1,
2123            flag=wx.ALL | wx.EXPAND,
2124            border=3)
2125        border.Add(sizer, proportion=0, flag=wx.ALL | wx.EXPAND, border=3)
2126        panel.SetSizer(border)
2127
2128        return panel
2129
2130    def _setTimeFormat(self, formatString):
2131        now = datetime.datetime.now()
2132        try:
2133            label = datetime.datetime.strftime(now, formatString)
2134            self._format = formatString
2135        except ValueError:
2136            label = _("Invalid")
2137        self.infoTimeLabel.SetLabel(label)
2138        self.infoTimeLabel.GetContainingSizer().Layout()
2139
2140    def _updateSettings(self):
2141        self.tempFormat.SetValue(self._format)
2142        PreferencesBaseDialog._updateSettings(self)
2143        if self._format != self._initFormat:
2144            self.formatChanged.emit()
2145        return True
2146
2147
2148def test():
2149    import wx.lib.inspection
2150
2151    app = wx.App()
2152
2153#    testTemporalLayer()
2154#    testAnimLmgr()
2155    testAnimInput()
2156    # wx.lib.inspection.InspectionTool().Show()
2157
2158    app.MainLoop()
2159
2160
2161def testAnimInput():
2162    anim = AnimationData()
2163    anim.SetDefaultValues(animationIndex=0, windowIndex=0)
2164
2165    dlg = InputDialog(parent=None, mode='add', animationData=anim)
2166    dlg.Show()
2167
2168
2169def testAnimEdit():
2170    anim = AnimationData()
2171    anim.SetDefaultValues(animationIndex=0, windowIndex=0)
2172
2173    dlg = EditDialog(parent=None, animationData=[anim])
2174    dlg.Show()
2175
2176
2177def testExport():
2178    dlg = ExportDialog(parent=None, temporal=TemporalMode.TEMPORAL,
2179                       timeTick=200)
2180    if dlg.ShowModal() == wx.ID_OK:
2181        print(dlg.GetDecorations())
2182        print(dlg.GetExportInformation())
2183        dlg.Destroy()
2184    else:
2185        dlg.Destroy()
2186
2187
2188def testTemporalLayer():
2189    frame = wx.Frame(None)
2190    frame.Show()
2191    layer = AnimLayer()
2192    dlg = AddTemporalLayerDialog(parent=frame, layer=layer)
2193    if dlg.ShowModal() == wx.ID_OK:
2194        layer = dlg.GetLayer()
2195        print(layer.name, layer.cmd, layer.mapType)
2196        dlg.Destroy()
2197    else:
2198        dlg.Destroy()
2199
2200
2201def testAnimLmgr():
2202    from core.layerlist import LayerList
2203
2204    frame = wx.Frame(None)
2205    mgr = AnimSimpleLayerManager(parent=frame, layerList=LayerList())
2206    frame.mgr = mgr
2207    frame.Show()
2208
2209
2210if __name__ == '__main__':
2211    gcore.set_raise_on_error(True)
2212    test()
2213