frame.py 31 KB

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