extensions.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. """!
  2. @package modules.extensions
  3. @brief GRASS Addons extensions management classes
  4. Classes:
  5. - extensions::InstallExtensionWindow
  6. - extensions::ExtensionTreeModelBuilder
  7. - extensions::UninstallExtensionWindow
  8. - extensions::CheckListExtension
  9. (C) 2008-2013 by the GRASS Development Team
  10. This program is free software under the GNU General Public License
  11. (>=v2). Read the file COPYING that comes with GRASS for details.
  12. @author Martin Landa <landa.martin gmail.com>
  13. @author Anna Petrasova <kratochanna gmail.com>
  14. """
  15. import os
  16. import sys
  17. import wx
  18. from grass.script import task as gtask
  19. from core import globalvar
  20. from core.gcmd import GError, RunCommand, GException
  21. from core.utils import SetAddOnPath
  22. from core.menutree import TreeModel, ModuleNode
  23. from gui_core.widgets import GListCtrl, SearchModuleWidget
  24. from gui_core.treeview import CTreeView
  25. from core.toolboxes import toolboxesOutdated
  26. class InstallExtensionWindow(wx.Frame):
  27. def __init__(self, parent, id = wx.ID_ANY,
  28. title = _("Fetch & install extension from GRASS Addons"), **kwargs):
  29. self.parent = parent
  30. self.options = dict() # list of options
  31. wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
  32. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  33. self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
  34. self.repoBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  35. label = " %s " % _("Repository"))
  36. self.treeBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  37. label = " %s " % _("List of extensions - double-click to install"))
  38. self.repo = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY)
  39. # modelBuilder loads data into tree model
  40. self.modelBuilder = ExtensionTreeModelBuilder()
  41. # tree view displays model data
  42. self.tree = CTreeView(parent=self.panel, model=self.modelBuilder.GetModel())
  43. self.search = SearchModuleWidget(parent=self.panel, model=self.modelBuilder.GetModel(),
  44. showChoice = False)
  45. self.search.SetSelection(0)
  46. self.search.showSearchResult.connect(lambda result: self.tree.Select(result))
  47. # show text in statusbar when notification appears
  48. self.search.showNotification.connect(lambda message: self.SetStatusText(message))
  49. self.optionBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  50. label = " %s " % _("Options"))
  51. if sys.platform == 'win32':
  52. task = gtask.parse_interface('g.extension.py')
  53. else:
  54. task = gtask.parse_interface('g.extension')
  55. ignoreFlags = ['l', 'c', 'g', 'a', 'f', 't', 'quiet']
  56. if sys.platform == 'win32':
  57. ignoreFlags.append('d')
  58. ignoreFlags.append('i')
  59. for f in task.get_options()['flags']:
  60. name = f.get('name', '')
  61. desc = f.get('label', '')
  62. if not desc:
  63. desc = f.get('description', '')
  64. if not name and not desc:
  65. continue
  66. if name in ignoreFlags:
  67. continue
  68. self.options[name] = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
  69. label = desc)
  70. defaultUrl = 'http://svn.osgeo.org/grass/grass-addons/grass7'
  71. self.repo.SetValue(task.get_param(value = 'svnurl').get('default', defaultUrl))
  72. self.statusbar = self.CreateStatusBar(number = 1)
  73. self.btnFetch = wx.Button(parent = self.panel, id = wx.ID_ANY,
  74. label = _("&Fetch"))
  75. self.btnFetch.SetToolTipString(_("Fetch list of available modules "
  76. "from GRASS Addons SVN repository"))
  77. self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
  78. self.btnInstall = wx.Button(parent = self.panel, id = wx.ID_ANY,
  79. label = _("&Install"))
  80. self.btnInstall.SetToolTipString(_("Install selected add-ons GRASS module"))
  81. self.btnInstall.Enable(False)
  82. self.btnClose.Bind(wx.EVT_BUTTON, lambda evt: self.Close())
  83. self.btnFetch.Bind(wx.EVT_BUTTON, self.OnFetch)
  84. self.btnInstall.Bind(wx.EVT_BUTTON, self.OnInstall)
  85. self.tree.selectionChanged.connect(self.OnItemSelected)
  86. self.tree.itemActivated.connect(self.OnItemActivated)
  87. wx.CallAfter(self._fetch)
  88. self._layout()
  89. def _layout(self):
  90. """!Do layout"""
  91. sizer = wx.BoxSizer(wx.VERTICAL)
  92. repoSizer = wx.StaticBoxSizer(self.repoBox, wx.VERTICAL)
  93. repo1Sizer = wx.BoxSizer(wx.HORIZONTAL)
  94. repo1Sizer.Add(item = self.repo, proportion = 1,
  95. flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
  96. repo1Sizer.Add(item = self.btnFetch, proportion = 0,
  97. flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
  98. repoSizer.Add(item = repo1Sizer,
  99. flag = wx.EXPAND)
  100. findSizer = wx.BoxSizer(wx.HORIZONTAL)
  101. findSizer.Add(item = self.search, proportion = 1)
  102. treeSizer = wx.StaticBoxSizer(self.treeBox, wx.HORIZONTAL)
  103. treeSizer.Add(item = self.tree, proportion = 1,
  104. flag = wx.ALL | wx.EXPAND, border = 1)
  105. # options
  106. optionSizer = wx.StaticBoxSizer(self.optionBox, wx.VERTICAL)
  107. for key in self.options.keys():
  108. optionSizer.Add(item = self.options[key], proportion = 0)
  109. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  110. btnSizer.Add(item = self.btnClose, proportion = 0,
  111. flag = wx.RIGHT, border = 5)
  112. btnSizer.Add(item = self.btnInstall, proportion = 0)
  113. sizer.Add(item = repoSizer, proportion = 0,
  114. flag = wx.ALL | wx.EXPAND, border = 3)
  115. sizer.Add(item = findSizer, proportion = 0,
  116. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
  117. sizer.Add(item = treeSizer, proportion = 1,
  118. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
  119. sizer.Add(item = optionSizer, proportion = 0,
  120. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
  121. sizer.Add(item = btnSizer, proportion = 0,
  122. flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
  123. self.panel.SetSizer(sizer)
  124. sizer.Fit(self.panel)
  125. self.Layout()
  126. def _getCmd(self):
  127. item = self.tree.GetSelected()
  128. if not item or 'command' not in item[0].data:
  129. GError(_("Extension not defined"), parent = self)
  130. return
  131. name = item[0].data['command']
  132. flags = list()
  133. for key in self.options.keys():
  134. if self.options[key].IsChecked():
  135. if len(key) == 1:
  136. flags.append('-%s' % key)
  137. else:
  138. flags.append('--%s' % key)
  139. return ['g.extension'] + flags + ['extension=' + name,
  140. 'svnurl=' + self.repo.GetValue().strip()]
  141. def OnFetch(self, event):
  142. """!Fetch list of available extensions"""
  143. self._fetch()
  144. def _fetch(self):
  145. """!Fetch list of available extensions"""
  146. wx.BeginBusyCursor()
  147. self.SetStatusText(_("Fetching list of modules from GRASS-Addons SVN (be patient)..."), 0)
  148. try:
  149. self.modelBuilder.Load(url = self.repo.GetValue().strip())
  150. except GException, e:
  151. GError(unicode(e), parent = self, showTraceback = False)
  152. self.tree.RefreshItems()
  153. self.SetStatusText("", 0)
  154. wx.EndBusyCursor()
  155. def OnItemActivated(self, node):
  156. data = node.data
  157. if data and 'command' in data:
  158. self.OnInstall(event=None)
  159. def OnInstall(self, event):
  160. """!Install selected extension"""
  161. log = self.parent.GetLogWindow()
  162. cmd = self._getCmd()
  163. if cmd:
  164. log.RunCmd(cmd, onDone = self.OnDone)
  165. def OnDone(self, cmd, returncode):
  166. if returncode == 0:
  167. if not os.getenv('GRASS_ADDON_BASE'):
  168. SetAddOnPath(key = 'BASE')
  169. globalvar.UpdateGRASSAddOnCommands()
  170. toolboxesOutdated()
  171. def OnItemSelected(self, node):
  172. """!Item selected"""
  173. data = node.data
  174. if data is None:
  175. self.SetStatusText('', 0)
  176. self.btnInstall.Enable(False)
  177. else:
  178. self.SetStatusText(data.get('description', ''), 0)
  179. self.btnInstall.Enable(True)
  180. class ExtensionTreeModelBuilder:
  181. """!Tree model of available extensions."""
  182. def __init__(self):
  183. self.mainNodes = dict()
  184. self.model = TreeModel(ModuleNode)
  185. for prefix in ('display', 'database',
  186. 'general', 'imagery',
  187. 'misc', 'postscript', 'paint',
  188. 'raster', 'raster3D', 'sites', 'vector', 'wxGUI', 'other'):
  189. node = self.model.AppendNode(parent=self.model.root, label=prefix)
  190. self.mainNodes[prefix] = node
  191. def GetModel(self):
  192. return self.model
  193. def _emptyTree(self):
  194. """!Remove modules from tree keeping the main structure"""
  195. for node in self.mainNodes.values():
  196. for child in reversed(node.children):
  197. self.model.RemoveNode(child)
  198. def _expandPrefix(self, c):
  199. name = { 'd' : 'display',
  200. 'db' : 'database',
  201. 'g' : 'general',
  202. 'i' : 'imagery',
  203. 'm' : 'misc',
  204. 'ps' : 'postscript',
  205. 'p' : 'paint',
  206. 'r' : 'raster',
  207. 'r3' : 'raster3D',
  208. 's' : 'sites',
  209. 'v' : 'vector',
  210. 'wx' : 'wxGUI',
  211. '' : 'other' }
  212. if c in name:
  213. return name[c]
  214. return c
  215. def Load(self, url, full = True):
  216. """!Load list of extensions"""
  217. self._emptyTree()
  218. if full:
  219. flags = 'g'
  220. else:
  221. flags = 'l'
  222. ret = RunCommand('g.extension', read = True,
  223. svnurl = url,
  224. flags = flags, quiet = True)
  225. if not ret:
  226. raise GException(_("Unable to load extensions."))
  227. currentNode = None
  228. for line in ret.splitlines():
  229. if full:
  230. try:
  231. key, value = line.split('=', 1)
  232. except ValueError:
  233. key = 'name'
  234. value = line
  235. if key == 'name':
  236. try:
  237. prefix, name = value.split('.', 1)
  238. except ValueError:
  239. prefix = ''
  240. name = value
  241. mainNode = self.mainNodes[self._expandPrefix(prefix)]
  242. currentNode = self.model.AppendNode(parent=mainNode, label=value)
  243. currentNode.data = {'command': value}
  244. else:
  245. if currentNode is not None:
  246. currentNode.data[key] = value
  247. else:
  248. try:
  249. prefix, name = line.strip().split('.', 1)
  250. except ValueError:
  251. prefix = ''
  252. name = line.strip()
  253. if self._expandPrefix(prefix) == prefix:
  254. prefix = ''
  255. module = prefix + '.' + name
  256. mainNode = self.mainNodes[self._expandPrefix(prefix)]
  257. currentNode = self.model.AppendNode(parent=mainNode, label=module)
  258. currentNode.data = {'command': module,
  259. 'keywords': '',
  260. 'description': ''}
  261. class UninstallExtensionWindow(wx.Frame):
  262. def __init__(self, parent, id = wx.ID_ANY,
  263. title = _("Uninstall GRASS Addons extensions"), **kwargs):
  264. self.parent = parent
  265. wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
  266. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  267. self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
  268. self.extBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  269. label = " %s " % _("List of installed extensions"))
  270. self.extList = CheckListExtension(parent = self.panel)
  271. # buttons
  272. self.btnUninstall = wx.Button(parent = self.panel, id = wx.ID_ANY,
  273. label = _("&Uninstall"))
  274. self.btnUninstall.SetToolTipString(_("Uninstall selected AddOns extensions"))
  275. self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
  276. self.btnUninstall.Bind(wx.EVT_BUTTON, self.OnUninstall)
  277. self.btnClose.Bind(wx.EVT_BUTTON, lambda evt: self.Close())
  278. self._layout()
  279. def _layout(self):
  280. """!Do layout"""
  281. sizer = wx.BoxSizer(wx.VERTICAL)
  282. extSizer = wx.StaticBoxSizer(self.extBox, wx.HORIZONTAL)
  283. extSizer.Add(item = self.extList, proportion = 1,
  284. flag = wx.ALL | wx.EXPAND, border = 1)
  285. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  286. btnSizer.Add(item = self.btnClose, proportion = 0,
  287. flag = wx.RIGHT, border = 5)
  288. btnSizer.Add(item = self.btnUninstall, proportion = 0)
  289. sizer.Add(item = extSizer, proportion = 1,
  290. flag = wx.ALL | wx.EXPAND, border = 3)
  291. sizer.Add(item = btnSizer, proportion = 0,
  292. flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
  293. self.panel.SetSizer(sizer)
  294. sizer.Fit(self.panel)
  295. self.Layout()
  296. def OnUninstall(self, event):
  297. """!Uninstall selected extensions"""
  298. eList = self.extList.GetExtensions()
  299. if not eList:
  300. GError(_("No extension selected for removal. "
  301. "Operation canceled."),
  302. parent = self)
  303. return
  304. for ext in eList:
  305. files = RunCommand('g.extension', parent = self, read = True, quiet = True,
  306. extension = ext, operation = 'remove').splitlines()
  307. dlg = wx.MessageDialog(parent = self,
  308. message = _("List of files to be removed:\n%(files)s\n\n"
  309. "Do you want really to remove <%(ext)s> extension?") % \
  310. { 'files' : os.linesep.join(files), 'ext' : ext },
  311. caption = _("Remove extension"),
  312. style = wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
  313. if dlg.ShowModal() == wx.ID_YES:
  314. RunCommand('g.extension', flags = 'f', parent = self, quiet = True,
  315. extension = ext, operation = 'remove')
  316. self.extList.LoadData()
  317. # update prompt
  318. globalvar.UpdateGRASSAddOnCommands(eList)
  319. toolboxesOutdated()
  320. class CheckListExtension(GListCtrl):
  321. """!List of mapset/owner/group"""
  322. def __init__(self, parent):
  323. GListCtrl.__init__(self, parent)
  324. # load extensions
  325. self.InsertColumn(0, _('Extension'))
  326. self.LoadData()
  327. def LoadData(self):
  328. """!Load data into list"""
  329. self.DeleteAllItems()
  330. for ext in RunCommand('g.extension',
  331. quiet = True, parent = self, read = True,
  332. flags = 'a').splitlines():
  333. if ext:
  334. self.InsertStringItem(sys.maxint, ext)
  335. def GetExtensions(self):
  336. """!Get extensions to be un-installed
  337. """
  338. extList = list()
  339. for i in range(self.GetItemCount()):
  340. if self.IsChecked(i):
  341. name = self.GetItemText(i)
  342. if name:
  343. extList.append(name)
  344. return extList