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