123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610 |
- """
- @package lmgr::datacatalog
- @brief Data catalog
- Classes:
- - datacatalog::DataCatalog
- - datacatalog::LocationMapTree
- - datacatalog::DataCatalogTree
- @todo:
- - use gui_core/treeview.py
- (C) 2014 by Tereza Fiedlerova, and the GRASS Development Team
- This program is free software under the GNU General Public
- License (>=v2). Read the file COPYING that comes with GRASS
- for details.
- @author Tereza Fiedlerova
- """
- import os
- import sys
- import wx
- import wx.gizmos as gizmos
- from core.gcmd import RunCommand, GError, GMessage
- from core.utils import GetListOfLocations, ListOfMapsets
- from core.gthread import gThread
- from core.debug import Debug
- from gui_core.dialogs import TextEntryDialog
- from grass.pydispatch.signal import Signal
- import grass.script as grass
- class DataCatalog(wx.Panel):
- """Data catalog panel"""
- def __init__(self, parent, giface=None, id = wx.ID_ANY, title=_("Data catalog"),
- name='catalog', **kwargs):
- """Panel constructor """
- self.showNotification = Signal('DataCatalog.showNotification')
- self.parent = parent
- self.baseTitle = title
- wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
- self.SetName("DataCatalog")
-
- Debug.msg(1, "DataCatalog.__init__()")
-
- # tree with layers
- self.tree = DataCatalogTree(self)
- self.thread = gThread()
- self._loaded = False
- self.tree.showNotification.connect(self.showNotification)
- # some layout
- self._layout()
-
- def _layout(self):
- """Do layout"""
- sizer = wx.BoxSizer(wx.VERTICAL)
- sizer.Add(item = self.tree.GetControl(), proportion = 1,
- flag = wx.EXPAND)
-
- self.SetAutoLayout(True)
- self.SetSizer(sizer)
-
- self.Layout()
- def LoadItems(self):
- if self._loaded:
- return
-
- self.thread.Run(callable=self.tree.InitTreeItems,
- ondone=lambda event: self.LoadItemsDone())
- def LoadItemsDone(self):
- self._loaded = True
- self.tree.ExpandCurrentLocation()
- class LocationMapTree(wx.TreeCtrl):
- def __init__(self, parent, style=wx.TR_HIDE_ROOT | wx.TR_EDIT_LABELS |
- wx.TR_HAS_BUTTONS | wx.TR_FULL_ROW_HIGHLIGHT | wx.TR_COLUMN_LINES | wx.TR_SINGLE):
- """Location Map Tree constructor."""
- super(LocationMapTree, self).__init__(parent, id=wx.ID_ANY, style=style)
- self.showNotification = Signal('Tree.showNotification')
- self.parent = parent
- self.root = self.AddRoot('Catalog') # will not be displayed when we use TR_HIDE_ROOT flag
-
- self._initVariables()
- self.MakeBackup()
- wx.EVT_TREE_ITEM_RIGHT_CLICK(self, wx.ID_ANY, self.OnRightClick)
-
- self.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick)
- self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
- self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
- def _initTreeItems(self, locations = [], mapsets = []):
- """Add locations, mapsets and layers to the tree."""
- if not locations:
- locations = GetListOfLocations(self.gisdbase)
- if not mapsets:
- mapsets = ['*']
-
- first = True
- for loc in locations:
- location = loc
- if first:
- self.ChangeEnvironment(location, 'PERMANENT')
- first = False
- else:
- self.ChangeEnvironment(location)
-
- varloc = self.AppendItem(self.root, loc)
- # add all mapsets
- mapsets = ListOfMapsets()
- if mapsets:
- for mapset in mapsets:
- self.AppendItem(varloc, mapset)
- else:
- self.AppendItem(varloc, _("No mapsets readable"))
- continue
- # get list of all maps in location
- maplist = RunCommand('g.list', flags='mt', type='raster,raster_3d,vector', mapset=','.join(mapsets),
- quiet=True, read=True)
- maplist = maplist.splitlines()
- for ml in maplist:
- # parse
- parts1 = ml.split('/')
- parts2 = parts1[1].split('@')
- mapset = parts2[1]
- mlayer = parts2[0]
- ltype = parts1[0]
- # add mapset
- if self.itemExists(mapset, varloc) == False:
- varmapset = self.AppendItem(varloc, mapset)
- else:
- varmapset = self.getItemByName(mapset, varloc)
- # add type node if not exists
- if self.itemExists(ltype, varmapset) == False:
- vartype = self.AppendItem(varmapset, ltype)
-
- self.AppendItem(vartype, mlayer)
-
- self.RestoreBackup()
- Debug.msg(1, "Tree filled")
- def InitTreeItems(self):
- """Create popup menu for layers"""
- raise NotImplementedError()
- def _popupMenuLayer(self):
- """Create popup menu for layers"""
- raise NotImplementedError()
- def _popupMenuMapset(self):
- """Create popup menu for mapsets"""
- raise NotImplementedError()
- def _initVariables(self):
- """Init variables."""
- self.selected_layer = None
- self.selected_type = None
- self.selected_mapset = None
- self.selected_location = None
-
- self.gisdbase = grass.gisenv()['GISDBASE']
- self.ctrldown = False
- def GetControl(self):
- """Returns control itself."""
- return self
-
- def DefineItems(self, item0):
- """Set selected items."""
- self.selected_layer = None
- self.selected_type = None
- self.selected_mapset = None
- self.selected_location = None
- items = []
- item = item0
- while (self.GetItemParent(item)):
- items.insert(0,item)
- item = self.GetItemParent(item)
-
- self.selected_location = items[0]
- length = len(items)
- if (length > 1):
- self.selected_mapset = items[1]
- if (length > 2):
- self.selected_type = items[2]
- if (length > 3):
- self.selected_layer = items[3]
-
- def getItemByName(self, match, root):
- """Return match item from the root."""
- item, cookie = self.GetFirstChild(root)
- while item.IsOk():
- if self.GetItemText(item) == match:
- return item
- item, cookie = self.GetNextChild(root, cookie)
- return None
-
- def itemExists(self, match, root):
- """Return true if match item exists in the root item."""
- item, cookie = self.GetFirstChild(root)
- while item.IsOk():
- if self.GetItemText(item) == match:
- return True
- item, cookie = self.GetNextChild(root, cookie)
- return False
-
- def UpdateTree(self):
- """Update whole tree."""
- self.DeleteAllItems()
- self.root = self.AddRoot('Tree')
- self.AddTreeItems()
- label = "Tree updated."
- self.showNotification.emit(message=label)
-
- def OnSelChanged(self, event):
- self.selected_layer = None
-
- def OnRightClick(self, event):
- """Display popup menu."""
- self.DefineItems(event.GetItem())
- if(self.selected_layer):
- self._popupMenuLayer()
- elif(self.selected_mapset and self.selected_type==None):
- self._popupMenuMapset()
-
- def OnDoubleClick(self, event):
- """Double click"""
- Debug.msg(1, "Double CLICK")
-
- def OnKeyDown(self, event):
- """Set key event and check if control key is down"""
- keycode = event.GetKeyCode()
- if keycode == wx.WXK_CONTROL:
- self.ctrldown = True
- Debug.msg(1,"CONTROL ON")
- def OnKeyUp(self, event):
- """Check if control key is up"""
- keycode = event.GetKeyCode()
- if keycode == wx.WXK_CONTROL:
- self.ctrldown = False
- Debug.msg(1,"CONTROL OFF")
- def MakeBackup(self):
- """Make backup for case of change"""
- gisenv = grass.gisenv()
- self.glocation = gisenv['LOCATION_NAME']
- self.gmapset = gisenv['MAPSET']
- def RestoreBackup(self):
- """Restore backup"""
- stringl = 'LOCATION_NAME='+self.glocation
- RunCommand('g.gisenv', set=stringl)
- stringm = 'MAPSET='+self.gmapset
- RunCommand('g.gisenv', set=stringm)
-
- def ChangeEnvironment(self, location, mapset=None):
- """Change gisenv variables -> location, mapset"""
- stringl = 'LOCATION_NAME='+location
- RunCommand('g.gisenv', set=stringl)
- if mapset:
- stringm = 'MAPSET='+mapset
- RunCommand('g.gisenv', set=stringm)
- def ExpandCurrentLocation(self):
- """Expand current location"""
- location = grass.gisenv()['LOCATION_NAME']
- item = self.getItemByName(location, self.root)
- if item is not None:
- self.SelectItem(item)
- self.ExpandAllChildren(item)
- self.EnsureVisible(item)
- else:
- Debug.msg(1, "Location <%s> not found" % location)
- class DataCatalogTree(LocationMapTree):
- def __init__(self, parent):
- """Data Catalog Tree constructor."""
- super(DataCatalogTree, self).__init__(parent)
-
- self._initVariablesCatalog()
- wx.EVT_TREE_BEGIN_DRAG(self, wx.ID_ANY, self.OnBeginDrag)
- wx.EVT_TREE_END_DRAG(self, wx.ID_ANY, self.OnEndDrag)
-
- wx.EVT_TREE_END_LABEL_EDIT(self, wx.ID_ANY, self.OnEditLabel)
- wx.EVT_TREE_BEGIN_LABEL_EDIT(self, wx.ID_ANY, self.OnStartEditLabel)
-
- def _initVariablesCatalog(self):
- """Init variables."""
- self.copy_layer = None
- self.copy_type = None
- self.copy_mapset = None
- self.copy_location = None
- def InitTreeItems(self):
- """Add locations, mapsets and layers to the tree."""
- self._initTreeItems()
-
- def OnCopy(self, event):
- """Copy layer or mapset (just save it temporarily, copying is done by paste)"""
- self.copy_layer = self.selected_layer
- self.copy_type = self.selected_type
- self.copy_mapset = self.selected_mapset
- self.copy_location = self.selected_location
- label = "Layer "+self.GetItemText(self.copy_layer)+" copied to clipboard. You can paste it to selected mapset."
- self.showNotification.emit(message=label)
-
- def OnRename(self, event):
- """Rename levent with dialog"""
- if (self.selected_layer):
- self.old_name = self.GetItemText(self.selected_layer)
- self.new_name = self._getUserEntry(_('New name'), _('Rename map'), self.old_name)
- self.rename()
-
- def OnStartEditLabel(self, event):
- """Start label editing"""
- item = event.GetItem()
- self.DefineItems(item)
- Debug.msg(1, "Start label edit "+self.GetItemText(item))
- label = _("Editing") + " " + self.GetItemText(item)
- self.showNotification.emit(message=label)
- if (self.selected_layer == None):
- event.Veto()
-
- def OnEditLabel(self, event):
- """End label editing"""
- if (self.selected_layer):
- item = event.GetItem()
- self.old_name = self.GetItemText(item)
- Debug.msg(1, "End label edit "+self.old_name)
- wx.CallAfter(self.afterEdit, self, item)
-
- def afterEdit(pro, self, item):
- self.new_name = self.GetItemText(item)
- self.rename()
-
- def rename(self):
- """Rename layer"""
- if self.selected_layer and self.new_name:
- string = self.old_name+','+self.new_name
- self.ChangeEnvironment(self.GetItemText(self.selected_location), self.GetItemText(self.selected_mapset))
- renamed = 0
- label = _("Renaming") + " " + string + " ..."
- self.showNotification.emit(message=label)
- if (self.GetItemText(self.selected_type)=='vector'):
- renamed = RunCommand('g.rename', vector=string)
- elif (self.GetItemText(self.selected_type)=='raster'):
- renamed = RunCommand('g.rename', raster=string)
- else:
- renamed = RunCommand('g.rename', raster3d=string)
- if (renamed==0):
- self.SetItemText(self.selected_layer,self.new_name)
- label = "g.rename "+self.GetItemText(self.selected_type)+"="+string+" -- completed"
- self.showNotification.emit(message=label)
- Debug.msg(1,"LAYER RENAMED TO: "+self.new_name)
- self.RestoreBackup()
-
- def OnPaste(self, event):
- """Paste layer or mapset"""
- # copying between mapsets of one location
- if (self.copy_layer == None):
- return
- if (self.selected_location == self.copy_location and self.selected_mapset):
- if (self.selected_type != None):
- if (self.GetItemText(self.copy_type) != self.GetItemText(self.selected_type)): # copy raster to vector or vice versa
- GError(_("Failed to copy layer: invalid type."), parent = self)
- return
- self.new_name = self._getUserEntry(_('New name'), _('Copy map'),
- self.GetItemText(self.copy_layer) + '_copy')
- if not self.new_name:
- return
- if (self.GetItemText(self.copy_layer) == self.new_name):
- GMessage(_("Layer was not copied: new layer has the same name"), parent=self)
- return
- string = self.GetItemText(self.copy_layer)+'@'+self.GetItemText(self.copy_mapset)+','+self.new_name
- self.ChangeEnvironment(self.GetItemText(self.selected_location), self.GetItemText(self.selected_mapset))
- pasted = 0
- type = None
- label = _("Copying") + " " + string + " ..."
- self.showNotification.emit(message=label)
- if (self.GetItemText(self.copy_type)=='vector'):
- pasted = RunCommand('g.copy', vector=string)
- node = 'vector'
- elif (self.GetItemText(self.copy_type)=='raster'):
- pasted = RunCommand('g.copy', raster=string)
- node = 'raster'
- else:
- pasted = RunCommand('g.copy', raster_3d=string)
- node = 'raster_3d'
- if pasted == 0:
- if self.selected_type == None:
- self.selected_type = self.getItemByName(node, self.selected_mapset)
- if self.selected_type == None:
- # add type node if not exists
- self.selected_type = self.AppendItem(self.selected_mapset, node)
- self.AppendItem(self.selected_type,self.new_name)
- self.SortChildren(self.selected_type)
- Debug.msg(1,"COPIED TO: "+self.new_name)
- label = "g.copy "+self.GetItemText(self.copy_type)+"="+string+" -- completed" # generate this message (command) automatically?
- self.showNotification.emit(message=label)
- else:
- GError(_("Failed to copy layer: action is allowed only within the same location."),
- parent=self)
-
- # expand selected mapset
- self.ExpandAllChildren(self.selected_mapset)
- self.EnsureVisible(self.selected_mapset)
-
- self.RestoreBackup()
-
-
- def OnDelete(self, event):
- """Delete layer or mapset"""
- if (self.selected_layer):
- string = self.GetItemText(self.selected_layer)
- self.ChangeEnvironment(self.GetItemText(self.selected_location), self.GetItemText(self.selected_mapset))
- removed = 0
- # TODO: rewrite this that it will tell map type in the dialog
- if (self._confirmDialog(question=_('Do you really want to delete map <{m}>?').format(m=string),
- title=_('Delete map')) == wx.ID_YES):
- label = _("Deleting") + " " + string + " ..."
- self.showNotification.emit(message=label)
- if (self.GetItemText(self.selected_type)=='vector'):
- removed = RunCommand('g.remove', flags='f', type='vector',
- name=string)
- elif (self.GetItemText(self.selected_type)=='raster'):
- removed = RunCommand('g.remove', flags='f', type='raster',
- name=string)
- else:
- removed = RunCommand('g.remove', flags='f', type='raster_3d',
- name=string)
- if (removed==0):
- self.Delete(self.selected_layer)
- Debug.msg(1,"LAYER "+string+" DELETED")
- label = "g.remove -f type="+self.GetItemText(self.selected_type)+" name="+string+" -- completed" # generate this message (command) automatically?
- self.showNotification.emit(message=label)
- self.RestoreBackup()
-
- def OnDisplayLayer(self, event):
- """Display layer in current graphics view"""
- layerName = []
- if (self.GetItemText(self.selected_location) == self.glocation and self.selected_mapset):
- string = self.GetItemText(self.selected_layer)+'@'+self.GetItemText(self.selected_mapset)
- layerName.append(string)
- label = _("Displaying") + " " + string + " ..."
- self.showNotification.emit(message=label)
- label = "d."+self.GetItemText(self.selected_type)+" --q map="+string+" -- completed. Go to Map layers for further operations."
- if (self.GetItemText(self.selected_type)=='vector'):
- self.parent.parent.AddMaps(layerName, 'vector', True)
- elif (self.GetItemText(self.selected_type)=='raster'):
- self.parent.parent.AddMaps(layerName, 'raster', True)
- else:
- self.parent.parent.AddMaps(layerName, 'raster_3d', True)
- label = "d.rast --q map="+string+" -- completed. Go to 'Map layers' for further operations." # generate this message (command) automatically?
- self.showNotification.emit(message=label)
- Debug.msg(1,"LAYER "+self.GetItemText(self.selected_layer)+" DISPLAYED")
- else:
- GError(_("Failed to display layer: not in current mapset or invalid layer"),
- parent = self)
- def OnBeginDrag(self, event):
- """Just copy necessary data"""
- if (self.ctrldown):
- #cursor = wx.StockCursor(wx.CURSOR_HAND)
- #self.SetCursor(cursor)
- event.Allow()
- self.DefineItems(event.GetItem())
- self.OnCopy(event)
- Debug.msg(1,"DRAG")
- else:
- event.Veto()
- Debug.msg(1,"DRAGGING without ctrl key does nothing")
-
- def OnEndDrag(self, event):
- """Copy layer into target"""
- #cursor = wx.StockCursor(wx.CURSOR_ARROW)
- #self.SetCursor(cursor)
- if (event.GetItem()):
- self.DefineItems(event.GetItem())
- if (self.selected_location == self.copy_location and self.selected_mapset):
- event.Allow()
- self.OnPaste(event)
- self.ctrldown = False
- #cursor = wx.StockCursor(wx.CURSOR_DEFAULT)
- #self.SetCursor(cursor) # TODO: change cursor while dragging and then back, this is not working
- Debug.msg(1,"DROP DONE")
- else:
- event.Veto()
- def _getUserEntry(self, message, title, value):
- """Dialog for simple text entry"""
- dlg = TextEntryDialog(self, message, title)
- dlg.SetValue(value)
- if dlg.ShowModal() == wx.ID_OK:
- name = dlg.GetValue()
- else:
- name = None
- dlg.Destroy()
- return name
- def _confirmDialog(self, question, title):
- """Confirm dialog"""
- dlg = wx.MessageDialog(self, question, title, wx.YES_NO)
- res = dlg.ShowModal()
- dlg.Destroy()
- return res
- def _popupMenuLayer(self):
- """Create popup menu for layers"""
- menu = wx.Menu()
-
- item = wx.MenuItem(menu, wx.NewId(), _("&Copy"))
- menu.AppendItem(item)
- self.Bind(wx.EVT_MENU, self.OnCopy, item)
- item = wx.MenuItem(menu, wx.NewId(), _("&Paste"))
- menu.AppendItem(item)
- self.Bind(wx.EVT_MENU, self.OnPaste, item)
-
- item = wx.MenuItem(menu, wx.NewId(), _("&Delete"))
- menu.AppendItem(item)
- self.Bind(wx.EVT_MENU, self.OnDelete, item)
-
- item = wx.MenuItem(menu, wx.NewId(), _("&Rename"))
- menu.AppendItem(item)
- self.Bind(wx.EVT_MENU, self.OnRename, item)
-
- item = wx.MenuItem(menu, wx.NewId(), _("&Display layer"))
- menu.AppendItem(item)
- self.Bind(wx.EVT_MENU, self.OnDisplayLayer, item)
-
- self.PopupMenu(menu)
- menu.Destroy()
-
- def _popupMenuMapset(self):
- """Create popup menu for mapsets"""
- menu = wx.Menu()
- item = wx.MenuItem(menu, wx.NewId(), _("&Paste"))
- menu.AppendItem(item)
- self.Bind(wx.EVT_MENU, self.OnPaste, item)
-
- self.PopupMenu(menu)
- menu.Destroy()
- # testing...
- if __name__ == "__main__":
- class TestTree(LocationMapTree):
- def __init__(self, parent):
- """Test Tree constructor."""
- super(TestTree, self).__init__(parent, style=wx.TR_HIDE_ROOT | wx.TR_EDIT_LABELS |
- wx.TR_HAS_BUTTONS | wx.TR_FULL_ROW_HIGHLIGHT | wx.TR_COLUMN_LINES |
- wx.TR_MULTIPLE)
-
- def InitTreeItems(self):
- """Add locations, mapsets and layers to the tree."""
- gisenv = grass.gisenv()
- location = gisenv['LOCATION_NAME']
- mapset = gisenv['MAPSET']
- self._initTreeItems(locations=[location],
- mapsets=[mapset])
-
- self.ExpandAll()
-
- def _popupMenuLayer(self):
- """Create popup menu for layers"""
- pass
- def _popupMenuMapset(self):
- """Create popup menu for mapsets"""
- pass
- class TestFrame(wx.Frame):
- """Frame for testing purposes only."""
- def __init__(self, model=None):
- wx.Frame.__init__(self, None, title='Test tree')
- panel = wx.Panel(self)
- self.tree = TestTree(parent=self)
- self.tree.SetMinSize((300, 500))
- self.tree.InitTreeItems()
- szr = wx.BoxSizer(wx.VERTICAL)
- szr.Add(self.tree, 1, wx.ALIGN_CENTER)
- panel.SetSizerAndFit(szr)
- szr.SetSizeHints(self)
- def main():
- app = wx.App()
- frame = TestFrame()
- frame.Show()
- app.MainLoop()
-
- main()
|