123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551 |
- """
- @package modules.extensions
- @brief GRASS Addons extensions management classes
- Classes:
- - extensions::InstallExtensionWindow
- - extensions::ExtensionTreeModelBuilder
- - extensions::ManageExtensionWindow
- - extensions::CheckListExtension
- (C) 2008-2016 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
- from gui_core.wrap import Button, StaticBox, TextCtrl, Menu
- 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 = StaticBox(
- # parent=self.panel, id=wx.ID_ANY, label=" %s " %
- # _("Repository (leave empty to use the official one)"))
- self.treeBox = StaticBox(
- parent=self.panel, id=wx.ID_ANY, label=" %s " %
- _("List of extensions - double-click to install"))
- # self.repo = 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 = 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 = '' # default/official one will be used when option empty
- # self.repo.SetValue(
- # task.get_param(
- # value='url').get(
- # 'default',
- # defaultUrl))
- self.statusbar = self.CreateStatusBar(number=1)
- # self.btnFetch = Button(parent=self.panel, id=wx.ID_ANY,
- # label=_("&Fetch"))
- # self.btnFetch.SetToolTip(_("Fetch list of available modules "
- # "from GRASS Addons repository"))
- self.btnClose = Button(parent=self.panel, id=wx.ID_CLOSE)
- self.btnInstall = Button(parent=self.panel, id=wx.ID_ANY,
- label=_("&Install"))
- self.btnInstall.SetToolTip(
- _("Install selected add-ons GRASS module"))
- self.btnInstall.Enable(False)
- self.btnHelp = Button(parent=self.panel, id=wx.ID_HELP)
- self.btnHelp.SetToolTip(_("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(self.repo, proportion=1,
- # flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=1)
- # repo1Sizer.Add(self.btnFetch, proportion=0,
- # flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=1)
- # repoSizer.Add(repo1Sizer,
- # flag=wx.EXPAND)
- findSizer = wx.BoxSizer(wx.HORIZONTAL)
- findSizer.Add(self.search, proportion=1)
- treeSizer = wx.StaticBoxSizer(self.treeBox, wx.HORIZONTAL)
- treeSizer.Add(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(self.options[key], proportion=0)
- btnSizer = wx.BoxSizer(wx.HORIZONTAL)
- btnSizer.Add(self.btnHelp, proportion=0)
- btnSizer.AddStretchSpacer()
- btnSizer.Add(self.btnClose, proportion=0,
- flag=wx.RIGHT, border=5)
- btnSizer.Add(self.btnInstall, proportion=0)
- # sizer.Add(repoSizer, proportion=0,
- # flag=wx.ALL | wx.EXPAND, border=3)
- sizer.Add(findSizer, proportion=0,
- flag=wx.ALL | wx.EXPAND, border=3)
- sizer.Add(treeSizer, proportion=1,
- flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=3)
- sizer.Add(optionSizer, proportion=0,
- flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=3)
- sizer.Add(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)
- # 'url=' + self.repo.GetValue().strip()]
- return ['g.extension'] + flags + ['extension={}'.format(name) ]
- 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 (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 = 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', 'temporal', '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',
- 't': 'temporal',
- '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, url=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 = StaticBox(
- parent=self.panel, id=wx.ID_ANY, label=" %s " %
- _("List of installed extensions"))
- self.extList = CheckListExtension(parent=self.panel)
- # buttons
- self.btnUninstall = Button(
- parent=self.panel,
- id=wx.ID_REMOVE,
- label=_("Uninstall"))
- self.btnUninstall.SetToolTip(
- _("Uninstall selected Addons extensions"))
- self.btnUpdate = Button(
- parent=self.panel,
- id=wx.ID_REFRESH,
- label=_("Reinstall"))
- self.btnUpdate.SetToolTip(
- _("Reinstall selected Addons extensions"))
- self.btnClose = 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(self.extList, proportion=1,
- flag=wx.ALL | wx.EXPAND, border=1)
- btnSizer = wx.BoxSizer(wx.HORIZONTAL)
- btnSizer.Add(self.btnClose, proportion=0,
- flag=wx.RIGHT, border=5)
- btnSizer.Add(
- self.btnUpdate,
- proportion=0,
- flag=wx.RIGHT,
- border=5)
- btnSizer.Add(self.btnUninstall, proportion=0)
- sizer.Add(extSizer, proportion=1,
- flag=wx.ALL | wx.EXPAND, border=3)
- sizer.Add(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()
- if len(files) > 10:
- files = files[:10]
- files.append('...')
- 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(self.GetItemCount(), 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
|