catalog.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. """
  2. @package datacatalog::catalog
  3. @brief Data catalog
  4. Classes:
  5. - datacatalog::DataCatalog
  6. (C) 2014-2018 by Tereza Fiedlerova, and the GRASS Development Team
  7. This program is free software under the GNU General Public
  8. License (>=v2). Read the file COPYING that comes with GRASS
  9. for details.
  10. @author Tereza Fiedlerova
  11. @author Linda Kladivova l.kladivova@seznam.cz
  12. """
  13. import wx
  14. import os
  15. from core.debug import Debug
  16. from datacatalog.tree import DataCatalogTree
  17. from datacatalog.toolbars import DataCatalogToolbar
  18. from gui_core.infobar import InfoBar
  19. from datacatalog.infomanager import DataCatalogInfoManager
  20. from gui_core.wrap import Menu
  21. from gui_core.forms import GUI
  22. from core.settings import UserSettings
  23. from grass.pydispatch.signal import Signal
  24. from grass.script.utils import clock
  25. from grass.script import gisenv
  26. from grass.grassdb.manage import split_mapset_path
  27. from grass.grassdb.checks import (
  28. get_reason_id_mapset_not_usable,
  29. is_fallback_session,
  30. is_first_time_user,
  31. )
  32. class DataCatalog(wx.Panel):
  33. """Data catalog panel"""
  34. def __init__(
  35. self,
  36. parent,
  37. giface=None,
  38. id=wx.ID_ANY,
  39. title=_("Data catalog"),
  40. name="catalog",
  41. **kwargs,
  42. ):
  43. """Panel constructor"""
  44. self.showNotification = Signal("DataCatalog.showNotification")
  45. self.parent = parent
  46. self.baseTitle = title
  47. self.giface = giface
  48. self._startLoadingTime = 0
  49. wx.Panel.__init__(self, parent=parent, id=id, **kwargs)
  50. self.SetName("DataCatalog")
  51. Debug.msg(1, "DataCatalog.__init__()")
  52. # toolbar
  53. self.toolbar = DataCatalogToolbar(parent=self)
  54. # tree with layers
  55. self.tree = DataCatalogTree(self, giface=giface)
  56. self.tree.showNotification.connect(self.showNotification)
  57. # infobar for data catalog
  58. delay = 2000
  59. self.infoBar = InfoBar(self)
  60. self.giface.currentMapsetChanged.connect(self.dismissInfobar)
  61. # infobar manager for data catalog
  62. self.infoManager = DataCatalogInfoManager(
  63. infobar=self.infoBar, giface=self.giface
  64. )
  65. self.tree.showImportDataInfo.connect(self.showImportDataInfo)
  66. self.tree.loadingDone.connect(self._loadingDone)
  67. # some layout
  68. self._layout()
  69. # show infobar for first-time user if applicable
  70. if is_first_time_user():
  71. # show data structure infobar for first-time user
  72. wx.CallLater(delay, self.showDataStructureInfo)
  73. # show infobar if last used mapset is not usable
  74. if is_fallback_session():
  75. # get reason why last used mapset is not usable
  76. last_mapset_path = gisenv()["LAST_MAPSET_PATH"]
  77. self.reason_id = get_reason_id_mapset_not_usable(last_mapset_path)
  78. if self.reason_id in ("non-existent", "invalid", "different-owner"):
  79. # show non-standard situation info
  80. wx.CallLater(delay, self.showFallbackSessionInfo)
  81. elif self.reason_id == "locked":
  82. # show info allowing to switch to locked mapset
  83. wx.CallLater(delay, self.showLockedMapsetInfo)
  84. def _layout(self):
  85. """Do layout"""
  86. sizer = wx.BoxSizer(wx.VERTICAL)
  87. sizer.Add(self.toolbar, proportion=0, flag=wx.EXPAND)
  88. sizer.Add(self.infoBar, proportion=0, flag=wx.EXPAND)
  89. sizer.Add(self.tree.GetControl(), proportion=1, flag=wx.EXPAND)
  90. self.SetAutoLayout(True)
  91. self.SetSizer(sizer)
  92. self.Fit()
  93. self.Layout()
  94. def showDataStructureInfo(self):
  95. self.infoManager.ShowDataStructureInfo(self.OnCreateLocation)
  96. def showLockedMapsetInfo(self):
  97. self.infoManager.ShowLockedMapsetInfo(self.OnSwitchToLastUsedMapset)
  98. def showFallbackSessionInfo(self):
  99. self.infoManager.ShowFallbackSessionInfo(self.reason_id)
  100. def showImportDataInfo(self):
  101. self.infoManager.ShowImportDataInfo(
  102. self.OnImportOgrLayers, self.OnImportGdalLayers
  103. )
  104. def LoadItems(self):
  105. """Reload tree - full or lazy - based on user settings"""
  106. self._startLoadingTime = clock()
  107. self.tree.ReloadTreeItems(full=False)
  108. def _loadingDone(self):
  109. """If loading took more time, suggest lazy loading"""
  110. if clock() - self._startLoadingTime > 5 and not self.tree._useLazyLoading():
  111. asked = UserSettings.Get(
  112. group="datacatalog", key="lazyLoading", subkey="asked"
  113. )
  114. if not asked:
  115. wx.CallAfter(
  116. self.infoManager.ShowLazyLoadingOn,
  117. setLazyLoadingOnHandler=self._saveLazyLoadingOnSettings,
  118. doNotAskHandler=self._saveDontAskLazyLoadingSettings,
  119. )
  120. def _saveLazyLoadingOnSettings(self, event):
  121. """Turn on lazy loading in settings"""
  122. UserSettings.Set(
  123. group="datacatalog", key="lazyLoading", subkey="enabled", value=True
  124. )
  125. UserSettings.Set(
  126. group="datacatalog", key="lazyLoading", subkey="asked", value=True
  127. )
  128. self._saveLazyLoadingSettings()
  129. event.Skip()
  130. def _saveDontAskLazyLoadingSettings(self, event):
  131. """Save in settings that decision on lazy loading was done to not ask again"""
  132. UserSettings.Set(
  133. group="datacatalog", key="lazyLoading", subkey="asked", value=True
  134. )
  135. self._saveLazyLoadingSettings()
  136. event.Skip()
  137. def _saveLazyLoadingSettings(self):
  138. dcSettings = {}
  139. UserSettings.ReadSettingsFile(settings=dcSettings)
  140. if "datacatalog" not in dcSettings:
  141. dcSettings["datacatalog"] = UserSettings.Get(group="datacatalog")
  142. dcSettings["datacatalog"]["lazyLoading"] = UserSettings.Get(
  143. group="datacatalog", key="lazyLoading"
  144. )
  145. UserSettings.SaveToFile(dcSettings)
  146. def dismissInfobar(self):
  147. if self.infoBar.IsShown():
  148. self.infoBar.Dismiss()
  149. def OnReloadTree(self, event):
  150. """Reload whole tree"""
  151. self.tree.ReloadTreeItems(full=True)
  152. def OnReloadCurrentMapset(self, event):
  153. """Reload current mapset tree only"""
  154. self.tree.ReloadCurrentMapset()
  155. def OnAddGrassDB(self, event):
  156. """Add grass database"""
  157. dlg = wx.DirDialog(
  158. self, _("Choose GRASS data directory:"), os.getcwd(), wx.DD_DEFAULT_STYLE
  159. )
  160. if dlg.ShowModal() == wx.ID_OK:
  161. grassdatabase = dlg.GetPath()
  162. grassdb_node = self.tree.InsertGrassDb(name=grassdatabase)
  163. # Offer to create a new location
  164. if grassdb_node and not os.listdir(grassdatabase):
  165. message = _("Do you want to create a location?")
  166. dlg2 = wx.MessageDialog(
  167. self,
  168. message=message,
  169. caption=_("Create location?"),
  170. style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION,
  171. )
  172. if dlg2.ShowModal() == wx.ID_YES:
  173. self.tree.CreateLocation(grassdb_node)
  174. dlg2.Destroy()
  175. dlg.Destroy()
  176. def OnCreateMapset(self, event):
  177. """Create new mapset in current location"""
  178. db_node, loc_node, mapset_node = self.tree.GetCurrentDbLocationMapsetNode()
  179. self.tree.CreateMapset(db_node, loc_node)
  180. def OnCreateLocation(self, event):
  181. """Create new location"""
  182. db_node, loc_node, mapset_node = self.tree.GetCurrentDbLocationMapsetNode()
  183. self.tree.CreateLocation(db_node)
  184. def OnDownloadLocation(self, event):
  185. """Download location to current grass database"""
  186. db_node, loc_node, mapset_node = self.tree.GetCurrentDbLocationMapsetNode()
  187. self.tree.DownloadLocation(db_node)
  188. def OnSwitchToLastUsedMapset(self, event):
  189. """Switch to last used mapset"""
  190. last_mapset_path = gisenv()["LAST_MAPSET_PATH"]
  191. grassdb, location, mapset = split_mapset_path(last_mapset_path)
  192. self.tree.SwitchMapset(grassdb, location, mapset)
  193. def OnImportGdalLayers(self, event):
  194. """Convert multiple GDAL layers to GRASS raster map layers"""
  195. from modules.import_export import GdalImportDialog
  196. dlg = GdalImportDialog(parent=self, giface=self.giface)
  197. dlg.CentreOnScreen()
  198. dlg.Show()
  199. def OnImportOgrLayers(self, event):
  200. """Convert multiple OGR layers to GRASS vector map layers"""
  201. from modules.import_export import OgrImportDialog
  202. dlg = OgrImportDialog(parent=self, giface=self.giface)
  203. dlg.CentreOnScreen()
  204. dlg.Show()
  205. def OnLinkGdalLayers(self, event):
  206. """Link multiple GDAL layers to GRASS raster map layers"""
  207. from modules.import_export import GdalImportDialog
  208. dlg = GdalImportDialog(parent=self, giface=self.giface, link=True)
  209. dlg.CentreOnScreen()
  210. dlg.Show()
  211. def OnLinkOgrLayers(self, event):
  212. """Links multiple OGR layers to GRASS vector map layers"""
  213. from modules.import_export import OgrImportDialog
  214. dlg = OgrImportDialog(parent=self, giface=self.giface, link=True)
  215. dlg.CentreOnScreen()
  216. dlg.Show()
  217. def OnRasterOutputFormat(self, event):
  218. """Set raster output format handler"""
  219. from modules.import_export import GdalOutputDialog
  220. dlg = GdalOutputDialog(parent=self, ogr=False)
  221. dlg.CentreOnScreen()
  222. dlg.Show()
  223. def OnVectorOutputFormat(self, event):
  224. """Set vector output format handler"""
  225. from modules.import_export import GdalOutputDialog
  226. dlg = GdalOutputDialog(parent=self, ogr=True)
  227. dlg.CentreOnScreen()
  228. dlg.Show()
  229. def GuiParseCommand(self, cmd):
  230. """Generic handler"""
  231. GUI(parent=self, giface=self.giface).ParseCommand(cmd=[cmd])
  232. def OnMoreOptions(self, event):
  233. self.giface.Help(entry="topic_import")
  234. def SetRestriction(self, restrict):
  235. """Allow editing other mapsets or restrict editing to current mapset"""
  236. self.tree.SetRestriction(restrict)
  237. def Filter(self, text, element=None):
  238. self.tree.Filter(text=text, element=element)
  239. def OnImportMenu(self, event):
  240. """Create popup menu for other import options"""
  241. # create submenu
  242. subMenu = Menu()
  243. subitem = wx.MenuItem(
  244. subMenu, wx.ID_ANY, _("Link external raster data [r.external]")
  245. )
  246. subMenu.AppendItem(subitem)
  247. self.Bind(wx.EVT_MENU, self.OnLinkGdalLayers, subitem)
  248. subitem = wx.MenuItem(
  249. subMenu, wx.ID_ANY, _("Link external vector data [v.external]")
  250. )
  251. subMenu.AppendItem(subitem)
  252. self.Bind(wx.EVT_MENU, self.OnLinkOgrLayers, subitem)
  253. subMenu.AppendSeparator()
  254. subitem = wx.MenuItem(
  255. subMenu, wx.ID_ANY, _("Set raster output format [r.external.out]")
  256. )
  257. subMenu.AppendItem(subitem)
  258. self.Bind(wx.EVT_MENU, self.OnRasterOutputFormat, subitem)
  259. subitem = wx.MenuItem(
  260. subMenu, wx.ID_ANY, _("Set vector output format [v.external.out]")
  261. )
  262. subMenu.AppendItem(subitem)
  263. self.Bind(wx.EVT_MENU, self.OnVectorOutputFormat, subitem)
  264. # create menu
  265. menu = Menu()
  266. item = wx.MenuItem(menu, wx.ID_ANY, _("Unpack GRASS raster map [r.unpack]"))
  267. menu.AppendItem(item)
  268. self.Bind(wx.EVT_MENU, lambda evt: self.GuiParseCommand("r.unpack"), item)
  269. item = wx.MenuItem(menu, wx.ID_ANY, _("Unpack GRASS vector map [v.unpack]"))
  270. menu.AppendItem(item)
  271. self.Bind(wx.EVT_MENU, lambda evt: self.GuiParseCommand("v.unpack"), item)
  272. menu.AppendSeparator()
  273. item = wx.MenuItem(
  274. menu, wx.ID_ANY, _("Create raster map from x,y,z data [r.in.xyz]")
  275. )
  276. menu.AppendItem(item)
  277. self.Bind(wx.EVT_MENU, lambda evt: self.GuiParseCommand("r.in.xyz"), item)
  278. item = wx.MenuItem(
  279. menu, wx.ID_ANY, _("Create vector map from x,y,z data [v.in.ascii]")
  280. )
  281. menu.AppendItem(item)
  282. self.Bind(wx.EVT_MENU, lambda evt: self.GuiParseCommand("v.in.ascii"), item)
  283. menu.AppendSeparator()
  284. menu.AppendMenu(wx.ID_ANY, _("Link external data"), subMenu)
  285. menu.AppendSeparator()
  286. item = wx.MenuItem(menu, wx.ID_ANY, _("More options..."))
  287. menu.AppendItem(item)
  288. self.Bind(wx.EVT_MENU, self.OnMoreOptions, item)
  289. self.PopupMenu(menu)
  290. menu.Destroy()