datacatalog.py 21 KB

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