frame.py 31 KB

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