catalog.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  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, DataCatalogSearch
  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. # search
  55. self.search = DataCatalogSearch(parent=self, filter_function=self.Filter)
  56. # tree with layers
  57. self.tree = DataCatalogTree(self, giface=giface)
  58. self.tree.showNotification.connect(self.showNotification)
  59. # infobar for data catalog
  60. delay = 2000
  61. self.infoBar = InfoBar(self)
  62. self.giface.currentMapsetChanged.connect(self.dismissInfobar)
  63. # infobar manager for data catalog
  64. self.infoManager = DataCatalogInfoManager(
  65. infobar=self.infoBar, giface=self.giface
  66. )
  67. self.tree.showImportDataInfo.connect(self.showImportDataInfo)
  68. self.tree.loadingDone.connect(self._loadingDone)
  69. # some layout
  70. self._layout()
  71. # show infobar for first-time user if applicable
  72. if is_first_time_user():
  73. # show data structure infobar for first-time user
  74. wx.CallLater(delay, self.showDataStructureInfo)
  75. # show infobar if last used mapset is not usable
  76. if is_fallback_session():
  77. # get reason why last used mapset is not usable
  78. last_mapset_path = gisenv()["LAST_MAPSET_PATH"]
  79. self.reason_id = get_reason_id_mapset_not_usable(last_mapset_path)
  80. if self.reason_id in ("non-existent", "invalid", "different-owner"):
  81. # show non-standard situation info
  82. wx.CallLater(delay, self.showFallbackSessionInfo)
  83. elif self.reason_id == "locked":
  84. # show info allowing to switch to locked mapset
  85. wx.CallLater(delay, self.showLockedMapsetInfo)
  86. def _layout(self):
  87. """Do layout"""
  88. sizer = wx.BoxSizer(wx.VERTICAL)
  89. sizer.Add(self.toolbar, proportion=0, flag=wx.EXPAND)
  90. sizer.Add(
  91. self.search,
  92. proportion=0,
  93. flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
  94. border=5,
  95. )
  96. sizer.Add(self.infoBar, proportion=0, flag=wx.EXPAND)
  97. sizer.Add(self.tree.GetControl(), proportion=1, flag=wx.EXPAND)
  98. self.SetAutoLayout(True)
  99. self.SetSizer(sizer)
  100. self.Fit()
  101. self.Layout()
  102. def showDataStructureInfo(self):
  103. self.infoManager.ShowDataStructureInfo(self.OnCreateLocation)
  104. def showLockedMapsetInfo(self):
  105. self.infoManager.ShowLockedMapsetInfo(self.OnSwitchToLastUsedMapset)
  106. def showFallbackSessionInfo(self):
  107. self.infoManager.ShowFallbackSessionInfo(self.reason_id)
  108. def showImportDataInfo(self):
  109. self.infoManager.ShowImportDataInfo(
  110. self.OnImportOgrLayers, self.OnImportGdalLayers
  111. )
  112. def LoadItems(self):
  113. """Reload tree - full or lazy - based on user settings"""
  114. self._startLoadingTime = clock()
  115. self.tree.ReloadTreeItems(full=False)
  116. def _loadingDone(self):
  117. """If loading took more time, suggest lazy loading"""
  118. if clock() - self._startLoadingTime > 5 and not self.tree._useLazyLoading():
  119. asked = UserSettings.Get(
  120. group="datacatalog", key="lazyLoading", subkey="asked"
  121. )
  122. if not asked:
  123. wx.CallAfter(
  124. self.infoManager.ShowLazyLoadingOn,
  125. setLazyLoadingOnHandler=self._saveLazyLoadingOnSettings,
  126. doNotAskHandler=self._saveDontAskLazyLoadingSettings,
  127. )
  128. def _saveLazyLoadingOnSettings(self, event):
  129. """Turn on lazy loading in settings"""
  130. UserSettings.Set(
  131. group="datacatalog", key="lazyLoading", subkey="enabled", value=True
  132. )
  133. UserSettings.Set(
  134. group="datacatalog", key="lazyLoading", subkey="asked", value=True
  135. )
  136. self._saveLazyLoadingSettings()
  137. event.Skip()
  138. def _saveDontAskLazyLoadingSettings(self, event):
  139. """Save in settings that decision on lazy loading was done to not ask again"""
  140. UserSettings.Set(
  141. group="datacatalog", key="lazyLoading", subkey="asked", value=True
  142. )
  143. self._saveLazyLoadingSettings()
  144. event.Skip()
  145. def _saveLazyLoadingSettings(self):
  146. dcSettings = {}
  147. UserSettings.ReadSettingsFile(settings=dcSettings)
  148. if "datacatalog" not in dcSettings:
  149. dcSettings["datacatalog"] = UserSettings.Get(group="datacatalog")
  150. dcSettings["datacatalog"]["lazyLoading"] = UserSettings.Get(
  151. group="datacatalog", key="lazyLoading"
  152. )
  153. UserSettings.SaveToFile(dcSettings)
  154. def dismissInfobar(self):
  155. if self.infoBar.IsShown():
  156. self.infoBar.Dismiss()
  157. def OnReloadTree(self, event):
  158. """Reload whole tree"""
  159. self.tree.ReloadTreeItems(full=True)
  160. def OnReloadCurrentMapset(self, event):
  161. """Reload current mapset tree only"""
  162. self.tree.ReloadCurrentMapset()
  163. def OnAddGrassDB(self, event):
  164. """Add grass database"""
  165. dlg = wx.DirDialog(
  166. self, _("Choose GRASS data directory:"), os.getcwd(), wx.DD_DEFAULT_STYLE
  167. )
  168. if dlg.ShowModal() == wx.ID_OK:
  169. grassdatabase = dlg.GetPath()
  170. grassdb_node = self.tree.InsertGrassDb(name=grassdatabase)
  171. # Offer to create a new location
  172. if grassdb_node and not os.listdir(grassdatabase):
  173. message = _("Do you want to create a location?")
  174. dlg2 = wx.MessageDialog(
  175. self,
  176. message=message,
  177. caption=_("Create location?"),
  178. style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION,
  179. )
  180. if dlg2.ShowModal() == wx.ID_YES:
  181. self.tree.CreateLocation(grassdb_node)
  182. dlg2.Destroy()
  183. dlg.Destroy()
  184. def OnCreateMapset(self, event):
  185. """Create new mapset in current location"""
  186. db_node, loc_node, mapset_node = self.tree.GetCurrentDbLocationMapsetNode()
  187. self.tree.CreateMapset(db_node, loc_node)
  188. def OnCreateLocation(self, event):
  189. """Create new location"""
  190. db_node, loc_node, mapset_node = self.tree.GetCurrentDbLocationMapsetNode()
  191. self.tree.CreateLocation(db_node)
  192. def OnDownloadLocation(self, event):
  193. """Download location to current grass database"""
  194. db_node, loc_node, mapset_node = self.tree.GetCurrentDbLocationMapsetNode()
  195. self.tree.DownloadLocation(db_node)
  196. def OnSwitchToLastUsedMapset(self, event):
  197. """Switch to last used mapset"""
  198. last_mapset_path = gisenv()["LAST_MAPSET_PATH"]
  199. grassdb, location, mapset = split_mapset_path(last_mapset_path)
  200. self.tree.SwitchMapset(grassdb, location, mapset)
  201. def OnImportGdalLayers(self, event):
  202. """Convert multiple GDAL layers to GRASS raster map layers"""
  203. from modules.import_export import GdalImportDialog
  204. dlg = GdalImportDialog(parent=self, giface=self.giface)
  205. dlg.CentreOnScreen()
  206. dlg.Show()
  207. def OnImportOgrLayers(self, event):
  208. """Convert multiple OGR layers to GRASS vector map layers"""
  209. from modules.import_export import OgrImportDialog
  210. dlg = OgrImportDialog(parent=self, giface=self.giface)
  211. dlg.CentreOnScreen()
  212. dlg.Show()
  213. def OnLinkGdalLayers(self, event):
  214. """Link multiple GDAL layers to GRASS raster map layers"""
  215. from modules.import_export import GdalImportDialog
  216. dlg = GdalImportDialog(parent=self, giface=self.giface, link=True)
  217. dlg.CentreOnScreen()
  218. dlg.Show()
  219. def OnLinkOgrLayers(self, event):
  220. """Links multiple OGR layers to GRASS vector map layers"""
  221. from modules.import_export import OgrImportDialog
  222. dlg = OgrImportDialog(parent=self, giface=self.giface, link=True)
  223. dlg.CentreOnScreen()
  224. dlg.Show()
  225. def OnRasterOutputFormat(self, event):
  226. """Set raster output format handler"""
  227. from modules.import_export import GdalOutputDialog
  228. dlg = GdalOutputDialog(parent=self, ogr=False)
  229. dlg.CentreOnScreen()
  230. dlg.Show()
  231. def OnVectorOutputFormat(self, event):
  232. """Set vector output format handler"""
  233. from modules.import_export import GdalOutputDialog
  234. dlg = GdalOutputDialog(parent=self, ogr=True)
  235. dlg.CentreOnScreen()
  236. dlg.Show()
  237. def GuiParseCommand(self, cmd):
  238. """Generic handler"""
  239. GUI(parent=self, giface=self.giface).ParseCommand(cmd=[cmd])
  240. def OnMoreOptions(self, event):
  241. self.giface.Help(entry="topic_import")
  242. def SetRestriction(self, restrict):
  243. """Allow editing other mapsets or restrict editing to current mapset"""
  244. self.tree.SetRestriction(restrict)
  245. def Filter(self, text, element=None):
  246. self.tree.Filter(text=text, element=element)
  247. def OnImportMenu(self, event):
  248. """Create popup menu for other import options"""
  249. # create submenu
  250. subMenu = Menu()
  251. subitem = wx.MenuItem(
  252. subMenu, wx.ID_ANY, _("Link external raster data [r.external]")
  253. )
  254. subMenu.AppendItem(subitem)
  255. self.Bind(wx.EVT_MENU, self.OnLinkGdalLayers, subitem)
  256. subitem = wx.MenuItem(
  257. subMenu, wx.ID_ANY, _("Link external vector data [v.external]")
  258. )
  259. subMenu.AppendItem(subitem)
  260. self.Bind(wx.EVT_MENU, self.OnLinkOgrLayers, subitem)
  261. subMenu.AppendSeparator()
  262. subitem = wx.MenuItem(
  263. subMenu, wx.ID_ANY, _("Set raster output format [r.external.out]")
  264. )
  265. subMenu.AppendItem(subitem)
  266. self.Bind(wx.EVT_MENU, self.OnRasterOutputFormat, subitem)
  267. subitem = wx.MenuItem(
  268. subMenu, wx.ID_ANY, _("Set vector output format [v.external.out]")
  269. )
  270. subMenu.AppendItem(subitem)
  271. self.Bind(wx.EVT_MENU, self.OnVectorOutputFormat, subitem)
  272. # create menu
  273. menu = Menu()
  274. item = wx.MenuItem(menu, wx.ID_ANY, _("Unpack GRASS raster map [r.unpack]"))
  275. menu.AppendItem(item)
  276. self.Bind(wx.EVT_MENU, lambda evt: self.GuiParseCommand("r.unpack"), item)
  277. item = wx.MenuItem(menu, wx.ID_ANY, _("Unpack GRASS vector map [v.unpack]"))
  278. menu.AppendItem(item)
  279. self.Bind(wx.EVT_MENU, lambda evt: self.GuiParseCommand("v.unpack"), item)
  280. menu.AppendSeparator()
  281. item = wx.MenuItem(
  282. menu, wx.ID_ANY, _("Create raster map from x,y,z data [r.in.xyz]")
  283. )
  284. menu.AppendItem(item)
  285. self.Bind(wx.EVT_MENU, lambda evt: self.GuiParseCommand("r.in.xyz"), item)
  286. item = wx.MenuItem(
  287. menu, wx.ID_ANY, _("Create vector map from x,y,z data [v.in.ascii]")
  288. )
  289. menu.AppendItem(item)
  290. self.Bind(wx.EVT_MENU, lambda evt: self.GuiParseCommand("v.in.ascii"), item)
  291. menu.AppendSeparator()
  292. menu.AppendMenu(wx.ID_ANY, _("Link external data"), subMenu)
  293. menu.AppendSeparator()
  294. item = wx.MenuItem(menu, wx.ID_ANY, _("More options..."))
  295. menu.AppendItem(item)
  296. self.Bind(wx.EVT_MENU, self.OnMoreOptions, item)
  297. self.PopupMenu(menu)
  298. menu.Destroy()