GRASS Programmer's Manual  6.5.svn(2014)-r66266
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
extensions.py
Go to the documentation of this file.
1 """!
2 @package modules.extensions
3 
4 @brief GRASS Addons extensions management classes
5 
6 Classes:
7  - extensions::InstallExtensionWindow
8  - extensions::ExtensionTree
9  - extensions::UninstallExtensionWindow
10  - extensions::CheckListExtension
11 
12 (C) 2008-2011 by the GRASS Development Team
13 
14 This program is free software under the GNU General Public License
15 (>=v2). Read the file COPYING that comes with GRASS for details.
16 
17 @author Martin Landa <landa.martin gmail.com>
18 """
19 
20 import os
21 import sys
22 
23 import wx
24 import wx.lib.mixins.listctrl as listmix
25 try:
26  import wx.lib.agw.customtreectrl as CT
27 except ImportError:
28  import wx.lib.customtreectrl as CT
29 import wx.lib.flatnotebook as FN
30 
31 import grass.script as grass
32 from grass.script import task as gtask
33 
34 from core import globalvar
35 from core.gcmd import GError, RunCommand
36 from core.utils import SetAddOnPath
37 from gui_core.forms import GUI
38 from gui_core.widgets import ItemTree
39 from gui_core.ghelp import SearchModuleWindow
40 
41 class InstallExtensionWindow(wx.Frame):
42  def __init__(self, parent, id = wx.ID_ANY,
43  title = _("Fetch & install extension from GRASS Addons"), **kwargs):
44  self.parent = parent
45  self.options = dict() # list of options
46 
47  wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
48  self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
49 
50  self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
51 
52  self.repoBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
53  label = " %s " % _("Repository"))
54  self.treeBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
55  label = " %s " % _("List of extensions"))
56 
57  self.repo = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY)
58  self.fullDesc = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
59  label = _("Fetch full info including description and keywords"))
60  self.fullDesc.SetValue(True)
61 
62  self.search = SearchModuleWindow(parent = self.panel)
63  self.search.SetSelection(0)
64 
65  self.tree = ExtensionTree(parent = self.panel, log = parent.GetLogWindow())
66 
67  self.optionBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
68  label = " %s " % _("Options"))
69 
70  task = gtask.parse_interface('g.extension.py')
71 
72  ignoreFlags = ['l', 'c', 'g', 'a', 'f', 'quiet', 'verbose']
73  if sys.platform == 'win32':
74  ignoreFlags.append('d')
75  ignoreFlags.append('i')
76 
77  for f in task.get_options()['flags']:
78  name = f.get('name', '')
79  desc = f.get('label', '')
80  if not desc:
81  desc = f.get('description', '')
82  if not name and not desc:
83  continue
84  if name in ignoreFlags:
85  continue
86  self.options[name] = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
87  label = desc)
88  self.repo.SetValue(task.get_param(value = 'svnurl').get('default',
89  'http://svn.osgeo.org/grass/grass-addons'))
90 
91  self.statusbar = self.CreateStatusBar(number = 1)
92 
93  self.btnFetch = wx.Button(parent = self.panel, id = wx.ID_ANY,
94  label = _("&Fetch"))
95  self.btnFetch.SetToolTipString(_("Fetch list of available modules from GRASS Addons SVN repository"))
96  self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
97  self.btnInstall = wx.Button(parent = self.panel, id = wx.ID_ANY,
98  label = _("&Install"))
99  self.btnInstall.SetToolTipString(_("Install selected add-ons GRASS module"))
100  self.btnInstall.Enable(False)
101  self.btnCmd = wx.Button(parent = self.panel, id = wx.ID_ANY,
102  label = _("Command dialog"))
103  self.btnCmd.SetToolTipString(_('Open %s dialog') % 'g.extension.py')
104 
105  self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
106  self.btnFetch.Bind(wx.EVT_BUTTON, self.OnFetch)
107  self.btnInstall.Bind(wx.EVT_BUTTON, self.OnInstall)
108  self.btnCmd.Bind(wx.EVT_BUTTON, self.OnCmdDialog)
109  self.tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated)
110  self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnItemSelected)
111  self.search.Bind(wx.EVT_TEXT_ENTER, self.OnShowItem)
112  self.search.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
113 
114  self._layout()
115 
116  def _layout(self):
117  """!Do layout"""
118  sizer = wx.BoxSizer(wx.VERTICAL)
119  repoSizer = wx.StaticBoxSizer(self.repoBox, wx.VERTICAL)
120  repo1Sizer = wx.BoxSizer(wx.HORIZONTAL)
121  repo1Sizer.Add(item = self.repo, proportion = 1,
122  flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
123  repo1Sizer.Add(item = self.btnFetch, proportion = 0,
124  flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
125  repoSizer.Add(item = repo1Sizer,
126  flag = wx.EXPAND)
127  repoSizer.Add(item = self.fullDesc)
128 
129  findSizer = wx.BoxSizer(wx.HORIZONTAL)
130  findSizer.Add(item = self.search, proportion = 1)
131 
132  treeSizer = wx.StaticBoxSizer(self.treeBox, wx.HORIZONTAL)
133  treeSizer.Add(item = self.tree, proportion = 1,
134  flag = wx.ALL | wx.EXPAND, border = 1)
135 
136  # options
137  optionSizer = wx.StaticBoxSizer(self.optionBox, wx.VERTICAL)
138  for key in self.options.keys():
139  optionSizer.Add(item = self.options[key], proportion = 0)
140 
141  btnSizer = wx.BoxSizer(wx.HORIZONTAL)
142  btnSizer.Add(item = self.btnCmd, proportion = 0,
143  flag = wx.RIGHT, border = 5)
144  btnSizer.AddSpacer(10)
145  btnSizer.Add(item = self.btnClose, proportion = 0,
146  flag = wx.RIGHT, border = 5)
147  btnSizer.Add(item = self.btnInstall, proportion = 0)
148 
149  sizer.Add(item = repoSizer, proportion = 0,
150  flag = wx.ALL | wx.EXPAND, border = 3)
151  sizer.Add(item = findSizer, proportion = 0,
152  flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
153  sizer.Add(item = treeSizer, proportion = 1,
154  flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
155  sizer.Add(item = optionSizer, proportion = 0,
156  flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
157  sizer.Add(item = btnSizer, proportion = 0,
158  flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
159 
160  self.panel.SetSizer(sizer)
161  sizer.Fit(self.panel)
162 
163  self.Layout()
164 
165  def _getCmd(self):
166  item = self.tree.GetSelected()
167  if not item or not item.IsOk():
168  return ['g.extension.py']
169 
170  name = self.tree.GetItemText(item)
171  if not name:
172  GError(_("Extension not defined"), parent = self)
173  return
174  flags = list()
175  for key in self.options.keys():
176  if self.options[key].IsChecked():
177  flags.append('-%s' % key)
178 
179  return ['g.extension.py'] + flags + ['extension=' + name,
180  'svnurl=' + self.repo.GetValue().strip()]
181 
182  def OnUpdateStatusBar(self, event):
183  """!Update statusbar text"""
184  element = self.search.GetSelection()
185  if not self.tree.IsLoaded():
186  self.SetStatusText(_("Fetch list of available extensions by clicking on 'Fetch' button"), 0)
187  return
188 
189  self.tree.SearchItems(element = element,
190  value = event.GetString())
191 
192  nItems = len(self.tree.itemsMarked)
193  if event.GetString():
194  self.SetStatusText(_("%d items match") % nItems, 0)
195  else:
196  self.SetStatusText("", 0)
197 
198  event.Skip()
199 
200  def OnCloseWindow(self, event):
201  """!Close window"""
202  self.Destroy()
203 
204  def OnFetch(self, event):
205  """!Fetch list of available extensions"""
206  wx.BeginBusyCursor()
207  self.SetStatusText(_("Fetching list of modules from GRASS-Addons SVN (be patient)..."), 0)
208  self.tree.Load(url = self.repo.GetValue().strip(), full = self.fullDesc.IsChecked())
209  self.SetStatusText("", 0)
210  wx.EndBusyCursor()
211 
212  def OnItemActivated(self, event):
213  item = event.GetItem()
214  data = self.tree.GetPyData(item)
215  if data and 'command' in data:
216  self.OnInstall(event = None)
217 
218  def OnInstall(self, event):
219  """!Install selected extension"""
220  log = self.parent.GetLogWindow()
221  log.RunCmd(self._getCmd(), onDone = self.OnDone)
222 
223  def OnDone(self, cmd, returncode):
224  if returncode == 0:
225  if not os.getenv('GRASS_ADDON_PATH'):
226  SetAddOnPath()
227 
229  log = self.parent.GetLogWindow()
230  log.GetPrompt().SetFilter(None)
231 
232  def OnItemSelected(self, event):
233  """!Item selected"""
234  item = event.GetItem()
235  self.tree.itemSelected = item
236  data = self.tree.GetPyData(item)
237  if data is None:
238  self.SetStatusText('', 0)
239  self.btnInstall.Enable(False)
240  else:
241  self.SetStatusText(data.get('description', ''), 0)
242  self.btnInstall.Enable(True)
243 
244  def OnShowItem(self, event):
245  """!Show selected item"""
246  self.tree.OnShowItem(event)
247  if self.tree.GetSelected():
248  self.btnInstall.Enable()
249  else:
250  self.btnInstall.Enable(False)
251 
252  def OnCmdDialog(self, event):
253  """!Shows command dialog"""
254  GUI(parent = self).ParseCommand(cmd = self._getCmd())
255 
256 class ExtensionTree(ItemTree):
257  """!List of available extensions"""
258  def __init__(self, parent, log, id = wx.ID_ANY,
259  ctstyle = CT.TR_HIDE_ROOT | CT.TR_FULL_ROW_HIGHLIGHT | CT.TR_HAS_BUTTONS |
260  CT.TR_LINES_AT_ROOT | CT.TR_SINGLE,
261  **kwargs):
262  self.parent = parent # GMFrame
263  self.log = log
264 
265  super(ExtensionTree, self).__init__(parent, id, ctstyle = ctstyle, **kwargs)
266 
267  self._initTree()
268 
269  def _initTree(self):
270  for prefix in ('display', 'database',
271  'general', 'imagery',
272  'misc', 'postscript', 'paint',
273  'raster', 'raster3d', 'sites', 'vector', 'wxGUI', 'other'):
274  self.AppendItem(parentId = self.root,
275  text = prefix)
276  self._loaded = False
277 
278  def _expandPrefix(self, c):
279  name = { 'd' : 'display',
280  'db' : 'database',
281  'g' : 'general',
282  'i' : 'imagery',
283  'm' : 'misc',
284  'ps' : 'postscript',
285  'p' : 'paint',
286  'r' : 'raster',
287  'r3' : 'raster3d',
288  's' : 'sites',
289  'v' : 'vector',
290  'wx' : 'wxGUI',
291  '' : 'other' }
292 
293  if c in name:
294  return name[c]
295 
296  return c
297 
298  def _findItem(self, text):
299  """!Find item"""
300  item = self.GetFirstChild(self.root)[0]
301  while item and item.IsOk():
302  if text == self.GetItemText(item):
303  return item
304 
305  item = self.GetNextSibling(item)
306 
307  return None
308 
309  def Load(self, url, full = False):
310  """!Load list of extensions"""
311  self.DeleteAllItems()
312  self.root = self.AddRoot(_("Menu tree"))
313  self._initTree()
314 
315  if full:
316  flags = 'g'
317  else:
318  flags = 'l'
319  ret = RunCommand('g.extension.py', read = True, parent = self,
320  svnurl = url,
321  flags = flags, quiet = True)
322  if not ret:
323  return
324 
325  mdict = dict()
326  for line in ret.splitlines():
327  if full:
328  try:
329  key, value = line.split('=', 1)
330  except ValueError:
331  key = 'name'
332  value = line
333 
334  if key == 'name':
335  try:
336  prefix, name = value.split('.', 1)
337  except ValueError:
338  prefix = ''
339  name = value
340  if prefix not in mdict:
341  mdict[prefix] = dict()
342  mdict[prefix][name] = dict()
343  else:
344  mdict[prefix][name][key] = value
345  else:
346  try:
347  prefix, name = line.strip().split('.', 1)
348  except:
349  prefix = ''
350  name = line.strip()
351 
352  if self._expandPrefix(prefix) == prefix:
353  prefix = ''
354 
355  if prefix not in mdict:
356  mdict[prefix] = dict()
357 
358  mdict[prefix][name] = { 'command' : prefix + '.' + name }
359 
360  for prefix in mdict.keys():
361  prefixName = self._expandPrefix(prefix)
362  item = self._findItem(prefixName)
363  names = mdict[prefix].keys()
364  names.sort()
365  for name in names:
366  if prefix:
367  text = prefix + '.' + name
368  else:
369  text = name
370  new = self.AppendItem(parentId = item,
371  text = text)
372  data = dict()
373  for key in mdict[prefix][name].keys():
374  data[key] = mdict[prefix][name][key]
375 
376  self.SetPyData(new, data)
377 
378  self._loaded = True
379 
380  def IsLoaded(self):
381  """Check if items are loaded"""
382  return self._loaded
383 
384 class UninstallExtensionWindow(wx.Frame):
385  def __init__(self, parent, id = wx.ID_ANY,
386  title = _("Uninstall GRASS Addons extensions"), **kwargs):
387  self.parent = parent
388 
389  wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
390  self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
391 
392  self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
393 
394  self.extBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
395  label = " %s " % _("List of installed extensions"))
396 
397  self.extList = CheckListExtension(parent = self.panel)
398 
399  # buttons
400  self.btnUninstall = wx.Button(parent = self.panel, id = wx.ID_ANY,
401  label = _("&Uninstall"))
402  self.btnUninstall.SetToolTipString(_("Uninstall selected AddOns extensions"))
403  self.btnCmd = wx.Button(parent = self.panel, id = wx.ID_ANY,
404  label = _("Command dialog"))
405  self.btnCmd.SetToolTipString(_('Open %s dialog') % 'g.extension')
406  self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
407 
408  self.btnUninstall.Bind(wx.EVT_BUTTON, self.OnUninstall)
409  self.btnCmd.Bind(wx.EVT_BUTTON, self.OnCmdDialog)
410  self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
411 
412  self._layout()
413 
414  def _layout(self):
415  """!Do layout"""
416  sizer = wx.BoxSizer(wx.VERTICAL)
417 
418  extSizer = wx.StaticBoxSizer(self.extBox, wx.HORIZONTAL)
419  extSizer.Add(item = self.extList, proportion = 1,
420  flag = wx.ALL | wx.EXPAND, border = 1)
421 
422  btnSizer = wx.BoxSizer(wx.HORIZONTAL)
423  btnSizer.Add(item = self.btnCmd, proportion = 0,
424  flag = wx.RIGHT, border = 5)
425  btnSizer.AddSpacer(10)
426  btnSizer.Add(item = self.btnClose, proportion = 0,
427  flag = wx.RIGHT, border = 5)
428  btnSizer.Add(item = self.btnUninstall, proportion = 0)
429 
430  sizer.Add(item = extSizer, proportion = 1,
431  flag = wx.ALL | wx.EXPAND, border = 3)
432  sizer.Add(item = btnSizer, proportion = 0,
433  flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
434 
435  self.panel.SetSizer(sizer)
436  sizer.Fit(self.panel)
437 
438  self.Layout()
439 
440  def OnCloseWindow(self, event):
441  """!Close window"""
442  self.Destroy()
443 
444  def OnUninstall(self, event):
445  """!Uninstall selected extensions"""
446  log = self.parent.GetLogWindow()
447  eList = self.extList.GetExtensions()
448  if not eList:
449  GError(_("No extension selected for removal. "
450  "Operation canceled."),
451  parent = self)
452  return
453 
454  for ext in eList:
455  files = RunCommand('g.extension.py', parent = self, read = True, quiet = True,
456  extension = ext, operation = 'remove').splitlines()
457  dlg = wx.MessageDialog(parent = self,
458  message = _("List of files to be removed:\n%(files)s\n\n"
459  "Do you want really to remove <%(ext)s> extension?") % \
460  { 'files' : os.linesep.join(files), 'ext' : ext },
461  caption = _("Remove extension"),
462  style = wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
463 
464  if dlg.ShowModal() == wx.ID_YES:
465  RunCommand('g.extension.py', flags = 'f', parent = self, quiet = True,
466  extension = ext, operation = 'remove')
467 
468  self.extList.LoadData()
469 
470  # update prompt
472  log = self.parent.GetLogWindow()
473  log.GetPrompt().SetFilter(None)
474 
475  def OnCmdDialog(self, event):
476  """!Shows command dialog"""
477  GUI(parent = self).ParseCommand(cmd = ['g.extension.py'])
478 
479 class CheckListExtension(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.CheckListCtrlMixin):
480  """!List of mapset/owner/group"""
481  def __init__(self, parent):
482  self.parent = parent
483 
484  wx.ListCtrl.__init__(self, parent, id = wx.ID_ANY,
485  style = wx.LC_REPORT)
486  listmix.CheckListCtrlMixin.__init__(self)
487 
488  # setup mixins
489  listmix.ListCtrlAutoWidthMixin.__init__(self)
490 
491  self.InsertColumn(0, _('Extension'))
492  self.LoadData()
493 
494  def LoadData(self):
495  """!Load data into list"""
496  self.DeleteAllItems()
497  for ext in RunCommand('g.extension.py',
498  quiet = True, parent = self, read = True,
499  flags = 'a').splitlines():
500  if ext:
501  self.InsertStringItem(sys.maxint, ext)
502 
503  def GetExtensions(self):
504  """!Get extensions to be un-installed
505  """
506  extList = list()
507  for i in range(self.GetItemCount()):
508  if self.IsChecked(i):
509  name = self.GetItemText(i)
510  if name:
511  extList.append(name)
512 
513  return extList
wxGUI command interface
List of mapset/owner/group.
Definition: extensions.py:479
def Load
Load list of extensions.
Definition: extensions.py:309
def _findItem
Find item.
Definition: extensions.py:298
def OnShowItem
Show selected item.
Definition: extensions.py:244
Core GUI widgets.
def split
Platform spefic shlex.split.
Definition: core/utils.py:37
def OnInstall
Install selected extension.
Definition: extensions.py:218
def SetAddOnPath
Set default AddOn path.
Definition: core/utils.py:893
def OnUpdateStatusBar
Update statusbar text.
Definition: extensions.py:182
def OnFetch
Fetch list of available extensions.
Definition: extensions.py:204
def OnItemSelected
Item selected.
Definition: extensions.py:232
Help window.
Misc utilities for wxGUI.
def OnCmdDialog
Shows command dialog.
Definition: extensions.py:475
def OnCloseWindow
Close window.
Definition: extensions.py:200
def LoadData
Load data into list.
Definition: extensions.py:494
tuple range
Definition: tools.py:1406
def UpdateGRASSAddOnCommands
Update list of available GRASS AddOns commands to use when parsing string from the command line...
Definition: globalvar.py:159
def OnCmdDialog
Shows command dialog.
Definition: extensions.py:252
def GetExtensions
Get extensions to be un-installed.
Definition: extensions.py:503
def RunCommand
Run GRASS command.
Definition: gcmd.py:625
List of available extensions.
Definition: extensions.py:256
def OnUninstall
Uninstall selected extensions.
Definition: extensions.py:444