extensions.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. """!
  2. @package modules.extensions
  3. @brief GRASS Addons extensions management classes
  4. Classes:
  5. - extensions::InstallExtensionWindow
  6. - extensions::ExtensionTree
  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. """
  14. import os
  15. import sys
  16. import wx
  17. try:
  18. import wx.lib.agw.customtreectrl as CT
  19. except ImportError:
  20. import wx.lib.customtreectrl as CT
  21. import wx.lib.flatnotebook as FN
  22. import grass.script as grass
  23. from grass.script import task as gtask
  24. from core import globalvar
  25. from core.gcmd import GError, RunCommand
  26. from core.utils import SetAddOnPath
  27. from core.events import EVT_SHOW_NOTIFICATION
  28. from gui_core.forms import GUI
  29. from gui_core.widgets import ItemTree, GListCtrl, SearchModuleWidget, EVT_MODULE_SELECTED
  30. class ExtensionModulesData(object):
  31. """!Holds information about modules.
  32. @todo add some test
  33. @todo this class has some common methods with core::modulesdata::ModulesData
  34. """
  35. def __init__(self, modulesDesc):
  36. self.moduleDesc = modulesDesc
  37. self.moduleDescOriginal = modulesDesc
  38. def GetCommandDesc(self, cmd):
  39. """!Gets the description for a given module (command).
  40. If the given module is not available, an empty string is returned.
  41. \code
  42. print data.GetCommandDesc('r.info')
  43. Outputs basic information about a raster map.
  44. \endcode
  45. """
  46. if cmd in self.moduleDesc:
  47. return self.moduleDesc[cmd]['description']
  48. return ''
  49. def GetCommandItems(self):
  50. """!Gets list of available modules (commands).
  51. The list contains available module names.
  52. \code
  53. print data.GetCommandItems()[0:4]
  54. ['d.barscale', 'd.colorlist', 'd.colortable', 'd.correlate']
  55. \endcode
  56. """
  57. items = self.moduleDesc.keys()
  58. items.sort()
  59. return items
  60. def FindModules(self, text, findIn):
  61. """!Finds modules according to given text.
  62. @param text string to search
  63. @param findIn where to search for text
  64. (allowed values are 'description', 'keywords' and 'command')
  65. """
  66. modules = dict()
  67. iFound = 0
  68. for module, data in self.moduleDescOriginal.iteritems():
  69. found = False
  70. if findIn == 'description':
  71. if text in data['description']:
  72. found = True
  73. elif findIn == 'keywords':
  74. if text in data['keywords']:
  75. found = True
  76. elif findIn == 'command':
  77. if module[:len(text)] == text:
  78. found = True
  79. else:
  80. raise ValueError("Parameter findIn is not valid")
  81. if found:
  82. iFound += 1
  83. modules[module] = data
  84. return modules, iFound
  85. def SetFilter(self, data = None):
  86. """!Sets filter modules
  87. If @p data is not specified, module dictionary is derived
  88. from an internal data structures.
  89. @todo Document this method.
  90. @param data data dict
  91. """
  92. if data:
  93. self.moduleDesc = data
  94. else:
  95. self.moduleDesc = self.moduleDescOriginal
  96. def SetData(self, data):
  97. self.moduleDesc = self.moduleDescOriginal = data
  98. class InstallExtensionWindow(wx.Frame):
  99. def __init__(self, parent, id = wx.ID_ANY,
  100. title = _("Fetch & install extension from GRASS Addons"), **kwargs):
  101. self.parent = parent
  102. self.options = dict() # list of options
  103. wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
  104. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  105. self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
  106. self.repoBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  107. label = " %s " % _("Repository"))
  108. self.treeBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  109. label = " %s " % _("List of extensions - double-click to install"))
  110. self.repo = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY)
  111. self.fullDesc = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
  112. label = _("Fetch full info including description and keywords"))
  113. self.fullDesc.SetValue(True)
  114. self.tree = ExtensionTree(parent = self.panel, log = parent.GetLogWindow())
  115. self.modulesData = ExtensionModulesData(modulesDesc = self.tree.GetModules())
  116. self.search = SearchModuleWidget(parent = self.panel, modulesData = self.modulesData,
  117. showChoice = False)
  118. self.search.SetSelection(0)
  119. self.search.Bind(EVT_MODULE_SELECTED, self.OnShowItem)
  120. self.optionBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  121. label = " %s " % _("Options"))
  122. if sys.platform == 'win32':
  123. task = gtask.parse_interface('g.extension.py')
  124. else:
  125. task = gtask.parse_interface('g.extension')
  126. ignoreFlags = ['l', 'c', 'g', 'a', 'f', 't', 'quiet']
  127. if sys.platform == 'win32':
  128. ignoreFlags.append('d')
  129. ignoreFlags.append('i')
  130. for f in task.get_options()['flags']:
  131. name = f.get('name', '')
  132. desc = f.get('label', '')
  133. if not desc:
  134. desc = f.get('description', '')
  135. if not name and not desc:
  136. continue
  137. if name in ignoreFlags:
  138. continue
  139. self.options[name] = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
  140. label = desc)
  141. self.repo.SetValue(task.get_param(value = 'svnurl').get('default',
  142. 'http://svn.osgeo.org/grass/grass-addons/grass7'))
  143. self.statusbar = self.CreateStatusBar(number = 1)
  144. self.btnFetch = wx.Button(parent = self.panel, id = wx.ID_ANY,
  145. label = _("&Fetch"))
  146. self.btnFetch.SetToolTipString(_("Fetch list of available modules from GRASS Addons SVN repository"))
  147. self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
  148. self.btnInstall = wx.Button(parent = self.panel, id = wx.ID_ANY,
  149. label = _("&Install"))
  150. self.btnInstall.SetToolTipString(_("Install selected add-ons GRASS module"))
  151. self.btnInstall.Enable(False)
  152. self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
  153. self.btnFetch.Bind(wx.EVT_BUTTON, self.OnFetch)
  154. self.btnInstall.Bind(wx.EVT_BUTTON, self.OnInstall)
  155. self.tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated)
  156. self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnItemSelected)
  157. self.search.Bind(wx.EVT_TEXT_ENTER, self.OnShowItem)
  158. self.search.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
  159. # show text in statusbar when notification command event occurs
  160. # propagation stops here, no need to show text twice
  161. self.Bind(EVT_SHOW_NOTIFICATION,
  162. lambda event: self.SetStatusText(event.message))
  163. self._layout()
  164. def _layout(self):
  165. """!Do layout"""
  166. sizer = wx.BoxSizer(wx.VERTICAL)
  167. repoSizer = wx.StaticBoxSizer(self.repoBox, wx.VERTICAL)
  168. repo1Sizer = wx.BoxSizer(wx.HORIZONTAL)
  169. repo1Sizer.Add(item = self.repo, proportion = 1,
  170. flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
  171. repo1Sizer.Add(item = self.btnFetch, proportion = 0,
  172. flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
  173. repoSizer.Add(item = repo1Sizer,
  174. flag = wx.EXPAND)
  175. repoSizer.Add(item = self.fullDesc)
  176. findSizer = wx.BoxSizer(wx.HORIZONTAL)
  177. findSizer.Add(item = self.search, proportion = 1)
  178. treeSizer = wx.StaticBoxSizer(self.treeBox, wx.HORIZONTAL)
  179. treeSizer.Add(item = self.tree, proportion = 1,
  180. flag = wx.ALL | wx.EXPAND, border = 1)
  181. # options
  182. optionSizer = wx.StaticBoxSizer(self.optionBox, wx.VERTICAL)
  183. for key in self.options.keys():
  184. optionSizer.Add(item = self.options[key], proportion = 0)
  185. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  186. btnSizer.Add(item = self.btnClose, proportion = 0,
  187. flag = wx.RIGHT, border = 5)
  188. btnSizer.Add(item = self.btnInstall, proportion = 0)
  189. sizer.Add(item = repoSizer, proportion = 0,
  190. flag = wx.ALL | wx.EXPAND, border = 3)
  191. sizer.Add(item = findSizer, proportion = 0,
  192. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
  193. sizer.Add(item = treeSizer, proportion = 1,
  194. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
  195. sizer.Add(item = optionSizer, proportion = 0,
  196. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
  197. sizer.Add(item = btnSizer, proportion = 0,
  198. flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
  199. self.panel.SetSizer(sizer)
  200. sizer.Fit(self.panel)
  201. self.Layout()
  202. def _getCmd(self):
  203. item = self.tree.GetSelected()
  204. if not item or not item.IsOk():
  205. return ['g.extension']
  206. name = self.tree.GetItemText(item)
  207. if not name:
  208. GError(_("Extension not defined"), parent = self)
  209. return
  210. flags = list()
  211. for key in self.options.keys():
  212. if self.options[key].IsChecked():
  213. if len(key) == 1:
  214. flags.append('-%s' % key)
  215. else:
  216. flags.append('--%s' % key)
  217. return ['g.extension'] + flags + ['extension=' + name,
  218. 'svnurl=' + self.repo.GetValue().strip()]
  219. def OnUpdateStatusBar(self, event):
  220. """!Update statusbar text"""
  221. element = self.search.GetSelection()
  222. if not self.tree.IsLoaded():
  223. self.SetStatusText(_("Fetch list of available extensions by clicking on 'Fetch' button"), 0)
  224. return
  225. self.tree.SearchItems(element = element,
  226. value = event.GetEventObject().GetValue())
  227. nItems = len(self.tree.itemsMarked)
  228. if event.GetString():
  229. self.SetStatusText(_("%d items match") % nItems, 0)
  230. else:
  231. self.SetStatusText("", 0)
  232. event.Skip()
  233. def OnCloseWindow(self, event):
  234. """!Close window"""
  235. self.Destroy()
  236. def OnFetch(self, event):
  237. """!Fetch list of available extensions"""
  238. wx.BeginBusyCursor()
  239. self.SetStatusText(_("Fetching list of modules from GRASS-Addons SVN (be patient)..."), 0)
  240. self.tree.Load(url = self.repo.GetValue().strip(), full = self.fullDesc.IsChecked())
  241. modulesDesc = self.tree.GetModules()
  242. self.modulesData.SetData(modulesDesc)
  243. self.SetStatusText("", 0)
  244. wx.EndBusyCursor()
  245. def OnItemActivated(self, event):
  246. item = event.GetItem()
  247. data = self.tree.GetPyData(item)
  248. if data and 'command' in data:
  249. self.OnInstall(event = None)
  250. def OnInstall(self, event):
  251. """!Install selected extension"""
  252. log = self.parent.GetLogWindow()
  253. log.RunCmd(self._getCmd(), onDone = self.OnDone)
  254. def OnDone(self, cmd, returncode):
  255. if returncode == 0:
  256. if not os.getenv('GRASS_ADDON_BASE'):
  257. SetAddOnPath(key = 'BASE')
  258. globalvar.UpdateGRASSAddOnCommands()
  259. def OnItemSelected(self, event):
  260. """!Item selected"""
  261. item = event.GetItem()
  262. self.tree.itemSelected = item
  263. data = self.tree.GetPyData(item)
  264. if data is None:
  265. self.SetStatusText('', 0)
  266. self.btnInstall.Enable(False)
  267. else:
  268. self.SetStatusText(data.get('description', ''), 0)
  269. self.btnInstall.Enable(True)
  270. def OnShowItem(self, event):
  271. """!Show selected item"""
  272. self.tree.OnShowItem(event)
  273. if self.tree.GetSelected():
  274. self.btnInstall.Enable()
  275. else:
  276. self.btnInstall.Enable(False)
  277. class ExtensionTree(ItemTree):
  278. """!List of available extensions"""
  279. def __init__(self, parent, log, id = wx.ID_ANY,
  280. ctstyle = CT.TR_HIDE_ROOT | CT.TR_FULL_ROW_HIGHLIGHT | CT.TR_HAS_BUTTONS |
  281. CT.TR_LINES_AT_ROOT | CT.TR_SINGLE,
  282. **kwargs):
  283. self.parent = parent # GMFrame
  284. self.log = log
  285. super(ExtensionTree, self).__init__(parent, id, ctstyle = ctstyle, **kwargs)
  286. self._initTree()
  287. def _initTree(self):
  288. for prefix in ('display', 'database',
  289. 'general', 'imagery',
  290. 'misc', 'postscript', 'paint',
  291. 'raster', 'raster3D', 'sites', 'vector', 'wxGUI', 'other'):
  292. self.AppendItem(parentId = self.root,
  293. text = prefix)
  294. self._loaded = False
  295. def _expandPrefix(self, c):
  296. name = { 'd' : 'display',
  297. 'db' : 'database',
  298. 'g' : 'general',
  299. 'i' : 'imagery',
  300. 'm' : 'misc',
  301. 'ps' : 'postscript',
  302. 'p' : 'paint',
  303. 'r' : 'raster',
  304. 'r3' : 'raster3D',
  305. 's' : 'sites',
  306. 'v' : 'vector',
  307. 'wx' : 'wxGUI',
  308. '' : 'other' }
  309. if c in name:
  310. return name[c]
  311. return c
  312. def _findItem(self, text):
  313. """!Find item"""
  314. item = self.GetFirstChild(self.root)[0]
  315. while item and item.IsOk():
  316. if text == self.GetItemText(item):
  317. return item
  318. item = self.GetNextSibling(item)
  319. return None
  320. def Load(self, url, full = False):
  321. """!Load list of extensions"""
  322. self.DeleteAllItems()
  323. self.root = self.AddRoot(_("Menu tree"))
  324. self._initTree()
  325. if full:
  326. flags = 'g'
  327. else:
  328. flags = 'l'
  329. ret = RunCommand('g.extension', read = True, parent = self,
  330. svnurl = url,
  331. flags = flags, quiet = True)
  332. if not ret:
  333. return
  334. mdict = dict()
  335. for line in ret.splitlines():
  336. if full:
  337. try:
  338. key, value = line.split('=', 1)
  339. except ValueError:
  340. key = 'name'
  341. value = line
  342. if key == 'name':
  343. try:
  344. prefix, name = value.split('.', 1)
  345. except ValueError:
  346. prefix = ''
  347. name = value
  348. if prefix not in mdict:
  349. mdict[prefix] = dict()
  350. mdict[prefix][name] = dict()
  351. mdict[prefix][name]['command'] = value
  352. else:
  353. mdict[prefix][name][key] = value
  354. else:
  355. try:
  356. prefix, name = line.strip().split('.', 1)
  357. except:
  358. prefix = ''
  359. name = line.strip()
  360. if self._expandPrefix(prefix) == prefix:
  361. prefix = ''
  362. if prefix not in mdict:
  363. mdict[prefix] = dict()
  364. mdict[prefix][name] = { 'command' : prefix + '.' + name }
  365. for prefix in mdict.keys():
  366. prefixName = self._expandPrefix(prefix)
  367. item = self._findItem(prefixName)
  368. names = mdict[prefix].keys()
  369. names.sort()
  370. for name in names:
  371. if prefix:
  372. text = prefix + '.' + name
  373. else:
  374. text = name
  375. new = self.AppendItem(parentId = item,
  376. text = text)
  377. data = dict()
  378. for key in mdict[prefix][name].keys():
  379. data[key] = mdict[prefix][name][key]
  380. self.SetPyData(new, data)
  381. self._loaded = True
  382. def IsLoaded(self):
  383. """Check if items are loaded"""
  384. return self._loaded
  385. def GetModules(self):
  386. modules = {}
  387. root = self.GetRootItem()
  388. child, cookie = self.GetFirstChild(root)
  389. while child and child.IsOk():
  390. if self.ItemHasChildren(child):
  391. subChild, subCookie = self.GetFirstChild(child)
  392. while subChild and subChild.IsOk():
  393. name = self.GetItemText(subChild)
  394. data = self.GetPyData(subChild)
  395. modules[name] = data
  396. subChild, subCookie = self.GetNextChild(child, subCookie)
  397. child, cookie = self.GetNextChild(root, cookie)
  398. return modules
  399. class UninstallExtensionWindow(wx.Frame):
  400. def __init__(self, parent, id = wx.ID_ANY,
  401. title = _("Uninstall GRASS Addons extensions"), **kwargs):
  402. self.parent = parent
  403. wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
  404. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  405. self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
  406. self.extBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  407. label = " %s " % _("List of installed extensions"))
  408. self.extList = CheckListExtension(parent = self.panel)
  409. # buttons
  410. self.btnUninstall = wx.Button(parent = self.panel, id = wx.ID_ANY,
  411. label = _("&Uninstall"))
  412. self.btnUninstall.SetToolTipString(_("Uninstall selected AddOns extensions"))
  413. self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
  414. self.btnUninstall.Bind(wx.EVT_BUTTON, self.OnUninstall)
  415. self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
  416. self._layout()
  417. def _layout(self):
  418. """!Do layout"""
  419. sizer = wx.BoxSizer(wx.VERTICAL)
  420. extSizer = wx.StaticBoxSizer(self.extBox, wx.HORIZONTAL)
  421. extSizer.Add(item = self.extList, proportion = 1,
  422. flag = wx.ALL | wx.EXPAND, border = 1)
  423. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  424. btnSizer.Add(item = self.btnClose, proportion = 0,
  425. flag = wx.RIGHT, border = 5)
  426. btnSizer.Add(item = self.btnUninstall, proportion = 0)
  427. sizer.Add(item = extSizer, proportion = 1,
  428. flag = wx.ALL | wx.EXPAND, border = 3)
  429. sizer.Add(item = btnSizer, proportion = 0,
  430. flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
  431. self.panel.SetSizer(sizer)
  432. sizer.Fit(self.panel)
  433. self.Layout()
  434. def OnCloseWindow(self, event):
  435. """!Close window"""
  436. self.Destroy()
  437. def OnUninstall(self, event):
  438. """!Uninstall selected extensions"""
  439. log = self.parent.GetLogWindow()
  440. eList = self.extList.GetExtensions()
  441. if not eList:
  442. GError(_("No extension selected for removal. "
  443. "Operation canceled."),
  444. parent = self)
  445. return
  446. for ext in eList:
  447. files = RunCommand('g.extension', parent = self, read = True, quiet = True,
  448. extension = ext, operation = 'remove').splitlines()
  449. dlg = wx.MessageDialog(parent = self,
  450. message = _("List of files to be removed:\n%(files)s\n\n"
  451. "Do you want really to remove <%(ext)s> extension?") % \
  452. { 'files' : os.linesep.join(files), 'ext' : ext },
  453. caption = _("Remove extension"),
  454. style = wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
  455. if dlg.ShowModal() == wx.ID_YES:
  456. RunCommand('g.extension', flags = 'f', parent = self, quiet = True,
  457. extension = ext, operation = 'remove')
  458. self.extList.LoadData()
  459. # update prompt
  460. globalvar.UpdateGRASSAddOnCommands(eList)
  461. log = self.parent.GetLogWindow()
  462. log.GetPrompt().SetFilter(None)
  463. class CheckListExtension(GListCtrl):
  464. """!List of mapset/owner/group"""
  465. def __init__(self, parent):
  466. GListCtrl.__init__(self, parent)
  467. # load extensions
  468. self.InsertColumn(0, _('Extension'))
  469. self.LoadData()
  470. def LoadData(self):
  471. """!Load data into list"""
  472. self.DeleteAllItems()
  473. for ext in RunCommand('g.extension',
  474. quiet = True, parent = self, read = True,
  475. flags = 'a').splitlines():
  476. if ext:
  477. self.InsertStringItem(sys.maxint, ext)
  478. def GetExtensions(self):
  479. """!Get extensions to be un-installed
  480. """
  481. extList = list()
  482. for i in range(self.GetItemCount()):
  483. if self.IsChecked(i):
  484. name = self.GetItemText(i)
  485. if name:
  486. extList.append(name)
  487. return extList