datacatalog.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. """
  2. @package lmgr::datacatalog
  3. @brief Data catalog
  4. Classes:
  5. - datacatalog::DataCatalog
  6. - datacatalog::DataCatalogTree
  7. (C) 2014 by Tereza Fiedlerova, and the GRASS Development Team
  8. This program is free software under the GNU General Public
  9. License (>=v2). Read the file COPYING that comes with GRASS
  10. for details.
  11. @author Tereza Fiedlerova
  12. """
  13. import os
  14. import sys
  15. import wx
  16. from core.gcmd import RunCommand, GError, GMessage
  17. from core.utils import GetListOfLocations
  18. from core.gthread import gThread
  19. from core.debug import Debug
  20. from gui_core.dialogs import TextEntryDialog
  21. from grass.pydispatch.signal import Signal
  22. import grass.script as grass
  23. class DataCatalog(wx.Panel):
  24. """Data catalog panel"""
  25. def __init__(self, parent, giface=None, id = wx.ID_ANY, title=_("Data catalog"),
  26. name='catalog', **kwargs):
  27. """Panel constructor """
  28. self.showNotification = Signal('DataCatalog.showNotification')
  29. self.parent = parent
  30. self.baseTitle = title
  31. wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
  32. self.SetName("DataCatalog")
  33. Debug.msg(1, "DataCatalog.__init__()")
  34. # tree with layers
  35. self.tree = DataCatalogTree(self)
  36. self.thread = gThread()
  37. self._loaded = False
  38. self.tree.showNotification.connect(self.showNotification)
  39. # some layout
  40. self._layout()
  41. def _layout(self):
  42. """Do layout"""
  43. sizer = wx.BoxSizer(wx.VERTICAL)
  44. sizer.Add(item = self.tree.GetControl(), proportion = 1,
  45. flag = wx.EXPAND)
  46. self.SetAutoLayout(True)
  47. self.SetSizer(sizer)
  48. self.Layout()
  49. def LoadItems(self):
  50. if self._loaded:
  51. return
  52. self.thread.Run(callable=self.tree.InitTreeItems,
  53. ondone=lambda event: self.LoadItemsDone())
  54. def LoadItemsDone(self):
  55. self._loaded = True
  56. class DataCatalogTree(wx.TreeCtrl):
  57. def __init__(self, parent):
  58. """Tree constructor."""
  59. super(DataCatalogTree, self).__init__(parent, id=wx.ID_ANY, style = wx.TR_HIDE_ROOT | wx.TR_EDIT_LABELS |
  60. wx.TR_HAS_BUTTONS | wx.TR_FULL_ROW_HIGHLIGHT | wx.TR_COLUMN_LINES | wx.TR_SINGLE)
  61. self.showNotification = Signal('Tree.showNotification')
  62. self.parent = parent
  63. self.root = self.AddRoot('Catalog') # will not be displayed when we use TR_HIDE_ROOT flag
  64. self._initVariables()
  65. self.MakeBackup()
  66. wx.EVT_TREE_ITEM_RIGHT_CLICK(self, wx.ID_ANY, self.OnRightClick)
  67. wx.EVT_TREE_BEGIN_DRAG(self, wx.ID_ANY, self.OnBeginDrag)
  68. wx.EVT_TREE_END_DRAG(self, wx.ID_ANY, self.OnEndDrag)
  69. self.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick)
  70. self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
  71. self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
  72. wx.EVT_TREE_END_LABEL_EDIT(self, wx.ID_ANY, self.OnEditLabel)
  73. wx.EVT_TREE_BEGIN_LABEL_EDIT(self, wx.ID_ANY, self.OnStartEditLabel)
  74. def _initVariables(self):
  75. """Init variables."""
  76. self.selected_layer = None
  77. self.selected_type = None
  78. self.selected_mapset = None
  79. self.selected_location = None
  80. self.copy_layer = None
  81. self.copy_type = None
  82. self.copy_mapset = None
  83. self.copy_location = None
  84. self.gisdbase = grass.gisenv()['GISDBASE']
  85. self.ctrldown = False
  86. def GetControl(self):
  87. """Returns control itself."""
  88. return self
  89. def DefineItems(self, item0):
  90. """Set selected items."""
  91. self.selected_layer = None
  92. self.selected_type = None
  93. self.selected_mapset = None
  94. self.selected_location = None
  95. items = []
  96. item = item0
  97. while (self.GetItemParent(item)):
  98. items.insert(0,item)
  99. item = self.GetItemParent(item)
  100. self.selected_location = items[0]
  101. length = len(items)
  102. if (length > 1):
  103. self.selected_mapset = items[1]
  104. if (length > 2):
  105. self.selected_type = items[2]
  106. if (length > 3):
  107. self.selected_layer = items[3]
  108. def InitTreeItems(self):
  109. """Add locations, mapsets and layers to the tree."""
  110. locations = GetListOfLocations(self.gisdbase)
  111. first = True
  112. for loc in locations:
  113. location = loc
  114. if first:
  115. self.ChangeEnvironment(location, 'PERMANENT')
  116. first = False
  117. else:
  118. self.ChangeEnvironment(location)
  119. varloc = self.AppendItem(self.root, loc)
  120. #get list of all maps in location
  121. maplist = RunCommand('g.mlist', flags='mt', type='rast,rast3d,vect', mapset='*', quiet=True, read=True)
  122. maplist = maplist.splitlines()
  123. for ml in maplist:
  124. # parse
  125. parts1 = ml.split('/')
  126. parts2 = parts1[1].split('@')
  127. mapset = parts2[1]
  128. mlayer = parts2[0]
  129. ltype = parts1[0]
  130. if self.itemExists(mapset, varloc) == False:
  131. varmapset = self.AppendItem(varloc, mapset)
  132. if (self.GetItemText(varmapset) == mapset):
  133. if (self.itemExists(ltype, varmapset) == False):
  134. vartype = self.AppendItem(varmapset, ltype)
  135. else:
  136. varmapset = self.getItemByName(mapset, varloc)
  137. if (self.itemExists(ltype, varmapset) == False):
  138. vartype = self.AppendItem(varmapset, ltype)
  139. self.AppendItem(vartype, mlayer)
  140. self.RestoreBackup()
  141. Debug.msg(1, "Tree filled")
  142. def getItemByName(self, match, root):
  143. """Return match item from the root."""
  144. item, cookie = self.GetFirstChild(root)
  145. while item.IsOk():
  146. if self.GetItemText(item) == match:
  147. return item
  148. item, cookie = self.GetNextChild(root, cookie)
  149. return None
  150. def itemExists(self, match, root):
  151. """Return true if match item exists in the root item."""
  152. item, cookie = self.GetFirstChild(root)
  153. while item.IsOk():
  154. if self.GetItemText(item) == match:
  155. return True
  156. item, cookie = self.GetNextChild(root, cookie)
  157. return False
  158. def UpdateTree(self):
  159. """Update whole tree."""
  160. self.DeleteAllItems()
  161. self.root = self.AddRoot('Tree')
  162. self.AddTreeItems()
  163. label = "Tree updated."
  164. self.showNotification.emit(message=label)
  165. def OnSelChanged(self, event):
  166. self.selected_layer = None
  167. def OnRightClick(self, event):
  168. """Display popup menu."""
  169. self.DefineItems(event.GetItem())
  170. if(self.selected_layer):
  171. self._popupMenuLayer()
  172. elif(self.selected_mapset and self.selected_type==None):
  173. self._popupMenuMapset()
  174. def OnDoubleClick(self, event):
  175. """Rename layer"""
  176. Debug.msg(1, "Double CLICK")
  177. def OnCopy(self, event):
  178. """Copy layer or mapset (just save it temporarily, copying is done by paste)"""
  179. self.copy_layer = self.selected_layer
  180. self.copy_type = self.selected_type
  181. self.copy_mapset = self.selected_mapset
  182. self.copy_location = self.selected_location
  183. label = "Layer "+self.GetItemText(self.copy_layer)+" copied to clipboard. You can paste it to selected mapset."
  184. self.showNotification.emit(message=label)
  185. def OnRename(self, event):
  186. """Rename levent with dialog"""
  187. if (self.selected_layer):
  188. self.old_name = self.GetItemText(self.selected_layer)
  189. self._textDialog(_('New name'), _('Rename map'), self.old_name)
  190. self.rename()
  191. def OnStartEditLabel(self, event):
  192. """Start label editing"""
  193. item = event.GetItem()
  194. self.DefineItems(item)
  195. Debug.msg(1, "Start label edit "+self.GetItemText(item))
  196. label = _("Editing") + " " + self.GetItemText(item)
  197. self.showNotification.emit(message=label)
  198. if (self.selected_layer == None):
  199. event.Veto()
  200. def OnEditLabel(self, event):
  201. """End label editing"""
  202. if (self.selected_layer):
  203. item = event.GetItem()
  204. self.old_name = self.GetItemText(item)
  205. Debug.msg(1, "End label edit "+self.old_name)
  206. wx.CallAfter(self.afterEdit, self, item)
  207. def afterEdit(pro, self, item):
  208. self.new_name = self.GetItemText(item)
  209. self.rename()
  210. def rename(self):
  211. """Rename layer"""
  212. if (self.selected_layer):
  213. string = self.old_name+','+self.new_name
  214. self.ChangeEnvironment(self.GetItemText(self.selected_location), self.GetItemText(self.selected_mapset))
  215. renamed = 0
  216. label = _("Renaming") + " " + string + " ..."
  217. self.showNotification.emit(message=label)
  218. if (self.GetItemText(self.selected_type)=='vect'):
  219. renamed = RunCommand('g.rename', vect=string)
  220. elif (self.GetItemText(self.selected_type)=='rast'):
  221. renamed = RunCommand('g.rename', rast=string)
  222. else:
  223. renamed = RunCommand('g.rename', rast3d=string)
  224. if (renamed==0):
  225. self.SetItemText(self.selected_layer,self.new_name)
  226. label = "g.rename "+self.GetItemText(self.selected_type)+"="+string+" -- completed"
  227. self.showNotification.emit(message=label)
  228. Debug.msg(1,"LAYER RENAMED TO: "+self.new_name)
  229. self.RestoreBackup()
  230. def OnPaste(self, event):
  231. """Paste layer or mapset"""
  232. # copying between mapsets of one location
  233. if (self.copy_layer == None):
  234. return
  235. if (self.selected_location == self.copy_location and self.selected_mapset):
  236. if (self.selected_type != None):
  237. if (self.GetItemText(self.copy_type) != self.GetItemText(self.selected_type)): # copy raster to vector or vice versa
  238. GError(_("Failed to copy layer: invalid type."), parent = self)
  239. return
  240. self._textDialog(_('New name'), _('Copy map'), self.GetItemText(self.copy_layer)+'_copy')
  241. if (self.GetItemText(self.copy_layer) == self.new_name):
  242. GMessage(_("Layer was not copied: new layer has the same name"), parent=self)
  243. return
  244. string = self.GetItemText(self.copy_layer)+'@'+self.GetItemText(self.copy_mapset)+','+self.new_name
  245. self.ChangeEnvironment(self.GetItemText(self.selected_location), self.GetItemText(self.selected_mapset))
  246. pasted = 0
  247. type = None
  248. label = _("Copying") + " " + string + " ..."
  249. self.showNotification.emit(message=label)
  250. if (self.GetItemText(self.copy_type)=='vect'):
  251. pasted = RunCommand('g.copy', vect=string)
  252. node = 'vect'
  253. elif (self.GetItemText(self.copy_type)=='rast'):
  254. pasted = RunCommand('g.copy', rast=string)
  255. node = 'rast'
  256. else:
  257. pasted = RunCommand('g.copy', rast3d=string)
  258. node = 'rast3d'
  259. if (pasted==0):
  260. if (self.selected_type == None):
  261. self.selected_type = self.getItemByName(node, self.selected_mapset)
  262. self.AppendItem(self.selected_type,self.new_name)
  263. self.SortChildren(self.selected_type)
  264. Debug.msg(1,"COPIED TO: "+self.new_name)
  265. label = "g.copy "+self.GetItemText(self.copy_type)+"="+string+" -- completed" # generate this message (command) automatically?
  266. self.showNotification.emit(message=label)
  267. else:
  268. GError(_("Failed to copy layer: action is allowed only within the same location."),
  269. parent=self)
  270. self.RestoreBackup()
  271. def OnDelete(self, event):
  272. """Delete layer or mapset"""
  273. if (self.selected_layer):
  274. string = self.GetItemText(self.selected_layer)
  275. self.ChangeEnvironment(self.GetItemText(self.selected_location), self.GetItemText(self.selected_mapset))
  276. removed = 0
  277. if (self._confirmDialog(_('Do you really want to delete layer') +string+'?', _('Delete map')) == wx.ID_YES):
  278. label = _("Deleting") + " " + string + " ..."
  279. self.showNotification.emit(message=label)
  280. if (self.GetItemText(self.selected_type)=='vect'):
  281. removed = RunCommand('g.remove', vect=string)
  282. elif (self.GetItemText(self.selected_type)=='rast'):
  283. removed = RunCommand('g.remove', rast=string)
  284. else:
  285. removed = RunCommand('g.remove', rast3d=string)
  286. if (removed==0):
  287. self.Delete(self.selected_layer)
  288. Debug.msg(1,"LAYER "+string+" DELETED")
  289. label = "g.remove "+self.GetItemText(self.selected_type)+"="+string+" -- completed" # generate this message (command) automatically?
  290. self.showNotification.emit(message=label)
  291. self.RestoreBackup()
  292. def OnDisplayLayer(self, event):
  293. """Display layer in current graphics view"""
  294. layerName = []
  295. if (self.GetItemText(self.selected_location) == self.glocation and self.selected_mapset):
  296. string = self.GetItemText(self.selected_layer)+'@'+self.GetItemText(self.selected_mapset)
  297. layerName.append(string)
  298. label = _("Displaying") + " " + string + " ..."
  299. self.showNotification.emit(message=label)
  300. label = "d."+self.GetItemText(self.selected_type)+" --q map="+string+" -- completed. Go to Map layers for further operations."
  301. if (self.GetItemText(self.selected_type)=='vect'):
  302. self.parent.parent.AddMaps(layerName, 'vect', True)
  303. elif (self.GetItemText(self.selected_type)=='rast'):
  304. self.parent.parent.AddMaps(layerName, 'rast', True)
  305. else:
  306. self.parent.parent.AddMaps(layerName, 'rast3d', True)
  307. label = "d.rast --q map="+string+" -- completed. Go to 'Map layers' for further operations." # generate this message (command) automatically?
  308. self.showNotification.emit(message=label)
  309. Debug.msg(1,"LAYER "+self.GetItemText(self.selected_layer)+" DISPLAYED")
  310. else:
  311. GError(_("Failed to display layer: not in current mapset or invalid layer"),
  312. parent = self)
  313. def OnBeginDrag(self, event):
  314. """Just copy necessary data"""
  315. if (self.ctrldown):
  316. #cursor = wx.StockCursor(wx.CURSOR_HAND)
  317. #self.SetCursor(cursor)
  318. event.Allow()
  319. self.DefineItems(event.GetItem())
  320. self.OnCopy(event)
  321. Debug.msg(1,"DRAG")
  322. else:
  323. event.Veto()
  324. Debug.msg(1,"DRAGGING without ctrl key does nothing")
  325. def OnEndDrag(self, event):
  326. """Copy layer into target"""
  327. #cursor = wx.StockCursor(wx.CURSOR_ARROW)
  328. #self.SetCursor(cursor)
  329. if (event.GetItem()):
  330. self.DefineItems(event.GetItem())
  331. if (self.selected_location == self.copy_location and self.selected_mapset):
  332. event.Allow()
  333. self.OnPaste(event)
  334. self.ctrldown = False
  335. #cursor = wx.StockCursor(wx.CURSOR_DEFAULT)
  336. #self.SetCursor(cursor) # TODO: change cursor while dragging and then back, this is not working
  337. Debug.msg(1,"DROP DONE")
  338. else:
  339. event.Veto()
  340. def OnKeyDown(self, event):
  341. """Set key event and check if control key is down"""
  342. keycode = event.GetKeyCode()
  343. if keycode == wx.WXK_CONTROL:
  344. self.ctrldown = True
  345. Debug.msg(1,"CONTROL ON")
  346. def OnKeyUp(self, event):
  347. """Check if control key is up"""
  348. keycode = event.GetKeyCode()
  349. if keycode == wx.WXK_CONTROL:
  350. self.ctrldown = False
  351. Debug.msg(1,"CONTROL OFF")
  352. def _textDialog(self, message, title, value):
  353. """Dialog for simple text entry"""
  354. dlg = TextEntryDialog(self, message, title)
  355. dlg.SetValue(value)
  356. res = dlg.ShowModal()
  357. self.new_name = dlg.GetValue()
  358. dlg.Destroy()
  359. def _confirmDialog(self, question, title):
  360. """Confirm dialog"""
  361. dlg = wx.MessageDialog(self, question, title, wx.YES_NO)
  362. res = dlg.ShowModal()
  363. dlg.Destroy()
  364. return res
  365. def _popupMenuLayer(self):
  366. """Create popup menu for layers"""
  367. menu = wx.Menu()
  368. item = wx.MenuItem(menu, wx.NewId(), _("&Copy"))
  369. menu.AppendItem(item)
  370. self.Bind(wx.EVT_MENU, self.OnCopy, item)
  371. item = wx.MenuItem(menu, wx.NewId(), _("&Paste"))
  372. menu.AppendItem(item)
  373. self.Bind(wx.EVT_MENU, self.OnPaste, item)
  374. item = wx.MenuItem(menu, wx.NewId(), _("&Delete"))
  375. menu.AppendItem(item)
  376. self.Bind(wx.EVT_MENU, self.OnDelete, item)
  377. item = wx.MenuItem(menu, wx.NewId(), _("&Rename"))
  378. menu.AppendItem(item)
  379. self.Bind(wx.EVT_MENU, self.OnRename, item)
  380. item = wx.MenuItem(menu, wx.NewId(), _("&Display layer"))
  381. menu.AppendItem(item)
  382. self.Bind(wx.EVT_MENU, self.OnDisplayLayer, item)
  383. self.PopupMenu(menu)
  384. menu.Destroy()
  385. def _popupMenuMapset(self):
  386. """Create popup menu for mapsets"""
  387. menu = wx.Menu()
  388. item = wx.MenuItem(menu, wx.NewId(), _("&Paste"))
  389. menu.AppendItem(item)
  390. self.Bind(wx.EVT_MENU, self.OnPaste, item)
  391. self.PopupMenu(menu)
  392. menu.Destroy()
  393. def MakeBackup(self):
  394. """Make backup for case of change"""
  395. self.glocation = grass.gisenv()['LOCATION_NAME']
  396. self.gmapset = grass.gisenv()['MAPSET']
  397. def RestoreBackup(self):
  398. """Restore backup"""
  399. stringl = 'LOCATION_NAME='+self.glocation
  400. RunCommand('g.gisenv', set=stringl)
  401. stringm = 'MAPSET='+self.gmapset
  402. RunCommand('g.gisenv', set=stringm)
  403. def ChangeEnvironment(self, location, mapset=None):
  404. """Change gisenv variables -> location, mapset"""
  405. stringl = 'LOCATION_NAME='+location
  406. RunCommand('g.gisenv', set=stringl)
  407. if mapset:
  408. stringm = 'MAPSET='+mapset
  409. RunCommand('g.gisenv', set=stringm)