frame.py 32 KB

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