extensions.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. """!
  2. @package modules.extensions
  3. @brief GRASS Addons extensions management classes
  4. Classes:
  5. - extensions::InstallExtensionWindow
  6. - extensions::ExtensionTree
  7. (C) 2008-2011 by the GRASS Development Team
  8. This program is free software under the GNU General Public License
  9. (>=v2). Read the file COPYING that comes with GRASS for details.
  10. @author Martin Landa <landa.martin gmail.com>
  11. """
  12. import os
  13. import sys
  14. import wx
  15. try:
  16. import wx.lib.agw.customtreectrl as CT
  17. except ImportError:
  18. import wx.lib.customtreectrl as CT
  19. import wx.lib.flatnotebook as FN
  20. import grass.script as grass
  21. from grass.script import task as gtask
  22. from core import globalvar
  23. from core.gcmd import GError, RunCommand
  24. from gui_core.forms import GUI
  25. from gui_core.widgets import ItemTree
  26. from gui_core.ghelp import SearchModuleWindow
  27. class InstallExtensionWindow(wx.Frame):
  28. def __init__(self, parent, id = wx.ID_ANY,
  29. title = _("Fetch & install extension from GRASS Addons"), **kwargs):
  30. self.parent = parent
  31. self.options = dict() # list of options
  32. wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
  33. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  34. self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
  35. self.repoBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  36. label = " %s " % _("Repository"))
  37. self.treeBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  38. label = " %s " % _("List of extensions"))
  39. self.repo = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY)
  40. self.fullDesc = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
  41. label = _("Fetch full info including description and keywords (takes time)"))
  42. self.fullDesc.SetValue(True)
  43. self.search = SearchModuleWindow(parent = self.panel)
  44. self.search.SetSelection(0)
  45. self.tree = ExtensionTree(parent = self.panel, log = parent.GetLogWindow())
  46. self.optionBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  47. label = " %s " % _("Options"))
  48. task = gtask.parse_interface('g.extension')
  49. ignoreFlags = ['l', 'c', 'g', 'f', 'quiet', 'verbose']
  50. if sys.platform == 'win32':
  51. ignoreFlags.append('d')
  52. ignoreFlags.append('i')
  53. for f in task.get_options()['flags']:
  54. name = f.get('name', '')
  55. desc = f.get('label', '')
  56. if not desc:
  57. desc = f.get('description', '')
  58. if not name and not desc:
  59. continue
  60. if name in ignoreFlags:
  61. continue
  62. self.options[name] = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
  63. label = desc)
  64. self.repo.SetValue(task.get_param(value = 'svnurl').get('default',
  65. 'http://svn.osgeo.org/grass/grass-addons/grass7'))
  66. self.statusbar = self.CreateStatusBar(number = 1)
  67. self.btnFetch = wx.Button(parent = self.panel, id = wx.ID_ANY,
  68. label = _("&Fetch"))
  69. self.btnFetch.SetToolTipString(_("Fetch list of available modules from GRASS Addons SVN repository"))
  70. self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
  71. self.btnInstall = wx.Button(parent = self.panel, id = wx.ID_ANY,
  72. label = _("&Install"))
  73. self.btnInstall.SetToolTipString(_("Install selected add-ons GRASS module"))
  74. self.btnInstall.Enable(False)
  75. self.btnCmd = wx.Button(parent = self.panel, id = wx.ID_ANY,
  76. label = _("Command dialog"))
  77. self.btnCmd.SetToolTipString(_('Open %s dialog') % 'g.extension')
  78. self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
  79. self.btnFetch.Bind(wx.EVT_BUTTON, self.OnFetch)
  80. self.btnInstall.Bind(wx.EVT_BUTTON, self.OnInstall)
  81. self.btnCmd.Bind(wx.EVT_BUTTON, self.OnCmdDialog)
  82. self.tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated)
  83. self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnItemSelected)
  84. self.search.Bind(wx.EVT_TEXT_ENTER, self.OnShowItem)
  85. self.search.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
  86. self._layout()
  87. def _layout(self):
  88. """!Do layout"""
  89. sizer = wx.BoxSizer(wx.VERTICAL)
  90. repoSizer = wx.StaticBoxSizer(self.repoBox, wx.VERTICAL)
  91. repo1Sizer = wx.BoxSizer(wx.HORIZONTAL)
  92. repo1Sizer.Add(item = self.repo, proportion = 1,
  93. flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
  94. repo1Sizer.Add(item = self.btnFetch, proportion = 0,
  95. flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
  96. repoSizer.Add(item = repo1Sizer,
  97. flag = wx.EXPAND)
  98. repoSizer.Add(item = self.fullDesc)
  99. findSizer = wx.BoxSizer(wx.HORIZONTAL)
  100. findSizer.Add(item = self.search, proportion = 1)
  101. treeSizer = wx.StaticBoxSizer(self.treeBox, wx.HORIZONTAL)
  102. treeSizer.Add(item = self.tree, proportion = 1,
  103. flag = wx.ALL | wx.EXPAND, border = 1)
  104. # options
  105. optionSizer = wx.StaticBoxSizer(self.optionBox, wx.VERTICAL)
  106. for key in self.options.keys():
  107. optionSizer.Add(item = self.options[key], proportion = 0)
  108. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  109. btnSizer.Add(item = self.btnCmd, proportion = 0,
  110. flag = wx.RIGHT, border = 5)
  111. btnSizer.AddSpacer(10)
  112. btnSizer.Add(item = self.btnClose, proportion = 0,
  113. flag = wx.RIGHT, border = 5)
  114. btnSizer.Add(item = self.btnInstall, proportion = 0)
  115. sizer.Add(item = repoSizer, proportion = 0,
  116. flag = wx.ALL | wx.EXPAND, border = 3)
  117. sizer.Add(item = findSizer, proportion = 0,
  118. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
  119. sizer.Add(item = treeSizer, proportion = 1,
  120. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
  121. sizer.Add(item = optionSizer, proportion = 0,
  122. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
  123. sizer.Add(item = btnSizer, proportion = 0,
  124. flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
  125. self.panel.SetSizer(sizer)
  126. sizer.Fit(self.panel)
  127. self.Layout()
  128. def _getCmd(self):
  129. item = self.tree.GetSelected()
  130. if not item or not item.IsOk():
  131. return ['g.extension']
  132. name = self.tree.GetItemText(item)
  133. if not name:
  134. GError(_("Extension not defined"), parent = self)
  135. return
  136. flags = list()
  137. for key in self.options.keys():
  138. if self.options[key].IsChecked():
  139. flags.append('-%s' % key)
  140. return ['g.extension'] + flags + ['extension=' + name,
  141. 'svnurl=' + self.repo.GetValue().strip()]
  142. def OnUpdateStatusBar(self, event):
  143. """!Update statusbar text"""
  144. element = self.search.GetSelection()
  145. if not self.tree.IsLoaded():
  146. self.SetStatusText(_("Fetch list of available extensions by clicking on 'Fetch' button"), 0)
  147. return
  148. self.tree.SearchItems(element = element,
  149. value = event.GetString())
  150. nItems = len(self.tree.itemsMarked)
  151. if event.GetString():
  152. self.SetStatusText(_("%d items match") % nItems, 0)
  153. else:
  154. self.SetStatusText("", 0)
  155. event.Skip()
  156. def OnCloseWindow(self, event):
  157. """!Close window"""
  158. self.Destroy()
  159. def OnFetch(self, event):
  160. """!Fetch list of available extensions"""
  161. wx.BeginBusyCursor()
  162. self.SetStatusText(_("Fetching list of modules from GRASS-Addons SVN (be patient)..."), 0)
  163. self.tree.Load(url = self.repo.GetValue().strip(), full = self.fullDesc.IsChecked())
  164. self.SetStatusText("", 0)
  165. wx.EndBusyCursor()
  166. def OnItemActivated(self, event):
  167. item = event.GetItem()
  168. data = self.tree.GetPyData(item)
  169. if data and 'command' in data:
  170. self.OnInstall(event = None)
  171. def OnInstall(self, event):
  172. """!Install selected extension"""
  173. log = self.parent.GetLogWindow()
  174. log.RunCmd(self._getCmd(), onDone = self.OnDone)
  175. def OnDone(self, cmd, returncode):
  176. item = self.tree.GetSelected()
  177. if not item or not item.IsOk() or \
  178. returncode != 0 or \
  179. not os.getenv('GRASS_ADDON_PATH'):
  180. return
  181. name = self.tree.GetItemText(item)
  182. globalvar.grassCmd['all'].append(name)
  183. def OnItemSelected(self, event):
  184. """!Item selected"""
  185. item = event.GetItem()
  186. self.tree.itemSelected = item
  187. data = self.tree.GetPyData(item)
  188. if not data:
  189. self.SetStatusText('', 0)
  190. self.btnInstall.Enable(False)
  191. else:
  192. self.SetStatusText(data.get('description', ''), 0)
  193. self.btnInstall.Enable(True)
  194. def OnShowItem(self, event):
  195. """!Show selected item"""
  196. self.tree.OnShowItem(event)
  197. if self.tree.GetSelected():
  198. self.btnInstall.Enable()
  199. else:
  200. self.btnInstall.Enable(False)
  201. def OnCmdDialog(self, event):
  202. """!Shows command dialog"""
  203. GUI(parent = self).ParseCommand(cmd = self._getCmd())
  204. class ExtensionTree(ItemTree):
  205. """!List of available extensions"""
  206. def __init__(self, parent, log, id = wx.ID_ANY,
  207. ctstyle = CT.TR_HIDE_ROOT | CT.TR_FULL_ROW_HIGHLIGHT | CT.TR_HAS_BUTTONS |
  208. CT.TR_LINES_AT_ROOT | CT.TR_SINGLE,
  209. **kwargs):
  210. self.parent = parent # GMFrame
  211. self.log = log
  212. super(ExtensionTree, self).__init__(parent, id, ctstyle = ctstyle, **kwargs)
  213. self._initTree()
  214. def _initTree(self):
  215. for prefix in ('display', 'database',
  216. 'general', 'imagery',
  217. 'misc', 'postscript', 'paint',
  218. 'raster', 'raster3D', 'sites', 'vector', 'wxGUI', 'other'):
  219. self.AppendItem(parentId = self.root,
  220. text = prefix)
  221. self._loaded = False
  222. def _expandPrefix(self, c):
  223. name = { 'd' : 'display',
  224. 'db' : 'database',
  225. 'g' : 'general',
  226. 'i' : 'imagery',
  227. 'm' : 'misc',
  228. 'ps' : 'postscript',
  229. 'p' : 'paint',
  230. 'r' : 'raster',
  231. 'r3' : 'raster3D',
  232. 's' : 'sites',
  233. 'v' : 'vector',
  234. 'wx' : 'wxGUI',
  235. '' : 'other' }
  236. if c in name:
  237. return name[c]
  238. return c
  239. def _findItem(self, text):
  240. """!Find item"""
  241. item = self.GetFirstChild(self.root)[0]
  242. while item and item.IsOk():
  243. if text == self.GetItemText(item):
  244. return item
  245. item = self.GetNextSibling(item)
  246. return None
  247. def Load(self, url, full = False):
  248. """!Load list of extensions"""
  249. self.DeleteAllItems()
  250. self.root = self.AddRoot(_("Menu tree"))
  251. self._initTree()
  252. if full:
  253. flags = 'g'
  254. else:
  255. flags = 'l'
  256. ret = RunCommand('g.extension', read = True, parent = self,
  257. svnurl = url,
  258. flags = flags, quiet = True)
  259. if not ret:
  260. return
  261. mdict = dict()
  262. for line in ret.splitlines():
  263. if full:
  264. key, value = line.split('=', 1)
  265. if key == 'name':
  266. try:
  267. prefix, name = value.split('.', 1)
  268. except ValueError:
  269. prefix = ''
  270. name = value
  271. if prefix not in mdict:
  272. mdict[prefix] = dict()
  273. mdict[prefix][name] = dict()
  274. else:
  275. mdict[prefix][name][key] = value
  276. else:
  277. try:
  278. prefix, name = line.strip().split('.', 1)
  279. except:
  280. prefix = ''
  281. name = line.strip()
  282. if self._expandPrefix(prefix) == prefix:
  283. prefix = ''
  284. if prefix not in mdict:
  285. mdict[prefix] = dict()
  286. mdict[prefix][name] = { 'command' : prefix + '.' + name }
  287. for prefix in mdict.keys():
  288. prefixName = self._expandPrefix(prefix)
  289. item = self._findItem(prefixName)
  290. names = mdict[prefix].keys()
  291. names.sort()
  292. for name in names:
  293. if prefix:
  294. text = prefix + '.' + name
  295. else:
  296. text = name
  297. new = self.AppendItem(parentId = item,
  298. text = text)
  299. data = dict()
  300. for key in mdict[prefix][name].keys():
  301. data[key] = mdict[prefix][name][key]
  302. self.SetPyData(new, data)
  303. self._loaded = True
  304. def IsLoaded(self):
  305. """Check if items are loaded"""
  306. return self._loaded