extensions.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  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-2011 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. import wx.lib.mixins.listctrl as listmix
  18. try:
  19. import wx.lib.agw.customtreectrl as CT
  20. except ImportError:
  21. import wx.lib.customtreectrl as CT
  22. import wx.lib.flatnotebook as FN
  23. import grass.script as grass
  24. from grass.script import task as gtask
  25. from core import globalvar
  26. from core.gcmd import GError, RunCommand
  27. from gui_core.forms import GUI
  28. from gui_core.widgets import ItemTree
  29. from gui_core.ghelp import SearchModuleWindow
  30. class InstallExtensionWindow(wx.Frame):
  31. def __init__(self, parent, id = wx.ID_ANY,
  32. title = _("Fetch & install extension from GRASS Addons"), **kwargs):
  33. self.parent = parent
  34. self.options = dict() # list of options
  35. wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
  36. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  37. self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
  38. self.repoBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  39. label = " %s " % _("Repository"))
  40. self.treeBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  41. label = " %s " % _("List of extensions"))
  42. self.repo = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY)
  43. self.fullDesc = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
  44. label = _("Fetch full info including description and keywords"))
  45. self.fullDesc.SetValue(True)
  46. self.search = SearchModuleWindow(parent = self.panel)
  47. self.search.SetSelection(0)
  48. self.tree = ExtensionTree(parent = self.panel, log = parent.GetLogWindow())
  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', 'verbose']
  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. self.repo.SetValue(task.get_param(value = 'svnurl').get('default',
  71. 'http://svn.osgeo.org/grass/grass-addons/grass7'))
  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 from GRASS Addons SVN repository"))
  76. self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
  77. self.btnInstall = wx.Button(parent = self.panel, id = wx.ID_ANY,
  78. label = _("&Install"))
  79. self.btnInstall.SetToolTipString(_("Install selected add-ons GRASS module"))
  80. self.btnInstall.Enable(False)
  81. self.btnCmd = wx.Button(parent = self.panel, id = wx.ID_ANY,
  82. label = _("Command dialog"))
  83. self.btnCmd.SetToolTipString(_('Open %s dialog') % 'g.extension')
  84. self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
  85. self.btnFetch.Bind(wx.EVT_BUTTON, self.OnFetch)
  86. self.btnInstall.Bind(wx.EVT_BUTTON, self.OnInstall)
  87. self.btnCmd.Bind(wx.EVT_BUTTON, self.OnCmdDialog)
  88. self.tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated)
  89. self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnItemSelected)
  90. self.search.Bind(wx.EVT_TEXT_ENTER, self.OnShowItem)
  91. self.search.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
  92. self._layout()
  93. def _layout(self):
  94. """!Do layout"""
  95. sizer = wx.BoxSizer(wx.VERTICAL)
  96. repoSizer = wx.StaticBoxSizer(self.repoBox, wx.VERTICAL)
  97. repo1Sizer = wx.BoxSizer(wx.HORIZONTAL)
  98. repo1Sizer.Add(item = self.repo, proportion = 1,
  99. flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
  100. repo1Sizer.Add(item = self.btnFetch, proportion = 0,
  101. flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
  102. repoSizer.Add(item = repo1Sizer,
  103. flag = wx.EXPAND)
  104. repoSizer.Add(item = self.fullDesc)
  105. findSizer = wx.BoxSizer(wx.HORIZONTAL)
  106. findSizer.Add(item = self.search, proportion = 1)
  107. treeSizer = wx.StaticBoxSizer(self.treeBox, wx.HORIZONTAL)
  108. treeSizer.Add(item = self.tree, proportion = 1,
  109. flag = wx.ALL | wx.EXPAND, border = 1)
  110. # options
  111. optionSizer = wx.StaticBoxSizer(self.optionBox, wx.VERTICAL)
  112. for key in self.options.keys():
  113. optionSizer.Add(item = self.options[key], proportion = 0)
  114. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  115. btnSizer.Add(item = self.btnCmd, proportion = 0,
  116. flag = wx.RIGHT, border = 5)
  117. btnSizer.AddSpacer(10)
  118. btnSizer.Add(item = self.btnClose, proportion = 0,
  119. flag = wx.RIGHT, border = 5)
  120. btnSizer.Add(item = self.btnInstall, proportion = 0)
  121. sizer.Add(item = repoSizer, proportion = 0,
  122. flag = wx.ALL | wx.EXPAND, border = 3)
  123. sizer.Add(item = findSizer, proportion = 0,
  124. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
  125. sizer.Add(item = treeSizer, proportion = 1,
  126. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
  127. sizer.Add(item = optionSizer, proportion = 0,
  128. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
  129. sizer.Add(item = btnSizer, proportion = 0,
  130. flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
  131. self.panel.SetSizer(sizer)
  132. sizer.Fit(self.panel)
  133. self.Layout()
  134. def _getCmd(self):
  135. item = self.tree.GetSelected()
  136. if not item or not item.IsOk():
  137. return ['g.extension']
  138. name = self.tree.GetItemText(item)
  139. if not name:
  140. GError(_("Extension not defined"), parent = self)
  141. return
  142. flags = list()
  143. for key in self.options.keys():
  144. if self.options[key].IsChecked():
  145. flags.append('-%s' % key)
  146. return ['g.extension'] + flags + ['extension=' + name,
  147. 'svnurl=' + self.repo.GetValue().strip()]
  148. def OnUpdateStatusBar(self, event):
  149. """!Update statusbar text"""
  150. element = self.search.GetSelection()
  151. if not self.tree.IsLoaded():
  152. self.SetStatusText(_("Fetch list of available extensions by clicking on 'Fetch' button"), 0)
  153. return
  154. self.tree.SearchItems(element = element,
  155. value = event.GetString())
  156. nItems = len(self.tree.itemsMarked)
  157. if event.GetString():
  158. self.SetStatusText(_("%d items match") % nItems, 0)
  159. else:
  160. self.SetStatusText("", 0)
  161. event.Skip()
  162. def OnCloseWindow(self, event):
  163. """!Close window"""
  164. self.Destroy()
  165. def OnFetch(self, event):
  166. """!Fetch list of available extensions"""
  167. wx.BeginBusyCursor()
  168. self.SetStatusText(_("Fetching list of modules from GRASS-Addons SVN (be patient)..."), 0)
  169. self.tree.Load(url = self.repo.GetValue().strip(), full = self.fullDesc.IsChecked())
  170. self.SetStatusText("", 0)
  171. wx.EndBusyCursor()
  172. def OnItemActivated(self, event):
  173. item = event.GetItem()
  174. data = self.tree.GetPyData(item)
  175. if data and 'command' in data:
  176. self.OnInstall(event = None)
  177. def OnInstall(self, event):
  178. """!Install selected extension"""
  179. log = self.parent.GetLogWindow()
  180. log.RunCmd(self._getCmd(), onDone = self.OnDone)
  181. def OnDone(self, cmd, returncode):
  182. item = self.tree.GetSelected()
  183. if not item or not item.IsOk() or \
  184. returncode != 0 or \
  185. not os.getenv('GRASS_ADDON_BASE'):
  186. return
  187. name = self.tree.GetItemText(item)
  188. globalvar.grassCmd.add(name)
  189. def OnItemSelected(self, event):
  190. """!Item selected"""
  191. item = event.GetItem()
  192. self.tree.itemSelected = item
  193. data = self.tree.GetPyData(item)
  194. if data is None:
  195. self.SetStatusText('', 0)
  196. self.btnInstall.Enable(False)
  197. else:
  198. self.SetStatusText(data.get('description', ''), 0)
  199. self.btnInstall.Enable(True)
  200. def OnShowItem(self, event):
  201. """!Show selected item"""
  202. self.tree.OnShowItem(event)
  203. if self.tree.GetSelected():
  204. self.btnInstall.Enable()
  205. else:
  206. self.btnInstall.Enable(False)
  207. def OnCmdDialog(self, event):
  208. """!Shows command dialog"""
  209. GUI(parent = self).ParseCommand(cmd = self._getCmd())
  210. class ExtensionTree(ItemTree):
  211. """!List of available extensions"""
  212. def __init__(self, parent, log, id = wx.ID_ANY,
  213. ctstyle = CT.TR_HIDE_ROOT | CT.TR_FULL_ROW_HIGHLIGHT | CT.TR_HAS_BUTTONS |
  214. CT.TR_LINES_AT_ROOT | CT.TR_SINGLE,
  215. **kwargs):
  216. self.parent = parent # GMFrame
  217. self.log = log
  218. super(ExtensionTree, self).__init__(parent, id, ctstyle = ctstyle, **kwargs)
  219. self._initTree()
  220. def _initTree(self):
  221. for prefix in ('display', 'database',
  222. 'general', 'imagery',
  223. 'misc', 'postscript', 'paint',
  224. 'raster', 'raster3D', 'sites', 'vector', 'wxGUI', 'other'):
  225. self.AppendItem(parentId = self.root,
  226. text = prefix)
  227. self._loaded = False
  228. def _expandPrefix(self, c):
  229. name = { 'd' : 'display',
  230. 'db' : 'database',
  231. 'g' : 'general',
  232. 'i' : 'imagery',
  233. 'm' : 'misc',
  234. 'ps' : 'postscript',
  235. 'p' : 'paint',
  236. 'r' : 'raster',
  237. 'r3' : 'raster3D',
  238. 's' : 'sites',
  239. 'v' : 'vector',
  240. 'wx' : 'wxGUI',
  241. '' : 'other' }
  242. if c in name:
  243. return name[c]
  244. return c
  245. def _findItem(self, text):
  246. """!Find item"""
  247. item = self.GetFirstChild(self.root)[0]
  248. while item and item.IsOk():
  249. if text == self.GetItemText(item):
  250. return item
  251. item = self.GetNextSibling(item)
  252. return None
  253. def Load(self, url, full = False):
  254. """!Load list of extensions"""
  255. self.DeleteAllItems()
  256. self.root = self.AddRoot(_("Menu tree"))
  257. self._initTree()
  258. if full:
  259. flags = 'g'
  260. else:
  261. flags = 'l'
  262. ret = RunCommand('g.extension', read = True, parent = self,
  263. svnurl = url,
  264. flags = flags, quiet = True)
  265. if not ret:
  266. return
  267. mdict = dict()
  268. for line in ret.splitlines():
  269. if full:
  270. try:
  271. key, value = line.split('=', 1)
  272. except ValueError:
  273. key = 'name'
  274. value = line
  275. if key == 'name':
  276. try:
  277. prefix, name = value.split('.', 1)
  278. except ValueError:
  279. prefix = ''
  280. name = value
  281. if prefix not in mdict:
  282. mdict[prefix] = dict()
  283. mdict[prefix][name] = dict()
  284. else:
  285. mdict[prefix][name][key] = value
  286. else:
  287. try:
  288. prefix, name = line.strip().split('.', 1)
  289. except:
  290. prefix = ''
  291. name = line.strip()
  292. if self._expandPrefix(prefix) == prefix:
  293. prefix = ''
  294. if prefix not in mdict:
  295. mdict[prefix] = dict()
  296. mdict[prefix][name] = { 'command' : prefix + '.' + name }
  297. for prefix in mdict.keys():
  298. prefixName = self._expandPrefix(prefix)
  299. item = self._findItem(prefixName)
  300. names = mdict[prefix].keys()
  301. names.sort()
  302. for name in names:
  303. if prefix:
  304. text = prefix + '.' + name
  305. else:
  306. text = name
  307. new = self.AppendItem(parentId = item,
  308. text = text)
  309. data = dict()
  310. for key in mdict[prefix][name].keys():
  311. data[key] = mdict[prefix][name][key]
  312. self.SetPyData(new, data)
  313. self._loaded = True
  314. def IsLoaded(self):
  315. """Check if items are loaded"""
  316. return self._loaded
  317. class UninstallExtensionWindow(wx.Frame):
  318. def __init__(self, parent, id = wx.ID_ANY,
  319. title = _("Uninstall GRASS Addons extensions"), **kwargs):
  320. self.parent = parent
  321. wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
  322. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  323. self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
  324. self.extBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  325. label = " %s " % _("List of installed extensions"))
  326. self.extList = CheckListExtension(parent = self.panel)
  327. # buttons
  328. self.btnUninstall = wx.Button(parent = self.panel, id = wx.ID_ANY,
  329. label = _("&Uninstall"))
  330. self.btnUninstall.SetToolTipString(_("Uninstall selected AddOns extensions"))
  331. self.btnCmd = wx.Button(parent = self.panel, id = wx.ID_ANY,
  332. label = _("Command dialog"))
  333. self.btnCmd.SetToolTipString(_('Open %s dialog') % 'g.extension')
  334. self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
  335. self.btnUninstall.Bind(wx.EVT_BUTTON, self.OnUninstall)
  336. self.btnCmd.Bind(wx.EVT_BUTTON, self.OnCmdDialog)
  337. self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
  338. self._layout()
  339. def _layout(self):
  340. """!Do layout"""
  341. sizer = wx.BoxSizer(wx.VERTICAL)
  342. extSizer = wx.StaticBoxSizer(self.extBox, wx.HORIZONTAL)
  343. extSizer.Add(item = self.extList, proportion = 1,
  344. flag = wx.ALL | wx.EXPAND, border = 1)
  345. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  346. btnSizer.Add(item = self.btnCmd, proportion = 0,
  347. flag = wx.RIGHT, border = 5)
  348. btnSizer.AddSpacer(10)
  349. btnSizer.Add(item = self.btnClose, proportion = 0,
  350. flag = wx.RIGHT, border = 5)
  351. btnSizer.Add(item = self.btnUninstall, proportion = 0)
  352. sizer.Add(item = extSizer, proportion = 1,
  353. flag = wx.ALL | wx.EXPAND, border = 3)
  354. sizer.Add(item = btnSizer, proportion = 0,
  355. flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
  356. self.panel.SetSizer(sizer)
  357. sizer.Fit(self.panel)
  358. self.Layout()
  359. def OnCloseWindow(self, event):
  360. """!Close window"""
  361. self.Destroy()
  362. def OnUninstall(self, event):
  363. """!Uninstall selected extensions"""
  364. log = self.parent.GetLogWindow()
  365. eList = self.extList.GetExtensions()
  366. if not eList:
  367. GError(_("No extension selected for removal. "
  368. "Operation canceled."),
  369. parent = self)
  370. return
  371. for ext in eList:
  372. files = RunCommand('g.extension', parent = self, read = True, quiet = True,
  373. extension = ext, operation = 'remove').splitlines()
  374. dlg = wx.MessageDialog(parent = self,
  375. message = _("List of files to be removed:\n%(files)s\n\n"
  376. "Do you want really to remove <%(ext)s> extension?") % \
  377. { 'files' : os.linesep.join(files), 'ext' : ext },
  378. caption = _("Remove extension"),
  379. style = wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
  380. if dlg.ShowModal() == wx.ID_YES:
  381. RunCommand('g.extension', flags = 'f', parent = self, quiet = True,
  382. extension = ext, operation = 'remove')
  383. self.extList.LoadData()
  384. def OnCmdDialog(self, event):
  385. """!Shows command dialog"""
  386. GUI(parent = self).ParseCommand(cmd = ['g.extension'])
  387. class CheckListExtension(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.CheckListCtrlMixin):
  388. """!List of mapset/owner/group"""
  389. def __init__(self, parent):
  390. self.parent = parent
  391. wx.ListCtrl.__init__(self, parent, id = wx.ID_ANY,
  392. style = wx.LC_REPORT)
  393. listmix.CheckListCtrlMixin.__init__(self)
  394. # setup mixins
  395. listmix.ListCtrlAutoWidthMixin.__init__(self)
  396. self.InsertColumn(0, _('Extension'))
  397. self.LoadData()
  398. def LoadData(self):
  399. """!Load data into list"""
  400. self.DeleteAllItems()
  401. for ext in RunCommand('g.extension',
  402. quiet = True, parent = self, read = True,
  403. flags = 'a').splitlines():
  404. if ext:
  405. self.InsertStringItem(sys.maxint, ext)
  406. def GetExtensions(self):
  407. """!Get extensions to be un-installed
  408. """
  409. extList = list()
  410. for i in range(self.GetItemCount()):
  411. if self.IsChecked(i):
  412. name = self.GetItemText(i)
  413. if name:
  414. extList.append(name)
  415. return extList