frame.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. """!
  2. @package swipe.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 sys
  13. import wx
  14. import time
  15. import grass.script as grass
  16. from gui_core.mapdisp import DoubleMapFrame
  17. from gui_core.dialogs import GetImageHandlers
  18. from core.render import Map
  19. from mapdisp import statusbar as sb
  20. from core.debug import Debug
  21. from core.gcmd import RunCommand, GError, GMessage
  22. from mapdisp.statusbar import EVT_AUTO_RENDER
  23. from swipe.toolbars import SwipeMapToolbar, SwipeMainToolbar, SwipeMiscToolbar
  24. from swipe.mapwindow import SwipeBufferedWindow
  25. from swipe.dialogs import SwipeMapDialog
  26. class SwipeMapFrame(DoubleMapFrame):
  27. def __init__(self, parent = None, title = _("GRASS GIS Map Swipe"), name = "swipe", **kwargs):
  28. DoubleMapFrame.__init__(self, parent = parent, title = title, name = name,
  29. firstMap = Map(), secondMap = Map(), **kwargs)
  30. Debug.msg (1, "SwipeMapFrame.__init__()")
  31. #
  32. # Add toolbars
  33. #
  34. toolbars = ['swipeMisc', 'swipeMap', 'swipeMain']
  35. if sys.platform == 'win32':
  36. self.AddToolbar(toolbars.pop(1))
  37. toolbars.reverse()
  38. else:
  39. self.AddToolbar(toolbars.pop(0))
  40. for toolb in toolbars:
  41. self.AddToolbar(toolb)
  42. #
  43. # create widgets
  44. #
  45. self.splitter = MapSplitter(parent = self, id = wx.ID_ANY)
  46. self.sliderH = wx.Slider(self, id = wx.ID_ANY, style = wx.SL_HORIZONTAL)
  47. self.sliderV = wx.Slider(self, id = wx.ID_ANY, style = wx.SL_VERTICAL)
  48. self.firstMapWindow = SwipeBufferedWindow(parent = self.splitter, Map = self.firstMap, frame = self)
  49. self.secondMapWindow = SwipeBufferedWindow(parent = self.splitter, Map = self.secondMap, frame = self)
  50. self.MapWindow = self.firstMapWindow # current by default
  51. self.firstMap.region = self.secondMap.region
  52. self.firstMapWindow.zoomhistory = self.secondMapWindow.zoomhistory
  53. self.splitter.SplitVertically(self.firstMapWindow, self.secondMapWindow, 0)
  54. self._addPanes()
  55. self._bindWindowsActivation()
  56. self._mgr.GetPane('sliderV').Hide()
  57. self._mgr.GetPane('sliderH').Show()
  58. self.slider = self.sliderH
  59. self.InitStatusbar()
  60. self.Bind(wx.EVT_SIZE, self.OnSize)
  61. self.Bind(EVT_AUTO_RENDER, self.OnAutoRenderChanged)
  62. self.Bind(wx.EVT_IDLE, self.OnIdle)
  63. self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
  64. self.SetSize((800, 600))
  65. self._mgr.Update()
  66. self.rasters = {'first': None, 'second': None}
  67. # default action in map toolbar
  68. self.OnPan(event = None)
  69. self.resize = False
  70. wx.CallAfter(self.CallAfterInit)
  71. def CallAfterInit(self):
  72. self.InitSliderBindings()
  73. if not (self.rasters['first'] and self.rasters['second']):
  74. self.OnSelectRasters(event = None)
  75. def InitStatusbar(self):
  76. """!Init statusbar (default items)."""
  77. # items for choice
  78. self.statusbarItems = [sb.SbCoordinates,
  79. sb.SbRegionExtent,
  80. sb.SbCompRegionExtent,
  81. sb.SbShowRegion,
  82. sb.SbAlignExtent,
  83. sb.SbResolution,
  84. sb.SbDisplayGeometry,
  85. sb.SbMapScale,
  86. sb.SbGoTo,
  87. sb.SbProjection]
  88. # create statusbar and its manager
  89. statusbar = self.CreateStatusBar(number = 4, style = 0)
  90. statusbar.SetStatusWidths([-5, -2, -1, -1])
  91. self.statusbarManager = sb.SbManager(mapframe = self, statusbar = statusbar)
  92. # fill statusbar manager
  93. self.statusbarManager.AddStatusbarItemsByClass(self.statusbarItems, mapframe = self, statusbar = statusbar)
  94. self.statusbarManager.AddStatusbarItem(sb.SbMask(self, statusbar = statusbar, position = 2))
  95. self.statusbarManager.AddStatusbarItem(sb.SbRender(self, statusbar = statusbar, position = 3))
  96. self.statusbarManager.Update()
  97. def ResetSlider(self):
  98. if self.splitter.GetSplitMode() == wx.SPLIT_VERTICAL:
  99. size = self.splitter.GetSize()[0]
  100. else:
  101. size = self.splitter.GetSize()[1]
  102. self.slider.SetRange(0, size)
  103. self.slider.SetValue(self.splitter.GetSashPosition())
  104. def InitSliderBindings(self):
  105. self.sliderH.Bind(wx.EVT_SPIN, self.OnSliderPositionChanging)
  106. self.sliderH.Bind(wx.EVT_SCROLL_THUMBRELEASE, self.OnSliderPositionChanged)
  107. self.sliderV.Bind(wx.EVT_SPIN, self.OnSliderPositionChanging)
  108. self.sliderV.Bind(wx.EVT_SCROLL_THUMBRELEASE, self.OnSliderPositionChanged)
  109. self.splitter.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGING, self.OnSashChanging)
  110. self.splitter.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, self.OnSashChanged)
  111. def OnSliderPositionChanging(self, event):
  112. """!Slider changes its position, sash must be moved too."""
  113. Debug.msg (5, "SwipeMapFrame.OnSliderPositionChanging()")
  114. self.GetFirstWindow().movingSash = True
  115. self.GetSecondWindow().movingSash = True
  116. pos = event.GetPosition()
  117. if pos > 0:
  118. self.splitter.SetSashPosition(pos)
  119. self.splitter.OnSashChanging(None)
  120. def OnSliderPositionChanged(self, event):
  121. """!Slider position changed, sash must be moved too."""
  122. Debug.msg (5, "SwipeMapFrame.OnSliderPositionChanged()")
  123. self.splitter.SetSashPosition(event.GetPosition())
  124. self.splitter.OnSashChanged(None)
  125. def OnSashChanging(self, event):
  126. """!Sash position is changing, slider must be moved too."""
  127. Debug.msg (5, "SwipeMapFrame.OnSashChanging()")
  128. self.slider.SetValue(self.splitter.GetSashPosition())
  129. event.Skip()
  130. def OnSashChanged(self, event):
  131. """!Sash position changed, slider must be moved too."""
  132. Debug.msg (5, "SwipeMapFrame.OnSashChanged()")
  133. self.OnSashChanging(event)
  134. event.Skip()
  135. def OnSize(self, event):
  136. Debug.msg (4, "SwipeMapFrame.OnSize()")
  137. self.resize = time.clock()
  138. def OnIdle(self, event):
  139. if self.resize and time.clock() - self.resize > 0.2:
  140. w1 = self.GetFirstWindow()
  141. w2 = self.GetSecondWindow()
  142. sizeAll = self.splitter.GetSize()
  143. w1.SetClientSize(sizeAll)
  144. w2.SetClientSize(sizeAll)
  145. w1.OnSize(event)
  146. w2.OnSize(event)
  147. self.ResetSlider()
  148. self.resize = False
  149. def OnAutoRenderChanged(self, event):
  150. """!Auto rendering state changed."""
  151. style = self.splitter.GetWindowStyle()
  152. style ^= wx.SP_LIVE_UPDATE
  153. self.splitter.SetWindowStyle(style)
  154. def AddToolbar(self, name):
  155. """!Add defined toolbar to the window
  156. Currently known toolbars are:
  157. - 'swipeMap' - basic map toolbar
  158. - 'swipeMain' - swipe functionality
  159. """
  160. if name == "swipeMap":
  161. self.toolbars[name] = SwipeMapToolbar(self)
  162. self._mgr.AddPane(self.toolbars[name],
  163. wx.aui.AuiPaneInfo().
  164. Name(name).Caption(_("Map Toolbar")).
  165. ToolbarPane().Top().
  166. LeftDockable(False).RightDockable(False).
  167. BottomDockable(False).TopDockable(True).
  168. CloseButton(False).Layer(2).Row(1).
  169. BestSize((self.toolbars[name].GetBestSize())))
  170. if name == "swipeMain":
  171. self.toolbars[name] = SwipeMainToolbar(self)
  172. self._mgr.AddPane(self.toolbars[name],
  173. wx.aui.AuiPaneInfo().
  174. Name(name).Caption(_("Main Toolbar")).
  175. ToolbarPane().Top().
  176. LeftDockable(False).RightDockable(False).
  177. BottomDockable(False).TopDockable(True).
  178. CloseButton(False).Layer(2).Row(1).
  179. BestSize((self.toolbars[name].GetBestSize())))
  180. if name == "swipeMisc":
  181. self.toolbars[name] = SwipeMiscToolbar(self)
  182. self._mgr.AddPane(self.toolbars[name],
  183. wx.aui.AuiPaneInfo().
  184. Name(name).Caption(_("Misc Toolbar")).
  185. ToolbarPane().Top().
  186. LeftDockable(False).RightDockable(False).
  187. BottomDockable(False).TopDockable(True).
  188. CloseButton(False).Layer(2).Row(1).
  189. BestSize((self.toolbars[name].GetBestSize())))
  190. def _addPanes(self):
  191. """!Add splitter window and sliders to aui manager"""
  192. # splitter window
  193. self._mgr.AddPane(self.splitter, wx.aui.AuiPaneInfo().
  194. Name('splitter').CaptionVisible(False).PaneBorder(True).
  195. Dockable(False).Floatable(False).CloseButton(False).
  196. Center().Layer(1).BestSize((self.splitter.GetBestSize())))
  197. # sliders
  198. self._mgr.AddPane(self.sliderH, wx.aui.AuiPaneInfo().
  199. Name('sliderH').CaptionVisible(False).PaneBorder(False).
  200. CloseButton(False).Gripper(True).GripperTop(False).
  201. BottomDockable(True).TopDockable(True).
  202. LeftDockable(False).RightDockable(False).
  203. Bottom().Layer(1).BestSize((self.sliderH.GetBestSize())))
  204. self._mgr.AddPane(self.sliderV, wx.aui.AuiPaneInfo().
  205. Name('sliderV').CaptionVisible(False).PaneBorder(False).
  206. CloseButton(False).Gripper(True).GripperTop(True).
  207. BottomDockable(False).TopDockable(False).
  208. LeftDockable(True).RightDockable(True).
  209. Right().Layer(1).BestSize((self.sliderV.GetBestSize())))
  210. def UpdateRegion(self):
  211. """!
  212. Rerender the second window
  213. when the region of the first changed.
  214. """
  215. Debug.msg(3, "SwipeMapFrame.UpdateRegion()")
  216. if self.GetWindow() == self.GetSecondWindow():
  217. self.Render(self.GetFirstWindow())
  218. else:
  219. self.Render(self.GetSecondWindow())
  220. def OnZoomToMap(self, event):
  221. """!
  222. Set display extents to match selected raster (including NULLs)
  223. or vector map.
  224. """
  225. self.GetFirstWindow().ZoomToMap(layers = self.Map.GetListOfLayers())
  226. self.GetSecondWindow().ZoomToMap(layers = self.Map.GetListOfLayers())
  227. # needed again, don't know why
  228. self.firstMap.region = self.secondMap.region
  229. def OnZoomBack(self, event):
  230. self.GetFirstWindow().ZoomBack()
  231. self.secondMap.region = self.firstMap.region
  232. self.Render(self.GetSecondWindow())
  233. def OnSelectRasters(self, event):
  234. """!Choose raster maps and rerender."""
  235. dlg = SwipeMapDialog(self, first = self.rasters['first'], second = self.rasters['second'])
  236. if dlg.ShowModal() == wx.ID_OK:
  237. maps = dlg.GetValues()
  238. res1 = self.SetFirstRaster(name = maps[0])
  239. res2 = self.SetSecondRaster(name = maps[1])
  240. if not (res1 and res2):
  241. message = ''
  242. if not res1:
  243. message += _("Map <%s> not found. ") % maps[0]
  244. if not res2:
  245. message += _("Map <%s> not found.") % maps[1]
  246. GError(parent = self, message = message)
  247. dlg.Destroy()
  248. dlg.Destroy()
  249. self.OnRender(event = None)
  250. def SetFirstRaster(self, name):
  251. """!Set raster map to first Map"""
  252. raster = grass.find_file(name = name, element = 'cell')
  253. if raster['fullname']:
  254. self.rasters['first'] = raster['fullname']
  255. self.SetLayer(name = raster['fullname'], mapInstance = self.GetFirstMap())
  256. self.OnZoomToMap(event = None)
  257. return True
  258. return False
  259. def SetSecondRaster(self, name):
  260. """!Set raster map to second Map"""
  261. raster = grass.find_file(name = name, element = 'cell')
  262. if raster['fullname']:
  263. self.rasters['second'] = raster['fullname']
  264. self.SetLayer(name = raster['fullname'], mapInstance = self.GetSecondMap())
  265. self.OnZoomToMap(event = None)
  266. return True
  267. return False
  268. def SetLayer(self, name, mapInstance):
  269. """!Sets layer in Map.
  270. @param name layer (raster) name
  271. """
  272. Debug.msg (3, "SwipeMapFrame.SetLayer(): name=%s" % name)
  273. # this simple application enables to keep only one raster
  274. mapInstance.DeleteAllLayers()
  275. cmdlist = ['d.rast', 'map=%s' % name]
  276. # add layer to Map instance (core.render)
  277. newLayer = mapInstance.AddLayer(type = 'raster', command = cmdlist, l_active = True,
  278. name = name, l_hidden = False, l_opacity = 1.0,
  279. l_render = True)
  280. def OnSwitchWindows(self, event):
  281. """!Switch windows position."""
  282. Debug.msg(3, "SwipeMapFrame.OnSwitchWindows()")
  283. splitter = self.splitter
  284. w1, w2 = splitter.GetWindow1(), splitter.GetWindow2()
  285. splitter.ReplaceWindow(w1, w2)
  286. splitter.ReplaceWindow(w2, w1)
  287. # self.OnSize(None)
  288. splitter.OnSashChanged(None)
  289. def _saveToFile(self, fileName, fileType):
  290. """!Creates composite image by rendering both images and
  291. pasting them into the new one.
  292. @todo specify size of the new image (problem is inaccurate scaling)
  293. @todo make dividing line width and color optional
  294. """
  295. # get size paramteres
  296. x, y = self.secondMapWindow.GetImageCoords()
  297. width, height = self.splitter.GetClientSize()
  298. lineWidth = 1
  299. # render to temporary files
  300. filename1 = grass.tempfile(False) + '1'
  301. filename2 = grass.tempfile(False) + '2'
  302. self.firstMapWindow.SaveToFile(filename1, fileType, width, height)
  303. self.secondMapWindow.SaveToFile(filename2, fileType, width, height)
  304. # create empty white image - needed for line
  305. im = wx.EmptyImage(width, height)
  306. im.Replace(0, 0, 0, 255, 255, 255)
  307. # paste images
  308. if self.splitter.GetSplitMode() == wx.SPLIT_HORIZONTAL:
  309. im1 = wx.Image(filename1).GetSubImage((0, 0, width, -y))
  310. im.Paste(im1, 0, 0)
  311. im.Paste(wx.Image(filename2), -x, -y + lineWidth)
  312. else:
  313. im1 = wx.Image(filename1).GetSubImage((0, 0, -x, height))
  314. im.Paste(im1, 0, 0)
  315. im.Paste(wx.Image(filename2), -x + lineWidth, -y)
  316. im.SaveFile(fileName, fileType)
  317. # remove temporary files
  318. grass.try_remove(filename1)
  319. grass.try_remove(filename2)
  320. def SaveToFile(self, event):
  321. """!Save map to image
  322. """
  323. img = self.firstMapWindow.img or self.secondMapWindow.img
  324. if not img:
  325. GMessage(parent = self,
  326. message = _("Nothing to render (empty map). Operation canceled."))
  327. return
  328. filetype, ltype = GetImageHandlers(img)
  329. # get filename
  330. dlg = wx.FileDialog(parent = self,
  331. message = _("Choose a file name to save the image "
  332. "(no need to add extension)"),
  333. wildcard = filetype,
  334. style = wx.SAVE | wx.FD_OVERWRITE_PROMPT)
  335. if dlg.ShowModal() == wx.ID_OK:
  336. path = dlg.GetPath()
  337. if not path:
  338. dlg.Destroy()
  339. return
  340. base, ext = os.path.splitext(path)
  341. fileType = ltype[dlg.GetFilterIndex()]['type']
  342. extType = ltype[dlg.GetFilterIndex()]['ext']
  343. if ext != extType:
  344. path = base + '.' + extType
  345. self._saveToFile(path, fileType)
  346. dlg.Destroy()
  347. def OnSwitchOrientation(self, event):
  348. """!Switch orientation of the sash."""
  349. Debug.msg(3, "SwipeMapFrame.OnSwitchOrientation()")
  350. splitter = self.splitter
  351. splitter.Unsplit()
  352. if splitter.GetSplitMode() == wx.SPLIT_HORIZONTAL:
  353. splitter.SplitVertically(self.firstMapWindow, self.secondMapWindow, 0)
  354. self.slider = self.sliderH
  355. self._mgr.GetPane('sliderH').Show()
  356. self._mgr.GetPane('sliderV').Hide()
  357. else:
  358. splitter.SplitHorizontally(self.firstMapWindow, self.secondMapWindow, 0)
  359. self.slider = self.sliderV
  360. self._mgr.GetPane('sliderV').Show()
  361. self._mgr.GetPane('sliderH').Hide()
  362. self._mgr.Update()
  363. splitter.OnSashChanged(None)
  364. self.OnSize(None)
  365. def GetMapToolbar(self):
  366. """!Returns toolbar with zooming tools"""
  367. return self.toolbars['swipeMap']
  368. def IsStandalone(self):
  369. if self.parent:
  370. return False
  371. return True
  372. def OnHelp(self, event):
  373. RunCommand('g.manual',
  374. quiet = True,
  375. entry = 'wxGUI.MapSwipe')
  376. def OnCloseWindow(self, event):
  377. self.GetFirstMap().Clean()
  378. self.GetSecondMap().Clean()
  379. self.Destroy()
  380. class MapSplitter(wx.SplitterWindow):
  381. """!Splitter window for displaying two maps"""
  382. def __init__(self, parent, id):
  383. wx.SplitterWindow.__init__(self, parent = parent, id = id,
  384. style = wx.SP_LIVE_UPDATE
  385. )
  386. Debug.msg(2, "MapSplitter.__init__()")
  387. self.sashWidthMin = 1
  388. self.sashWidthMax = 10
  389. self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, self.OnSashChanged)
  390. self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGING, self.OnSashChanging)
  391. wx.CallAfter(self.Init)
  392. def Init(self):
  393. self.OnSashChanged(evt = None)
  394. self.SetMinimumPaneSize(0)
  395. self.SetSashSize(self.sashWidthMin)
  396. # def OnMotion(self, event):
  397. # w = self.GetSashSize()
  398. # w1, w2 = self.GetWindow1(), self.GetWindow2()
  399. # if self.SashHitTest(event.GetX(), event.GetY(), tolerance = 20):
  400. # if w == self.sashWidthMin:
  401. # self.SetSashSize(self.sashWidthMax)
  402. # self.SetNeedUpdating(True)
  403. # w1.movingSash = True
  404. # w2.movingSash = True
  405. # else:
  406. # w1.movingSash = False
  407. # w1.movingSash = False
  408. # else:
  409. # if w == self.sashWidthMax:
  410. # self.SetSashSize(self.sashWidthMin)
  411. # self.SetNeedUpdating(True)
  412. # w1.movingSash = True
  413. # w2.movingSash = True
  414. # else:
  415. # w1.movingSash = False
  416. # w2.movingSash = False
  417. # event.Skip()
  418. def OnSashChanged(self, evt):
  419. Debug.msg(5, "MapSplitter.OnSashChanged()")
  420. w1, w2 = self.GetWindow1(), self.GetWindow2()
  421. w1.movingSash = False
  422. w2.movingSash = False
  423. wx.CallAfter(self.SashChanged)
  424. def SashChanged(self):
  425. Debug.msg(5, "MapSplitter.SashChanged()")
  426. w1, w2 = self.GetWindow1(), self.GetWindow2()
  427. w1.SetImageCoords((0, 0))
  428. if self.GetSplitMode() == wx.SPLIT_VERTICAL:
  429. w = w1.GetSize()[0]
  430. w2.SetImageCoords((-w, 0))
  431. else:
  432. h = w1.GetSize()[1]
  433. w2.SetImageCoords((0, -h))
  434. w1.UpdateMap(render = False, renderVector = False)
  435. w2.UpdateMap(render = False, renderVector = False)
  436. pos = self.GetSashPosition()
  437. self.last = pos
  438. def OnSashChanging(self, event):
  439. Debug.msg(5, "MapSplitter.OnSashChanging()")
  440. if not (self.GetWindowStyle() & wx.SP_LIVE_UPDATE):
  441. if event:
  442. event.Skip()
  443. return
  444. pos = self.GetSashPosition()
  445. dpos = pos - self.last
  446. self.last = pos
  447. if self.GetSplitMode() == wx.SPLIT_VERTICAL:
  448. dx = -dpos
  449. dy = 0
  450. else:
  451. dx = 0
  452. dy = -dpos
  453. self.GetWindow2().TranslateImage(dx, dy)
  454. self.GetWindow1().movingSash = True
  455. self.GetWindow2().movingSash = True