frame.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959
  1. """
  2. @package mapswipe.frame
  3. @brief Map Swipe Frame
  4. Classes:
  5. - frame::SwipeMapPanel
  6. - frame::SwipeMapDisplay
  7. - frame::MapSplitter
  8. (C) 2012 by the GRASS Development Team
  9. This program is free software under the GNU General Public License
  10. (>=v2). Read the file COPYING that comes with GRASS for details.
  11. @author Anna Kratochvilova <kratochanna gmail.com>
  12. """
  13. import os
  14. import wx
  15. import grass.script as grass
  16. from gui_core.mapdisp import DoubleMapPanel, FrameMixin
  17. from gui_core.dialogs import GetImageHandlers
  18. from gui_core.wrap import Slider
  19. from core.render import Map
  20. from mapdisp import statusbar as sb
  21. from core.debug import Debug
  22. from core.gcmd import GError, GMessage
  23. from core.layerlist import LayerListToRendererConverter
  24. from core import globalvar
  25. from gui_core.query import QueryDialog, PrepareQueryResults
  26. from mapswipe.toolbars import SwipeMapToolbar, SwipeMainToolbar, SwipeMiscToolbar
  27. from mapswipe.mapwindow import SwipeBufferedWindow
  28. from mapswipe.dialogs import SwipeMapDialog, PreferencesDialog
  29. class SwipeMapPanel(DoubleMapPanel):
  30. def __init__(
  31. self, parent=None, giface=None, title=_("Map Swipe"), name="swipe", **kwargs
  32. ):
  33. DoubleMapPanel.__init__(
  34. self,
  35. parent=parent,
  36. title=title,
  37. name=name,
  38. firstMap=Map(),
  39. secondMap=Map(),
  40. **kwargs,
  41. )
  42. Debug.msg(1, "SwipeMapPanel.__init__()")
  43. #
  44. # Add toolbars
  45. #
  46. for name in ("swipeMain", "swipeMap", "swipeMisc"):
  47. self.AddToolbar(name)
  48. self._mgr.Update()
  49. self._giface = giface
  50. #
  51. # create widgets
  52. #
  53. self.splitter = MapSplitter(parent=self, id=wx.ID_ANY)
  54. self.sliderH = Slider(self, id=wx.ID_ANY, style=wx.SL_HORIZONTAL)
  55. self.sliderV = Slider(self, id=wx.ID_ANY, style=wx.SL_VERTICAL)
  56. self.mapWindowProperties.autoRenderChanged.connect(self.OnAutoRenderChanged)
  57. self.firstMapWindow = SwipeBufferedWindow(
  58. parent=self.splitter,
  59. giface=self._giface,
  60. properties=self.mapWindowProperties,
  61. Map=self.firstMap,
  62. )
  63. self.secondMapWindow = SwipeBufferedWindow(
  64. parent=self.splitter,
  65. giface=self._giface,
  66. properties=self.mapWindowProperties,
  67. Map=self.secondMap,
  68. )
  69. # bind query signal
  70. self.firstMapWindow.mapQueried.connect(self.Query)
  71. self.secondMapWindow.mapQueried.connect(self.Query)
  72. # bind tracking cursosr to mirror it
  73. self.firstMapWindow.Bind(wx.EVT_MOTION, lambda evt: self.TrackCursor(evt))
  74. self.secondMapWindow.Bind(wx.EVT_MOTION, lambda evt: self.TrackCursor(evt))
  75. self.MapWindow = self.firstMapWindow # current by default
  76. self.firstMapWindow.zoomhistory = self.secondMapWindow.zoomhistory
  77. self.SetBindRegions(True)
  78. self._mode = "swipe"
  79. # statusbar items
  80. statusbarItems = [
  81. sb.SbCoordinates,
  82. sb.SbRegionExtent,
  83. sb.SbCompRegionExtent,
  84. sb.SbDisplayGeometry,
  85. sb.SbMapScale,
  86. sb.SbGoTo,
  87. ]
  88. self.statusbar = self.CreateStatusbar(statusbarItems)
  89. self._addPanes()
  90. self._bindWindowsActivation()
  91. self._setUpMapWindow(self.firstMapWindow)
  92. self._setUpMapWindow(self.secondMapWindow)
  93. self._mgr.GetPane("sliderV").Hide()
  94. self._mgr.GetPane("sliderH").Show()
  95. self.slider = self.sliderH
  96. self.Bind(wx.EVT_SIZE, self.OnSize)
  97. self.Bind(wx.EVT_IDLE, self.OnIdle)
  98. self.SetSize((800, 600))
  99. self._mgr.Update()
  100. self.rasters = {"first": None, "second": None}
  101. self._inputDialog = None
  102. self._preferencesDialog = None
  103. self._queryDialog = None
  104. # default action in map toolbar
  105. self.GetMapToolbar().SelectDefault()
  106. self.resize = False
  107. wx.CallAfter(self.CallAfterInit)
  108. def TrackCursor(self, event):
  109. """Track cursor in one window and show cross in the other.
  110. Only for mirror mode.
  111. """
  112. if self._mode == "swipe":
  113. event.Skip()
  114. return
  115. coords = event.GetPosition()
  116. if event.GetId() == self.secondMapWindow.GetId():
  117. self.firstMapWindow.DrawMouseCursor(coords=coords)
  118. else:
  119. self.secondMapWindow.DrawMouseCursor(coords=coords)
  120. event.Skip()
  121. def ActivateFirstMap(self, event=None):
  122. """Switch tracking direction"""
  123. super(SwipeMapPanel, self).ActivateFirstMap(event)
  124. self.firstMapWindow.ClearLines()
  125. self.firstMapWindow.Refresh()
  126. def ActivateSecondMap(self, event=None):
  127. """Switch tracking direction"""
  128. super(SwipeMapPanel, self).ActivateSecondMap(event)
  129. self.secondMapWindow.ClearLines()
  130. self.secondMapWindow.Refresh()
  131. def CallAfterInit(self):
  132. self.InitSliderBindings()
  133. self.splitter.SplitVertically(self.firstMapWindow, self.secondMapWindow, 0)
  134. self.splitter.Init()
  135. if not (self.rasters["first"] and self.rasters["second"]):
  136. self.OnSelectLayers(event=None)
  137. def ResetSlider(self):
  138. if self.splitter.GetSplitMode() == wx.SPLIT_VERTICAL:
  139. size = self.splitter.GetSize()[0]
  140. else:
  141. size = self.splitter.GetSize()[1]
  142. self.slider.SetRange(0, size)
  143. self.slider.SetValue(self.splitter.GetSashPosition())
  144. def InitSliderBindings(self):
  145. self.sliderH.Bind(wx.EVT_SPIN, self.OnSliderPositionChanging)
  146. self.sliderH.Bind(wx.EVT_SCROLL_THUMBRELEASE, self.OnSliderPositionChanged)
  147. self.sliderV.Bind(wx.EVT_SPIN, self.OnSliderPositionChanging)
  148. self.sliderV.Bind(wx.EVT_SCROLL_THUMBRELEASE, self.OnSliderPositionChanged)
  149. self.splitter.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGING, self.OnSashChanging)
  150. self.splitter.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, self.OnSashChanged)
  151. def OnSliderPositionChanging(self, event):
  152. """Slider changes its position, sash must be moved too."""
  153. Debug.msg(5, "SwipeMapPanel.OnSliderPositionChanging()")
  154. self.GetFirstWindow().movingSash = True
  155. self.GetSecondWindow().movingSash = True
  156. pos = event.GetPosition()
  157. if pos > 0:
  158. self.splitter.SetSashPosition(pos)
  159. self.splitter.OnSashChanging(None)
  160. def OnSliderPositionChanged(self, event):
  161. """Slider position changed, sash must be moved too."""
  162. Debug.msg(5, "SwipeMapPanel.OnSliderPositionChanged()")
  163. self.splitter.SetSashPosition(event.GetPosition())
  164. self.splitter.OnSashChanged(None)
  165. def OnSashChanging(self, event):
  166. """Sash position is changing, slider must be moved too."""
  167. Debug.msg(5, "SwipeMapPanel.OnSashChanging()")
  168. self.slider.SetValue(self.splitter.GetSashPosition())
  169. event.Skip()
  170. def OnSashChanged(self, event):
  171. """Sash position changed, slider must be moved too."""
  172. Debug.msg(5, "SwipeMapPanel.OnSashChanged()")
  173. self.OnSashChanging(event)
  174. event.Skip()
  175. def OnSize(self, event):
  176. Debug.msg(4, "SwipeMapPanel.OnSize()")
  177. self.resize = grass.clock()
  178. super(SwipeMapPanel, self).OnSize(event)
  179. def OnIdle(self, event):
  180. if self.resize and grass.clock() - self.resize > 0.2:
  181. w1 = self.GetFirstWindow()
  182. w2 = self.GetSecondWindow()
  183. sizeAll = self.splitter.GetSize()
  184. w1.SetClientSize(sizeAll)
  185. w2.SetClientSize(sizeAll)
  186. w1.OnSize(event)
  187. w2.OnSize(event)
  188. self.ResetSlider()
  189. self.resize = False
  190. def OnAutoRenderChanged(self, value):
  191. """Auto rendering state changed."""
  192. style = self.splitter.GetWindowStyle()
  193. style ^= wx.SP_LIVE_UPDATE
  194. self.splitter.SetWindowStyle(style)
  195. def AddToolbar(self, name):
  196. """Add defined toolbar to the window
  197. Currently known toolbars are:
  198. - 'swipeMain' - swipe functionality
  199. - 'swipeMap' - basic map toolbar
  200. - 'swipeMisc' - misc (settings, help)
  201. """
  202. if name == "swipeMain":
  203. if "swipeMain" not in self.toolbars:
  204. self.toolbars["swipeMain"] = SwipeMainToolbar(self)
  205. self._mgr.AddPane(
  206. self.toolbars["swipeMain"],
  207. wx.aui.AuiPaneInfo()
  208. .Name("swipeMain")
  209. .Caption(_("Main Toolbar"))
  210. .ToolbarPane()
  211. .Top()
  212. .LeftDockable(False)
  213. .RightDockable(False)
  214. .BottomDockable(False)
  215. .TopDockable(True)
  216. .CloseButton(False)
  217. .Layer(2)
  218. .Row(1)
  219. .Position(0)
  220. .BestSize((self.toolbars["swipeMain"].GetBestSize())),
  221. )
  222. if name == "swipeMap":
  223. if "swipeMap" not in self.toolbars:
  224. self.toolbars["swipeMap"] = SwipeMapToolbar(self, self._toolSwitcher)
  225. self._mgr.AddPane(
  226. self.toolbars["swipeMap"],
  227. wx.aui.AuiPaneInfo()
  228. .Name("swipeMap")
  229. .Caption(_("Map Toolbar"))
  230. .ToolbarPane()
  231. .Top()
  232. .LeftDockable(False)
  233. .RightDockable(False)
  234. .BottomDockable(False)
  235. .TopDockable(True)
  236. .CloseButton(False)
  237. .Layer(2)
  238. .Row(1)
  239. .Position(1)
  240. .BestSize((self.toolbars["swipeMap"].GetBestSize())),
  241. )
  242. if name == "swipeMisc":
  243. if "swipeMisc" not in self.toolbars:
  244. self.toolbars["swipeMisc"] = SwipeMiscToolbar(self)
  245. self._mgr.AddPane(
  246. self.toolbars["swipeMisc"],
  247. wx.aui.AuiPaneInfo()
  248. .Name("swipeMisc")
  249. .Caption(_("Misc Toolbar"))
  250. .ToolbarPane()
  251. .Top()
  252. .LeftDockable(False)
  253. .RightDockable(False)
  254. .BottomDockable(False)
  255. .TopDockable(True)
  256. .CloseButton(False)
  257. .Layer(2)
  258. .Row(1)
  259. .Position(2)
  260. .BestSize((self.toolbars["swipeMisc"].GetBestSize())),
  261. )
  262. def _addPanes(self):
  263. """Add splitter window, sliders and statusbar to aui manager"""
  264. # splitter window
  265. self._mgr.AddPane(
  266. self.splitter,
  267. wx.aui.AuiPaneInfo()
  268. .Name("splitter")
  269. .CaptionVisible(False)
  270. .PaneBorder(True)
  271. .Dockable(False)
  272. .Floatable(False)
  273. .CloseButton(False)
  274. .Center()
  275. .Layer(1)
  276. .BestSize((self.splitter.GetBestSize())),
  277. )
  278. # sliders
  279. self._mgr.AddPane(
  280. self.sliderH,
  281. wx.aui.AuiPaneInfo()
  282. .Name("sliderH")
  283. .CaptionVisible(False)
  284. .PaneBorder(False)
  285. .CloseButton(False)
  286. .Gripper(True)
  287. .GripperTop(False)
  288. .BottomDockable(True)
  289. .TopDockable(True)
  290. .LeftDockable(False)
  291. .RightDockable(False)
  292. .Bottom()
  293. .Layer(1)
  294. .BestSize((self.sliderH.GetBestSize())),
  295. )
  296. self._mgr.AddPane(
  297. self.sliderV,
  298. wx.aui.AuiPaneInfo()
  299. .Name("sliderV")
  300. .CaptionVisible(False)
  301. .PaneBorder(False)
  302. .CloseButton(False)
  303. .Gripper(True)
  304. .GripperTop(True)
  305. .BottomDockable(False)
  306. .TopDockable(False)
  307. .LeftDockable(True)
  308. .RightDockable(True)
  309. .Right()
  310. .Layer(1)
  311. .BestSize((self.sliderV.GetBestSize())),
  312. )
  313. # statusbar
  314. self.AddStatusbarPane()
  315. def ZoomToMap(self):
  316. """
  317. Set display extents to match selected raster (including NULLs)
  318. or vector map.
  319. """
  320. layers = []
  321. if self.rasters["first"]:
  322. layers += self.firstMap.GetListOfLayers()
  323. if self.rasters["second"]:
  324. layers += self.secondMap.GetListOfLayers()
  325. if layers:
  326. self.GetFirstWindow().ZoomToMap(layers=layers)
  327. self.GetSecondWindow().ZoomToMap(layers=layers)
  328. def OnZoomToMap(self, event):
  329. """Zoom to map"""
  330. self.ZoomToMap()
  331. def OnZoomBack(self, event):
  332. self.GetFirstWindow().ZoomBack()
  333. self.secondMap.region = self.firstMap.region
  334. self.Render(self.GetSecondWindow())
  335. def OnSelectLayers(self, event):
  336. if self._inputDialog is None:
  337. dlg = SwipeMapDialog(
  338. self,
  339. first=self.rasters["first"],
  340. second=self.rasters["second"],
  341. firstLayerList=None,
  342. secondLayerList=None,
  343. )
  344. dlg.applyChanges.connect(self.OnApplyInputChanges)
  345. # connect to convertor object to convert to Map
  346. # store reference to convertor is needed otherwise it would be
  347. # discarded
  348. self._firstConverter = self._connectSimpleLmgr(
  349. dlg.GetFirstSimpleLmgr(), self.GetFirstMap()
  350. )
  351. self._secondConverter = self._connectSimpleLmgr(
  352. dlg.GetSecondSimpleLmgr(), self.GetSecondMap()
  353. )
  354. self._inputDialog = dlg
  355. dlg.CentreOnParent()
  356. dlg.Show()
  357. else:
  358. if self._inputDialog.IsShown():
  359. self._inputDialog.Raise()
  360. self._inputDialog.SetFocus()
  361. else:
  362. self._inputDialog.Show()
  363. def _connectSimpleLmgr(self, lmgr, renderer):
  364. converter = LayerListToRendererConverter(renderer)
  365. lmgr.opacityChanged.connect(converter.ChangeLayerOpacity)
  366. lmgr.cmdChanged.connect(converter.ChangeLayerCmd)
  367. lmgr.layerAdded.connect(converter.AddLayer)
  368. lmgr.layerRemoved.connect(converter.RemoveLayer)
  369. lmgr.layerActivated.connect(converter.ChangeLayerActive)
  370. lmgr.layerMovedUp.connect(converter.MoveLayerUp)
  371. lmgr.layerMovedDown.connect(converter.MoveLayerDown)
  372. lmgr.anyChange.connect(self._simpleLmgrChanged)
  373. return converter
  374. def _simpleLmgrChanged(self):
  375. if self.IsAutoRendered():
  376. self.OnRender(event=None)
  377. def OnApplyInputChanges(self):
  378. first, second = self._inputDialog.GetValues()
  379. if self._inputDialog.IsSimpleMode():
  380. self.rasters["first"], self.rasters["second"] = first, second
  381. res1 = self.SetFirstRaster(name=self.rasters["first"])
  382. res2 = self.SetSecondRaster(name=self.rasters["second"])
  383. if not (res1 and res2) and (first or second):
  384. message = ""
  385. if first and not res1:
  386. message += _("Map <%s> not found. ") % self.rasters["first"]
  387. if second and not res2:
  388. message += _("Map <%s> not found.") % self.rasters["second"]
  389. if message:
  390. GError(parent=self, message=message)
  391. return
  392. self.ZoomToMap()
  393. else:
  394. LayerListToRendererConverter(self.GetFirstMap()).ConvertAll(first)
  395. LayerListToRendererConverter(self.GetSecondMap()).ConvertAll(second)
  396. self.SetRasterNames()
  397. if self.IsAutoRendered():
  398. self.OnRender(event=None)
  399. def SetFirstRaster(self, name):
  400. """Set raster map to first Map"""
  401. if name:
  402. raster = grass.find_file(name=name, element="cell")
  403. if raster.get("fullname"):
  404. self.rasters["first"] = raster["fullname"]
  405. self.SetLayer(name=raster["fullname"], mapInstance=self.GetFirstMap())
  406. return True
  407. return False
  408. def SetSecondRaster(self, name):
  409. """Set raster map to second Map"""
  410. if name:
  411. raster = grass.find_file(name=name, element="cell")
  412. if raster.get("fullname"):
  413. self.rasters["second"] = raster["fullname"]
  414. self.SetLayer(name=raster["fullname"], mapInstance=self.GetSecondMap())
  415. return True
  416. return False
  417. def SetLayer(self, name, mapInstance):
  418. """Sets layer in Map.
  419. :param name: layer (raster) name
  420. """
  421. Debug.msg(3, "SwipeMapPanel.SetLayer(): name=%s" % name)
  422. # this simple application enables to keep only one raster
  423. mapInstance.DeleteAllLayers()
  424. cmdlist = ["d.rast", "map=%s" % name]
  425. # add layer to Map instance (core.render)
  426. mapInstance.AddLayer(
  427. ltype="raster",
  428. command=cmdlist,
  429. active=True,
  430. name=name,
  431. hidden=False,
  432. opacity=1.0,
  433. render=True,
  434. )
  435. def OnSwitchWindows(self, event):
  436. """Switch windows position."""
  437. Debug.msg(3, "SwipeMapPanel.OnSwitchWindows()")
  438. splitter = self.splitter
  439. w1, w2 = splitter.GetWindow1(), splitter.GetWindow2()
  440. splitter.ReplaceWindow(w1, w2)
  441. splitter.ReplaceWindow(w2, w1)
  442. # self.OnSize(None)
  443. splitter.OnSashChanged(None)
  444. def _saveToFile(self, fileName, fileType):
  445. """Creates composite image by rendering both images and
  446. pasting them into the new one.
  447. .. todo::
  448. specify size of the new image (problem is inaccurate scaling)
  449. .. todo::
  450. make dividing line width and color optional
  451. """
  452. w1 = self.splitter.GetWindow1()
  453. w2 = self.splitter.GetWindow2()
  454. lineWidth = 1
  455. # render to temporary files
  456. filename1 = grass.tempfile(False) + "1"
  457. filename2 = grass.tempfile(False) + "2"
  458. width, height = self.splitter.GetClientSize()
  459. class _onDone:
  460. """Callback class that remembers how many times
  461. it was called. Needs to be called twice because
  462. we are pasting together 2 rendered images, so
  463. we need to know when both are finished."""
  464. def __init__(self2):
  465. self2.called = 0
  466. def __call__(self2):
  467. self2.called += 1
  468. if self2.called == 2:
  469. self2.process()
  470. def process(self2):
  471. # create empty white image - needed for line
  472. im = wx.Image(width, height)
  473. im.Replace(0, 0, 0, 255, 255, 255)
  474. # paste images
  475. if self._mode == "swipe":
  476. if self.splitter.GetSplitMode() == wx.SPLIT_HORIZONTAL:
  477. im1 = wx.Image(filename1).GetSubImage((0, 0, width, -y))
  478. im.Paste(im1, 0, 0)
  479. im.Paste(wx.Image(filename2), -x, -y + lineWidth)
  480. else:
  481. im1 = wx.Image(filename1).GetSubImage((0, 0, -x, height))
  482. im.Paste(im1, 0, 0)
  483. im.Paste(wx.Image(filename2), -x + lineWidth, -y)
  484. else:
  485. if self.splitter.GetSplitMode() == wx.SPLIT_HORIZONTAL:
  486. im1 = wx.Image(filename1)
  487. im.Paste(im1, 0, 0)
  488. im.Paste(wx.Image(filename2), 0, fh + lineWidth)
  489. else:
  490. im1 = wx.Image(filename1)
  491. im.Paste(im1, 0, 0)
  492. im.Paste(wx.Image(filename2), fw + lineWidth, 0)
  493. im.SaveFile(fileName, fileType)
  494. # remove temporary files
  495. grass.try_remove(filename1)
  496. grass.try_remove(filename2)
  497. callback = _onDone()
  498. if self._mode == "swipe":
  499. x, y = w2.GetImageCoords()
  500. w1.SaveToFile(filename1, fileType, width, height, callback=callback)
  501. w2.SaveToFile(filename2, fileType, width, height, callback=callback)
  502. else:
  503. fw, fh = w1.GetClientSize()
  504. w1.SaveToFile(filename1, fileType, fw, fh, callback=callback)
  505. sw, sh = w2.GetClientSize()
  506. w2.SaveToFile(filename2, fileType, sw, sh, callback=callback)
  507. def SaveToFile(self, event):
  508. """Save map to image"""
  509. img = self.firstMapWindow.img or self.secondMapWindow.img
  510. if not img:
  511. GMessage(
  512. parent=self,
  513. message=_("Nothing to render (empty map). Operation canceled."),
  514. )
  515. return
  516. filetype, ltype = GetImageHandlers(img)
  517. # get filename
  518. dlg = wx.FileDialog(
  519. parent=self,
  520. message=_(
  521. "Choose a file name to save the image " "(no need to add extension)"
  522. ),
  523. wildcard=filetype,
  524. style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT,
  525. )
  526. if dlg.ShowModal() == wx.ID_OK:
  527. path = dlg.GetPath()
  528. if not path:
  529. dlg.Destroy()
  530. return
  531. base, ext = os.path.splitext(path)
  532. fileType = ltype[dlg.GetFilterIndex()]["type"]
  533. extType = ltype[dlg.GetFilterIndex()]["ext"]
  534. if ext != extType:
  535. path = base + "." + extType
  536. self._saveToFile(path, fileType)
  537. dlg.Destroy()
  538. def OnSwitchOrientation(self, event):
  539. """Switch orientation of the sash."""
  540. Debug.msg(3, "SwipeMapPanel.OnSwitchOrientation()")
  541. splitter = self.splitter
  542. splitter.Unsplit()
  543. if splitter.GetSplitMode() == wx.SPLIT_HORIZONTAL:
  544. splitter.SplitVertically(self.firstMapWindow, self.secondMapWindow, 0)
  545. self.slider = self.sliderH
  546. if self._mode == "swipe":
  547. self._mgr.GetPane("sliderH").Show()
  548. self._mgr.GetPane("sliderV").Hide()
  549. else:
  550. splitter.SplitHorizontally(self.firstMapWindow, self.secondMapWindow, 0)
  551. self.slider = self.sliderV
  552. if self._mode == "swipe":
  553. self._mgr.GetPane("sliderV").Show()
  554. self._mgr.GetPane("sliderH").Hide()
  555. self._mgr.Update()
  556. splitter.OnSashChanged(None)
  557. self.OnSize(None)
  558. self.SetRasterNames()
  559. def OnAddText(self, event):
  560. """Double click on text overlay
  561. So far not implemented.
  562. """
  563. pass
  564. def SetViewMode(self, mode):
  565. """Sets view mode.
  566. :param mode: view mode ('swipe', 'mirror')
  567. """
  568. if self._mode == mode:
  569. return
  570. self._mode = mode
  571. self.toolbars["swipeMain"].SetMode(mode)
  572. # set window mode
  573. self.GetFirstWindow().SetMode(mode)
  574. self.GetSecondWindow().SetMode(mode)
  575. # hide/show slider
  576. if self.splitter.GetSplitMode() == wx.SPLIT_HORIZONTAL:
  577. self._mgr.GetPane("sliderV").Show(mode == "swipe")
  578. size = self.splitter.GetSize()[1] // 2
  579. else:
  580. self._mgr.GetPane("sliderH").Show(mode == "swipe")
  581. size = self.splitter.GetSize()[0] // 2
  582. # set sash in the middle
  583. self.splitter.SetSashPosition(size)
  584. self.slider.SetValue(size)
  585. self._mgr.Update()
  586. # enable / disable sash
  587. self.splitter.EnableSash(mode == "swipe")
  588. # hack to make it work
  589. self.splitter.OnSashChanged(None)
  590. self.SendSizeEvent()
  591. def SetRasterNames(self):
  592. if not self._inputDialog or self._inputDialog.IsSimpleMode():
  593. if self.rasters["first"]:
  594. self.GetFirstWindow().SetRasterNameText(self.rasters["first"], 101)
  595. if self.rasters["second"]:
  596. self.GetSecondWindow().SetRasterNameText(self.rasters["second"], 102)
  597. else:
  598. self.GetFirstWindow().SetRasterNameText("", 101)
  599. self.GetSecondWindow().SetRasterNameText("", 102)
  600. def Query(self, x, y):
  601. """Query active layers from both mapwindows.
  602. :param x,y: coordinates
  603. """
  604. rasters = (
  605. [
  606. layer.GetName()
  607. for layer in self.GetFirstMap().GetListOfLayers(
  608. ltype="raster", active=True
  609. )
  610. ],
  611. [
  612. layer.GetName()
  613. for layer in self.GetSecondMap().GetListOfLayers(
  614. ltype="raster", active=True
  615. )
  616. ],
  617. )
  618. vectors = (
  619. [
  620. layer.GetName()
  621. for layer in self.GetFirstMap().GetListOfLayers(
  622. ltype="vector", active=True
  623. )
  624. ],
  625. [
  626. layer.GetName()
  627. for layer in self.GetSecondMap().GetListOfLayers(
  628. ltype="vector", active=True
  629. )
  630. ],
  631. )
  632. if not (rasters[0] + rasters[1] + vectors[0] + vectors[1]):
  633. GMessage(
  634. parent=self,
  635. message=_("No raster or vector map layer selected for querying."),
  636. )
  637. return
  638. # set query snap distance for v.what at map unit equivalent of 10
  639. # pixels
  640. qdist = 10.0 * (
  641. (self.GetFirstMap().region["e"] - self.GetFirstMap().region["w"])
  642. / self.GetFirstMap().width
  643. )
  644. east, north = self.GetFirstWindow().Pixel2Cell((x, y))
  645. # use display region settings instead of computation region settings
  646. self.tmpreg = os.getenv("GRASS_REGION")
  647. os.environ["GRASS_REGION"] = self.GetFirstMap().SetRegion(windres=False)
  648. result = []
  649. if rasters[0]:
  650. result.extend(
  651. grass.raster_what(map=rasters[0], coord=(east, north), localized=True)
  652. )
  653. if vectors[0]:
  654. result.extend(
  655. grass.vector_what(map=vectors[0], coord=(east, north), distance=qdist)
  656. )
  657. if rasters[1]:
  658. result.extend(
  659. grass.raster_what(map=rasters[1], coord=(east, north), localized=True)
  660. )
  661. if vectors[1]:
  662. result.extend(
  663. grass.vector_what(map=vectors[1], coord=(east, north), distance=qdist)
  664. )
  665. self._QueryMapDone()
  666. result = PrepareQueryResults(coordinates=(east, north), result=result)
  667. if self._queryDialog:
  668. self._queryDialog.Raise()
  669. self._queryDialog.SetData(result)
  670. else:
  671. self._queryDialog = QueryDialog(parent=self, data=result)
  672. self._queryDialog.Bind(wx.EVT_CLOSE, self._oncloseQueryDialog)
  673. self._queryDialog.redirectOutput.connect(
  674. lambda output: self._giface.WriteLog(output)
  675. )
  676. self._queryDialog.Show()
  677. def _oncloseQueryDialog(self, event):
  678. self._queryDialog = None
  679. event.Skip()
  680. def _QueryMapDone(self):
  681. """Restore settings after querying (restore GRASS_REGION)"""
  682. if hasattr(self, "tmpreg"):
  683. if self.tmpreg:
  684. os.environ["GRASS_REGION"] = self.tmpreg
  685. elif "GRASS_REGION" in os.environ:
  686. del os.environ["GRASS_REGION"]
  687. elif "GRASS_REGION" in os.environ:
  688. del os.environ["GRASS_REGION"]
  689. if hasattr(self, "tmpreg"):
  690. del self.tmpreg
  691. def GetMapToolbar(self):
  692. """Returns toolbar with zooming tools"""
  693. return self.toolbars["swipeMap"]
  694. def OnHelp(self, event):
  695. self._giface.Help(entry="wxGUI.mapswipe")
  696. def OnPreferences(self, event):
  697. if not self._preferencesDialog:
  698. dlg = PreferencesDialog(parent=self, giface=self._giface)
  699. self._preferencesDialog = dlg
  700. self._preferencesDialog.CenterOnParent()
  701. self._preferencesDialog.Show()
  702. def OnCloseWindow(self, event):
  703. self.GetFirstMap().Clean()
  704. self.GetSecondMap().Clean()
  705. self._mgr.UnInit()
  706. if self._inputDialog:
  707. self._inputDialog.UnInit()
  708. self.Destroy()
  709. class SwipeMapDisplay(FrameMixin, SwipeMapPanel):
  710. """Map display for wrapping map panel with frame methods"""
  711. def __init__(self, parent, giface, **kwargs):
  712. # init map panel
  713. SwipeMapPanel.__init__(
  714. self,
  715. parent=parent,
  716. giface=giface,
  717. **kwargs,
  718. )
  719. # set system icon
  720. parent.SetIcon(
  721. wx.Icon(
  722. os.path.join(globalvar.ICONDIR, "grass_map.ico"), wx.BITMAP_TYPE_ICO
  723. )
  724. )
  725. # bindings
  726. parent.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
  727. # extend shortcuts and create frame accelerator table
  728. self.shortcuts_table.append((self.OnFullScreen, wx.ACCEL_NORMAL, wx.WXK_F11))
  729. self._initShortcuts()
  730. # add Map Display panel to Map Display frame
  731. sizer = wx.BoxSizer(wx.VERTICAL)
  732. sizer.Add(self, proportion=1, flag=wx.EXPAND)
  733. parent.SetSizer(sizer)
  734. parent.Layout()
  735. class MapSplitter(wx.SplitterWindow):
  736. """Splitter window for displaying two maps"""
  737. def __init__(self, parent, id):
  738. wx.SplitterWindow.__init__(self, parent=parent, id=id, style=wx.SP_LIVE_UPDATE)
  739. Debug.msg(2, "MapSplitter.__init__()")
  740. self.sashWidthMin = 1
  741. self.sashWidthMax = 10
  742. self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, self.OnSashChanged)
  743. self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGING, self.OnSashChanging)
  744. self._moveSash = True
  745. def EnableSash(self, enable):
  746. self._moveSash = enable
  747. def Init(self):
  748. self.OnSashChanged(evt=None)
  749. self.SetMinimumPaneSize(0)
  750. # def OnMotion(self, event):
  751. # w = self.GetSashSize()
  752. # w1, w2 = self.GetWindow1(), self.GetWindow2()
  753. # if self.SashHitTest(event.GetX(), event.GetY(), tolerance = 20):
  754. # if w == self.sashWidthMin:
  755. # self.SetSashSize(self.sashWidthMax)
  756. # self.SetNeedUpdating(True)
  757. # w1.movingSash = True
  758. # w2.movingSash = True
  759. # else:
  760. # w1.movingSash = False
  761. # w1.movingSash = False
  762. # else:
  763. # if w == self.sashWidthMax:
  764. # self.SetSashSize(self.sashWidthMin)
  765. # self.SetNeedUpdating(True)
  766. # w1.movingSash = True
  767. # w2.movingSash = True
  768. # else:
  769. # w1.movingSash = False
  770. # w2.movingSash = False
  771. # event.Skip()
  772. def OnSashChanged(self, evt):
  773. Debug.msg(5, "MapSplitter.OnSashChanged()")
  774. if not self._moveSash:
  775. return
  776. w1, w2 = self.GetWindow1(), self.GetWindow2()
  777. w1.movingSash = False
  778. w2.movingSash = False
  779. wx.CallAfter(self.SashChanged)
  780. def SashChanged(self):
  781. Debug.msg(5, "MapSplitter.SashChanged()")
  782. w1, w2 = self.GetWindow1(), self.GetWindow2()
  783. w1.SetImageCoords((0, 0))
  784. if self.GetSplitMode() == wx.SPLIT_VERTICAL:
  785. w = w1.GetSize()[0]
  786. w2.SetImageCoords((-w, 0))
  787. else:
  788. h = w1.GetSize()[1]
  789. w2.SetImageCoords((0, -h))
  790. w1.UpdateMap(render=False, renderVector=False)
  791. w2.UpdateMap(render=False, renderVector=False)
  792. pos = self.GetSashPosition()
  793. self.last = pos
  794. def OnSashChanging(self, event):
  795. Debug.msg(5, "MapSplitter.OnSashChanging()")
  796. if not self._moveSash:
  797. event.SetSashPosition(-1)
  798. return
  799. if not (self.GetWindowStyle() & wx.SP_LIVE_UPDATE):
  800. if event:
  801. event.Skip()
  802. return
  803. pos = self.GetSashPosition()
  804. dpos = pos - self.last
  805. self.last = pos
  806. if self.GetSplitMode() == wx.SPLIT_VERTICAL:
  807. dx = -dpos
  808. dy = 0
  809. else:
  810. dx = 0
  811. dy = -dpos
  812. self.GetWindow2().TranslateImage(dx, dy)
  813. self.GetWindow1().movingSash = True
  814. self.GetWindow2().movingSash = True