dialogs.py 75 KB

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