123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485 |
- """
- @package modules.extensions
- @brief GRASS Addons extensions management classes
- Classes:
- - extensions::InstallExtensionWindow
- - extensions::ExtensionTreeModelBuilder
- - extensions::ManageExtensionWindow
- - extensions::CheckListExtension
- (C) 2008-2015 by 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 Martin Landa <landa.martin gmail.com>
- @author Anna Petrasova <kratochanna gmail.com>
- """
- import os
- import sys
- import wx
- from grass.script import task as gtask
- from core import globalvar
- from core.gcmd import GError, RunCommand, GException, GMessage
- from core.utils import SetAddOnPath, _
- from core.gthread import gThread
- from core.menutree import TreeModel, ModuleNode
- from gui_core.widgets import GListCtrl, SearchModuleWidget
- from gui_core.treeview import CTreeView
- from core.toolboxes import toolboxesOutdated
- class InstallExtensionWindow(wx.Frame):
- def __init__(self, parent, giface, id = wx.ID_ANY,
- title = _("Fetch & install extension from GRASS Addons"), **kwargs):
- self.parent = parent
- self._giface = giface
- self.options = dict() # list of options
- wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
- self.SetIcon(wx.Icon(os.path.join(globalvar.ICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
-
- self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
- self.repoBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
- label = " %s " % _("Repository"))
- self.treeBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
- label = " %s " % _("List of extensions - double-click to install"))
-
- self.repo = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY)
-
- # modelBuilder loads data into tree model
- self.modelBuilder = ExtensionTreeModelBuilder()
- # tree view displays model data
- self.tree = CTreeView(parent=self.panel, model=self.modelBuilder.GetModel())
-
- self.search = SearchModuleWidget(parent=self.panel, model=self.modelBuilder.GetModel(),
- showChoice = False)
- self.search.showSearchResult.connect(lambda result: self.tree.Select(result))
- # show text in statusbar when notification appears
- self.search.showNotification.connect(lambda message: self.SetStatusText(message))
- # load data in different thread
- self.thread = gThread()
- self.optionBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
- label = " %s " % _("Options"))
- task = gtask.parse_interface('g.extension')
- ignoreFlags = ['l', 'c', 'g', 'a', 'f', 't', 'help', 'quiet']
- if sys.platform == 'win32':
- ignoreFlags.append('d')
- ignoreFlags.append('i')
-
- for f in task.get_options()['flags']:
- name = f.get('name', '')
- desc = f.get('label', '')
- if not desc:
- desc = f.get('description', '')
- if not name and not desc:
- continue
- if name in ignoreFlags:
- continue
- self.options[name] = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
- label = desc)
- defaultUrl = 'http://svn.osgeo.org/grass/grass-addons/grass7'
- self.repo.SetValue(task.get_param(value = 'svnurl').get('default', defaultUrl))
-
- self.statusbar = self.CreateStatusBar(number = 1)
-
- self.btnFetch = wx.Button(parent = self.panel, id = wx.ID_ANY,
- label = _("&Fetch"))
- self.btnFetch.SetToolTipString(_("Fetch list of available modules "
- "from GRASS Addons SVN repository"))
- self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
- self.btnInstall = wx.Button(parent = self.panel, id = wx.ID_ANY,
- label = _("&Install"))
- self.btnInstall.SetToolTipString(_("Install selected add-ons GRASS module"))
- self.btnInstall.Enable(False)
- self.btnHelp = wx.Button(parent = self.panel, id = wx.ID_HELP)
- self.btnHelp.SetToolTipString(_("Show g.extension manual page"))
-
- self.btnClose.Bind(wx.EVT_BUTTON, lambda evt: self.Close())
- self.btnFetch.Bind(wx.EVT_BUTTON, self.OnFetch)
- self.btnInstall.Bind(wx.EVT_BUTTON, self.OnInstall)
- self.btnHelp.Bind(wx.EVT_BUTTON, self.OnHelp)
- self.tree.selectionChanged.connect(self.OnItemSelected)
- self.tree.itemActivated.connect(self.OnItemActivated)
- self.tree.contextMenu.connect(self.OnContextMenu)
- wx.CallAfter(self._fetch)
-
- self._layout()
- def _layout(self):
- """Do layout"""
- sizer = wx.BoxSizer(wx.VERTICAL)
- repoSizer = wx.StaticBoxSizer(self.repoBox, wx.VERTICAL)
- repo1Sizer = wx.BoxSizer(wx.HORIZONTAL)
- repo1Sizer.Add(item = self.repo, proportion = 1,
- flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
- repo1Sizer.Add(item = self.btnFetch, proportion = 0,
- flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
- repoSizer.Add(item = repo1Sizer,
- flag = wx.EXPAND)
-
- findSizer = wx.BoxSizer(wx.HORIZONTAL)
- findSizer.Add(item = self.search, proportion = 1)
-
- treeSizer = wx.StaticBoxSizer(self.treeBox, wx.HORIZONTAL)
- treeSizer.Add(item = self.tree, proportion = 1,
- flag = wx.ALL | wx.EXPAND, border = 1)
- # options
- optionSizer = wx.StaticBoxSizer(self.optionBox, wx.VERTICAL)
- for key in self.options.keys():
- optionSizer.Add(item = self.options[key], proportion = 0)
-
- btnSizer = wx.BoxSizer(wx.HORIZONTAL)
- btnSizer.Add(item = self.btnHelp, proportion = 0)
- btnSizer.AddStretchSpacer()
- btnSizer.Add(item = self.btnClose, proportion = 0,
- flag = wx.RIGHT, border = 5)
- btnSizer.Add(item = self.btnInstall, proportion = 0)
-
- sizer.Add(item = repoSizer, proportion = 0,
- flag = wx.ALL | wx.EXPAND, border = 3)
- sizer.Add(item = findSizer, proportion = 0,
- flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
- sizer.Add(item = treeSizer, proportion = 1,
- flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
- sizer.Add(item = optionSizer, proportion = 0,
- flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
- sizer.Add(item = btnSizer, proportion = 0,
- flag = wx.ALIGN_RIGHT | wx.ALL | wx.EXPAND, border = 5)
-
- self.panel.SetSizer(sizer)
- sizer.Fit(self.panel)
-
- self.Layout()
- def _getCmd(self):
- item = self.tree.GetSelected()
- if not item or 'command' not in item[0].data:
- GError(_("Extension not defined"), parent = self)
- return
- name = item[0].data['command']
-
- flags = list()
- for key in self.options.keys():
- if self.options[key].IsChecked():
- if len(key) == 1:
- flags.append('-%s' % key)
- else:
- flags.append('--%s' % key)
-
- return ['g.extension'] + flags + ['extension=' + name,
- 'svnurl=' + self.repo.GetValue().strip()]
- def OnFetch(self, event):
- """Fetch list of available extensions"""
- self._fetch()
- def _fetch(self):
- """Fetch list of available extensions"""
- wx.BeginBusyCursor()
- self.SetStatusText(_("Fetching list of modules from GRASS-Addons SVN (be patient)..."), 0)
- try:
- self.thread.Run(callable=self.modelBuilder.Load, url=self.repo.GetValue().strip(),
- ondone=lambda event: self._fetchDone())
- except GException as e:
- self._fetchDone()
- GError(unicode(e), parent = self, showTraceback = False)
-
- def _fetchDone(self):
- self.tree.RefreshItems()
- nitems = len(self.modelBuilder.GetModel().SearchNodes(key='command', value='*'))
- self.SetStatusText(_("%d extensions loaded") % nitems, 0)
- wx.EndBusyCursor()
-
- def OnContextMenu(self, node):
- if not hasattr (self, "popupID"):
- self.popupID = dict()
- for key in ('install', 'help'):
- self.popupID[key] = wx.NewId()
-
- data = node.data
- if data and 'command' in data:
- self.popupMenu = wx.Menu()
- self.popupMenu.Append(self.popupID['install'], text = _("Install"))
- self.Bind(wx.EVT_MENU, self.OnInstall, id = self.popupID['install'])
- self.popupMenu.AppendSeparator()
- self.popupMenu.Append(self.popupID['help'], text = _("Show manual page"))
- self.Bind(wx.EVT_MENU, self.OnItemHelp, id = self.popupID['help'])
-
- self.PopupMenu(self.popupMenu)
- self.popupMenu.Destroy()
- def OnItemActivated(self, node):
- data = node.data
- if data and 'command' in data:
- self.OnInstall(event=None)
-
- def OnInstall(self, event):
- """Install selected extension"""
- log = self.parent.GetLogWindow()
- cmd = self._getCmd()
- if cmd:
- log.RunCmd(cmd, onDone = self.OnDone)
-
- def OnDone(self, event):
- if event.returncode == 0:
- if not os.getenv('GRASS_ADDON_BASE'):
- SetAddOnPath(key = 'BASE')
-
- globalvar.UpdateGRASSAddOnCommands()
- toolboxesOutdated()
- def OnItemHelp(self, event):
- item = self.tree.GetSelected()
- if not item or 'command' not in item[0].data:
- return
-
- self._giface.Help(entry=item[0].data['command'], online=True)
- def OnHelp(self, event):
- self._giface.Help(entry='g.extension')
- def OnItemSelected(self, node):
- """Item selected"""
- data = node.data
- if data is None:
- self.SetStatusText('', 0)
- self.btnInstall.Enable(False)
- else:
- self.SetStatusText(data.get('description', ''), 0)
- self.btnInstall.Enable(True)
- class ExtensionTreeModelBuilder:
- """Tree model of available extensions."""
- def __init__(self):
- self.mainNodes = dict()
- self.model = TreeModel(ModuleNode)
- for prefix in ('display', 'database',
- 'general', 'imagery',
- 'misc', 'postscript', 'paint',
- 'raster', 'raster3D', 'sites', 'vector', 'wxGUI', 'other'):
- node = self.model.AppendNode(parent=self.model.root, label=prefix)
- self.mainNodes[prefix] = node
-
- def GetModel(self):
- return self.model
- def _emptyTree(self):
- """Remove modules from tree keeping the main structure"""
- for node in self.mainNodes.values():
- for child in reversed(node.children):
- self.model.RemoveNode(child)
-
- def _expandPrefix(self, c):
- name = { 'd' : 'display',
- 'db' : 'database',
- 'g' : 'general',
- 'i' : 'imagery',
- 'm' : 'misc',
- 'ps' : 'postscript',
- 'p' : 'paint',
- 'r' : 'raster',
- 'r3' : 'raster3D',
- 's' : 'sites',
- 'v' : 'vector',
- 'wx' : 'wxGUI',
- '' : 'other' }
-
- if c in name:
- return name[c]
-
- return c
-
- def Load(self, url, full = True):
- """Load list of extensions"""
- self._emptyTree()
-
- if full:
- flags = 'g'
- else:
- flags = 'l'
- retcode, ret, msg = RunCommand('g.extension', read = True, getErrorMsg = True,
- svnurl = url,
- flags = flags, quiet = True)
- if retcode != 0:
- raise GException(_("Unable to load extensions. %s") % msg)
-
- currentNode = None
- for line in ret.splitlines():
- if full:
- try:
- key, value = line.split('=', 1)
- except ValueError:
- key = 'name'
- value = line
- if key == 'name':
- try:
- prefix, name = value.split('.', 1)
- except ValueError:
- prefix = ''
- name = value
- mainNode = self.mainNodes[self._expandPrefix(prefix)]
- currentNode = self.model.AppendNode(parent=mainNode, label=value)
- currentNode.data = {'command': value}
- else:
- if currentNode is not None:
- currentNode.data[key] = value
- else:
- try:
- prefix, name = line.strip().split('.', 1)
- except ValueError:
- prefix = ''
- name = line.strip()
-
- if self._expandPrefix(prefix) == prefix:
- prefix = ''
- module = prefix + '.' + name
- mainNode = self.mainNodes[self._expandPrefix(prefix)]
- currentNode = self.model.AppendNode(parent=mainNode, label=module)
- currentNode.data = {'command': module,
- 'keywords': '',
- 'description': ''}
-
- class ManageExtensionWindow(wx.Frame):
- def __init__(self, parent, id = wx.ID_ANY,
- title = _("Manage installed GRASS Addons extensions"), **kwargs):
- self.parent = parent
-
- wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
- self.SetIcon(wx.Icon(os.path.join(globalvar.ICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
-
- self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
- self.extBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
- label = " %s " % _("List of installed extensions"))
-
- self.extList = CheckListExtension(parent = self.panel)
- # buttons
- self.btnUninstall = wx.Button(parent = self.panel, id = wx.ID_REMOVE, label=_("Uninstall"))
- self.btnUninstall.SetToolTipString(_("Uninstall selected Addons extensions"))
- self.btnUpdate = wx.Button(parent = self.panel, id = wx.ID_REFRESH, label=_("Reinstall"))
- self.btnUpdate.SetToolTipString(_("Reinstall selected Addons extensions"))
- self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
-
- self.btnUninstall.Bind(wx.EVT_BUTTON, self.OnUninstall)
- self.btnUpdate.Bind(wx.EVT_BUTTON, self.OnUpdate)
- self.btnClose.Bind(wx.EVT_BUTTON, lambda evt: self.Close())
-
- self._layout()
-
- def _layout(self):
- """Do layout"""
- sizer = wx.BoxSizer(wx.VERTICAL)
-
- extSizer = wx.StaticBoxSizer(self.extBox, wx.HORIZONTAL)
- extSizer.Add(item = self.extList, proportion = 1,
- flag = wx.ALL | wx.EXPAND, border = 1)
-
- btnSizer = wx.BoxSizer(wx.HORIZONTAL)
- btnSizer.Add(item = self.btnClose, proportion = 0,
- flag = wx.RIGHT, border = 5)
- btnSizer.Add(item = self.btnUpdate, proportion = 0, flag=wx.RIGHT, border=5)
- btnSizer.Add(item = self.btnUninstall, proportion = 0)
-
- sizer.Add(item = extSizer, proportion = 1,
- flag = wx.ALL | wx.EXPAND, border = 3)
- sizer.Add(item = btnSizer, proportion = 0,
- flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
-
- self.panel.SetSizer(sizer)
- sizer.Fit(self.panel)
-
- self.Layout()
- def _getSelectedExtensions(self):
- eList = self.extList.GetExtensions()
- if not eList:
- GMessage(_("No extension selected. "
- "Operation canceled."),
- parent = self)
- return []
-
- return eList
- def OnUninstall(self, event):
- """Uninstall selected extensions"""
- eList = self._getSelectedExtensions()
- if not eList:
- return
-
- for ext in eList:
- files = RunCommand('g.extension', parent = self, read = True, quiet = True,
- extension = ext, operation = 'remove').splitlines()
- dlg = wx.MessageDialog(parent = self,
- message = _("List of files to be removed:\n%(files)s\n\n"
- "Do you want really to remove <%(ext)s> extension?") % \
- { 'files' : os.linesep.join(files), 'ext' : ext },
- caption = _("Remove extension"),
- style = wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
-
- if dlg.ShowModal() == wx.ID_YES:
- RunCommand('g.extension', flags = 'f', parent = self, quiet = True,
- extension = ext, operation = 'remove')
-
- self.extList.LoadData()
-
- # update prompt
- globalvar.UpdateGRASSAddOnCommands(eList)
- toolboxesOutdated()
- def OnUpdate(self, event):
- """Update selected extensions"""
- eList = self._getSelectedExtensions()
- if not eList:
- return
-
- log = self.parent.GetLogWindow()
-
- for ext in eList:
- log.RunCmd(['g.extension', 'extension=%s' % ext,
- 'operation=add'])
-
- class CheckListExtension(GListCtrl):
- """List of mapset/owner/group"""
- def __init__(self, parent):
- GListCtrl.__init__(self, parent)
-
- # load extensions
- self.InsertColumn(0, _('Extension'))
- self.LoadData()
-
- def LoadData(self):
- """Load data into list"""
- self.DeleteAllItems()
- for ext in RunCommand('g.extension',
- quiet = True, parent = self, read = True,
- flags = 'a').splitlines():
- if ext:
- self.InsertStringItem(sys.maxint, ext)
- def GetExtensions(self):
- """Get extensions to be un-installed
- """
- extList = list()
- for i in range(self.GetItemCount()):
- if self.IsChecked(i):
- name = self.GetItemText(i)
- if name:
- extList.append(name)
-
- return extList
|