GRASS Programmer's Manual  6.5.svn(2012)-r51648
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
extensions.py
Go to the documentation of this file.
00001 """!
00002 @package modules.extensions
00003 
00004 @brief GRASS Addons extensions management classes
00005 
00006 Classes:
00007  - extensions::InstallExtensionWindow
00008  - extensions::ExtensionTree
00009  - extensions::UninstallExtensionWindow
00010  - extensions::CheckListExtension
00011 
00012 (C) 2008-2011 by the GRASS Development Team
00013 
00014 This program is free software under the GNU General Public License
00015 (>=v2). Read the file COPYING that comes with GRASS for details.
00016 
00017 @author Martin Landa <landa.martin gmail.com>
00018 """
00019 
00020 import os
00021 import sys
00022 
00023 import wx
00024 import wx.lib.mixins.listctrl as listmix
00025 try:
00026     import wx.lib.agw.customtreectrl as CT
00027 except ImportError:
00028     import wx.lib.customtreectrl as CT
00029 import wx.lib.flatnotebook as FN
00030 
00031 import grass.script as grass
00032 from grass.script import task as gtask
00033 
00034 from core             import globalvar
00035 from core.gcmd        import GError, RunCommand
00036 from gui_core.forms   import GUI
00037 from gui_core.widgets import ItemTree
00038 from gui_core.ghelp   import SearchModuleWindow
00039 
00040 class InstallExtensionWindow(wx.Frame):
00041     def __init__(self, parent, id = wx.ID_ANY,
00042                  title = _("Fetch & install extension from GRASS Addons"), **kwargs):
00043         self.parent = parent
00044         self.options = dict() # list of options
00045         
00046         wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
00047         self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
00048         
00049         self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
00050 
00051         self.repoBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
00052                                     label = " %s " % _("Repository"))
00053         self.treeBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
00054                                     label = " %s " % _("List of extensions"))
00055         
00056         self.repo = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY)
00057         self.fullDesc = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
00058                                     label = _("Fetch full info including description and keywords"))
00059         self.fullDesc.SetValue(True)
00060         
00061         self.search = SearchModuleWindow(parent = self.panel)
00062         self.search.SetSelection(0) 
00063         
00064         self.tree   = ExtensionTree(parent = self.panel, log = parent.GetLogWindow())
00065         
00066         self.optionBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
00067                                       label = " %s " % _("Options"))
00068         
00069         task = gtask.parse_interface('g.extension.py')
00070         
00071         ignoreFlags = ['l', 'c', 'g', 'a', 'f', 'quiet', 'verbose']
00072         if sys.platform == 'win32':
00073             ignoreFlags.append('d')
00074             ignoreFlags.append('i')
00075         
00076         for f in task.get_options()['flags']:
00077             name = f.get('name', '')
00078             desc = f.get('label', '')
00079             if not desc:
00080                 desc = f.get('description', '')
00081             if not name and not desc:
00082                 continue
00083             if name in ignoreFlags:
00084                 continue
00085             self.options[name] = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
00086                                              label = desc)
00087         self.repo.SetValue(task.get_param(value = 'svnurl').get('default',
00088                                                                 'http://svn.osgeo.org/grass/grass-addons'))
00089         
00090         self.statusbar = self.CreateStatusBar(number = 1)
00091         
00092         self.btnFetch = wx.Button(parent = self.panel, id = wx.ID_ANY,
00093                                   label = _("&Fetch"))
00094         self.btnFetch.SetToolTipString(_("Fetch list of available modules from GRASS Addons SVN repository"))
00095         self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
00096         self.btnInstall = wx.Button(parent = self.panel, id = wx.ID_ANY,
00097                                     label = _("&Install"))
00098         self.btnInstall.SetToolTipString(_("Install selected add-ons GRASS module"))
00099         self.btnInstall.Enable(False)
00100         self.btnCmd = wx.Button(parent = self.panel, id = wx.ID_ANY,
00101                                 label = _("Command dialog"))
00102         self.btnCmd.SetToolTipString(_('Open %s dialog') % 'g.extension.py')
00103 
00104         self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
00105         self.btnFetch.Bind(wx.EVT_BUTTON, self.OnFetch)
00106         self.btnInstall.Bind(wx.EVT_BUTTON, self.OnInstall)
00107         self.btnCmd.Bind(wx.EVT_BUTTON, self.OnCmdDialog)
00108         self.tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated)
00109         self.tree.Bind(wx.EVT_TREE_SEL_CHANGED,    self.OnItemSelected)
00110         self.search.Bind(wx.EVT_TEXT_ENTER,        self.OnShowItem)
00111         self.search.Bind(wx.EVT_TEXT,              self.OnUpdateStatusBar)
00112 
00113         self._layout()
00114 
00115     def _layout(self):
00116         """!Do layout"""
00117         sizer = wx.BoxSizer(wx.VERTICAL)
00118         repoSizer = wx.StaticBoxSizer(self.repoBox, wx.VERTICAL)
00119         repo1Sizer = wx.BoxSizer(wx.HORIZONTAL)
00120         repo1Sizer.Add(item = self.repo, proportion = 1,
00121                       flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
00122         repo1Sizer.Add(item = self.btnFetch, proportion = 0,
00123                       flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
00124         repoSizer.Add(item = repo1Sizer,
00125                       flag = wx.EXPAND)
00126         repoSizer.Add(item = self.fullDesc)
00127         
00128         findSizer = wx.BoxSizer(wx.HORIZONTAL)
00129         findSizer.Add(item = self.search, proportion = 1)
00130         
00131         treeSizer = wx.StaticBoxSizer(self.treeBox, wx.HORIZONTAL)
00132         treeSizer.Add(item = self.tree, proportion = 1,
00133                       flag = wx.ALL | wx.EXPAND, border = 1)
00134 
00135         # options
00136         optionSizer = wx.StaticBoxSizer(self.optionBox, wx.VERTICAL)
00137         for key in self.options.keys():
00138             optionSizer.Add(item = self.options[key], proportion = 0)
00139         
00140         btnSizer = wx.BoxSizer(wx.HORIZONTAL)
00141         btnSizer.Add(item = self.btnCmd, proportion = 0,
00142                      flag = wx.RIGHT, border = 5)
00143         btnSizer.AddSpacer(10)
00144         btnSizer.Add(item = self.btnClose, proportion = 0,
00145                      flag = wx.RIGHT, border = 5)
00146         btnSizer.Add(item = self.btnInstall, proportion = 0)
00147         
00148         sizer.Add(item = repoSizer, proportion = 0,
00149                   flag = wx.ALL | wx.EXPAND, border = 3)
00150         sizer.Add(item = findSizer, proportion = 0,
00151                   flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
00152         sizer.Add(item = treeSizer, proportion = 1,
00153                   flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
00154         sizer.Add(item = optionSizer, proportion = 0,
00155                         flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
00156         sizer.Add(item = btnSizer, proportion = 0,
00157                   flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
00158         
00159         self.panel.SetSizer(sizer)
00160         sizer.Fit(self.panel)
00161         
00162         self.Layout()
00163 
00164     def _getCmd(self):
00165         item = self.tree.GetSelected()
00166         if not item or not item.IsOk():
00167             return ['g.extension.py']
00168         
00169         name = self.tree.GetItemText(item)
00170         if not name:
00171             GError(_("Extension not defined"), parent = self)
00172             return
00173         flags = list()
00174         for key in self.options.keys():
00175             if self.options[key].IsChecked():
00176                 flags.append('-%s' % key)
00177         
00178         return ['g.extension.py'] + flags + ['extension=' + name,
00179                                              'svnurl=' + self.repo.GetValue().strip()]
00180     
00181     def OnUpdateStatusBar(self, event):
00182         """!Update statusbar text"""
00183         element = self.search.GetSelection()
00184         if not self.tree.IsLoaded():
00185             self.SetStatusText(_("Fetch list of available extensions by clicking on 'Fetch' button"), 0)
00186             return
00187         
00188         self.tree.SearchItems(element = element,
00189                               value = event.GetString())
00190         
00191         nItems = len(self.tree.itemsMarked)
00192         if event.GetString():
00193             self.SetStatusText(_("%d items match") % nItems, 0)
00194         else:
00195             self.SetStatusText("", 0)
00196         
00197         event.Skip()
00198     
00199     def OnCloseWindow(self, event):
00200         """!Close window"""
00201         self.Destroy()
00202 
00203     def OnFetch(self, event):
00204         """!Fetch list of available extensions"""
00205         wx.BeginBusyCursor()
00206         self.SetStatusText(_("Fetching list of modules from GRASS-Addons SVN (be patient)..."), 0)
00207         self.tree.Load(url = self.repo.GetValue().strip(), full = self.fullDesc.IsChecked())
00208         self.SetStatusText("", 0)
00209         wx.EndBusyCursor()
00210 
00211     def OnItemActivated(self, event):
00212         item = event.GetItem()
00213         data = self.tree.GetPyData(item)
00214         if data and 'command' in data:
00215             self.OnInstall(event = None)
00216         
00217     def OnInstall(self, event):
00218         """!Install selected extension"""
00219         log = self.parent.GetLogWindow()
00220         log.RunCmd(self._getCmd(), onDone = self.OnDone)
00221         
00222     def OnDone(self, cmd, returncode):
00223         item = self.tree.GetSelected()
00224         if not item or not item.IsOk() or \
00225                 returncode != 0 or \
00226                 not os.getenv('GRASS_ADDON_PATH'):
00227             return
00228         
00229         name = self.tree.GetItemText(item)
00230         globalvar.grassCmd.add(name)
00231         
00232     def OnItemSelected(self, event):
00233         """!Item selected"""
00234         item = event.GetItem()
00235         self.tree.itemSelected = item
00236         data = self.tree.GetPyData(item)
00237         if data is None:
00238             self.SetStatusText('', 0)
00239             self.btnInstall.Enable(False)
00240         else:
00241             self.SetStatusText(data.get('description', ''), 0)
00242             self.btnInstall.Enable(True)
00243 
00244     def OnShowItem(self, event):
00245         """!Show selected item"""
00246         self.tree.OnShowItem(event)
00247         if self.tree.GetSelected():
00248             self.btnInstall.Enable()
00249         else:
00250             self.btnInstall.Enable(False)
00251 
00252     def OnCmdDialog(self, event):
00253         """!Shows command dialog"""
00254         GUI(parent = self).ParseCommand(cmd = self._getCmd())
00255         
00256 class ExtensionTree(ItemTree):
00257     """!List of available extensions"""
00258     def __init__(self, parent, log, id = wx.ID_ANY,
00259                  ctstyle = CT.TR_HIDE_ROOT | CT.TR_FULL_ROW_HIGHLIGHT | CT.TR_HAS_BUTTONS |
00260                  CT.TR_LINES_AT_ROOT | CT.TR_SINGLE,
00261                  **kwargs):
00262         self.parent = parent # GMFrame
00263         self.log    = log
00264         
00265         super(ExtensionTree, self).__init__(parent, id, ctstyle = ctstyle, **kwargs)
00266         
00267         self._initTree()
00268         
00269     def _initTree(self):
00270         for prefix in ('display', 'database',
00271                        'general', 'imagery',
00272                        'misc', 'postscript', 'paint',
00273                        'raster', 'raster3d', 'sites', 'vector', 'wxGUI', 'other'):
00274             self.AppendItem(parentId = self.root,
00275                             text = prefix)
00276         self._loaded = False
00277         
00278     def _expandPrefix(self, c):
00279         name = { 'd'  : 'display',
00280                  'db' : 'database',
00281                  'g'  : 'general',
00282                  'i'  : 'imagery',
00283                  'm'  : 'misc',
00284                  'ps' : 'postscript',
00285                  'p'  : 'paint',
00286                  'r'  : 'raster',
00287                  'r3' : 'raster3d',
00288                  's'  : 'sites',
00289                  'v'  : 'vector',
00290                  'wx' : 'wxGUI',
00291                  ''   : 'other' }
00292         
00293         if c in name:
00294             return name[c]
00295         
00296         return c
00297     
00298     def _findItem(self, text):
00299         """!Find item"""
00300         item = self.GetFirstChild(self.root)[0]
00301         while item and item.IsOk():
00302             if text == self.GetItemText(item):
00303                 return item
00304             
00305             item = self.GetNextSibling(item)
00306         
00307         return None
00308     
00309     def Load(self, url, full = False):
00310         """!Load list of extensions"""
00311         self.DeleteAllItems()
00312         self.root = self.AddRoot(_("Menu tree"))
00313         self._initTree()
00314         
00315         if full:
00316             flags = 'g'
00317         else:
00318             flags = 'l'
00319         ret = RunCommand('g.extension.py', read = True, parent = self,
00320                          svnurl = url,
00321                          flags = flags, quiet = True)
00322         if not ret:
00323             return
00324         
00325         mdict = dict()
00326         for line in ret.splitlines():
00327             if full:
00328                 try:
00329                     key, value = line.split('=', 1)
00330                 except ValueError:
00331                     key = 'name'
00332                     value = line
00333                 
00334                 if key == 'name':
00335                     try:
00336                         prefix, name = value.split('.', 1)
00337                     except ValueError:
00338                         prefix = ''
00339                         name = value
00340                     if prefix not in mdict:
00341                         mdict[prefix] = dict()
00342                     mdict[prefix][name] = dict()
00343                 else:
00344                     mdict[prefix][name][key] = value
00345             else:
00346                 try:
00347                     prefix, name = line.strip().split('.', 1)
00348                 except:
00349                     prefix = ''
00350                     name = line.strip()
00351                 
00352                 if self._expandPrefix(prefix) == prefix:
00353                     prefix = ''
00354                     
00355                 if prefix not in mdict:
00356                     mdict[prefix] = dict()
00357                     
00358                 mdict[prefix][name] = { 'command' : prefix + '.' + name }
00359         
00360         for prefix in mdict.keys():
00361             prefixName = self._expandPrefix(prefix)
00362             item = self._findItem(prefixName)
00363             names = mdict[prefix].keys()
00364             names.sort()
00365             for name in names:
00366                 if prefix:
00367                     text = prefix + '.' + name
00368                 else:
00369                     text = name
00370                 new = self.AppendItem(parentId = item,
00371                                       text = text)
00372                 data = dict()
00373                 for key in mdict[prefix][name].keys():
00374                     data[key] = mdict[prefix][name][key]
00375                 
00376                 self.SetPyData(new, data)
00377         
00378         self._loaded = True
00379 
00380     def IsLoaded(self):
00381         """Check if items are loaded"""
00382         return self._loaded
00383 
00384 class UninstallExtensionWindow(wx.Frame):
00385     def __init__(self, parent, id = wx.ID_ANY,
00386                  title = _("Uninstall GRASS Addons extensions"), **kwargs):
00387         self.parent = parent
00388         
00389         wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
00390         self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
00391         
00392         self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
00393 
00394         self.extBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
00395                                    label = " %s " % _("List of installed extensions"))
00396         
00397         self.extList = CheckListExtension(parent = self.panel)
00398 
00399         # buttons
00400         self.btnUninstall = wx.Button(parent = self.panel, id = wx.ID_ANY,
00401                                     label = _("&Uninstall"))
00402         self.btnUninstall.SetToolTipString(_("Uninstall selected AddOns extensions"))
00403         self.btnCmd = wx.Button(parent = self.panel, id = wx.ID_ANY,
00404                                 label = _("Command dialog"))
00405         self.btnCmd.SetToolTipString(_('Open %s dialog') % 'g.extension')
00406         self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
00407         
00408         self.btnUninstall.Bind(wx.EVT_BUTTON, self.OnUninstall)
00409         self.btnCmd.Bind(wx.EVT_BUTTON, self.OnCmdDialog)
00410         self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
00411         
00412         self._layout()
00413         
00414     def _layout(self):
00415         """!Do layout"""
00416         sizer = wx.BoxSizer(wx.VERTICAL)
00417         
00418         extSizer = wx.StaticBoxSizer(self.extBox, wx.HORIZONTAL)
00419         extSizer.Add(item = self.extList, proportion = 1,
00420                      flag = wx.ALL | wx.EXPAND, border = 1)
00421         
00422         btnSizer = wx.BoxSizer(wx.HORIZONTAL)
00423         btnSizer.Add(item = self.btnCmd, proportion = 0,
00424                      flag = wx.RIGHT, border = 5)
00425         btnSizer.AddSpacer(10)
00426         btnSizer.Add(item = self.btnClose, proportion = 0,
00427                      flag = wx.RIGHT, border = 5)
00428         btnSizer.Add(item = self.btnUninstall, proportion = 0)
00429         
00430         sizer.Add(item = extSizer, proportion = 1,
00431                   flag = wx.ALL | wx.EXPAND, border = 3)
00432         sizer.Add(item = btnSizer, proportion = 0,
00433                   flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
00434         
00435         self.panel.SetSizer(sizer)
00436         sizer.Fit(self.panel)
00437         
00438         self.Layout()
00439 
00440     def OnCloseWindow(self, event):
00441         """!Close window"""
00442         self.Destroy()
00443 
00444     def OnUninstall(self, event):
00445         """!Uninstall selected extensions"""
00446         log = self.parent.GetLogWindow()
00447         eList = self.extList.GetExtensions()
00448         if not eList:
00449             GError(_("No extension selected for removal. "
00450                      "Operation canceled."),
00451                    parent = self)
00452             return
00453         
00454         for ext in eList:
00455             files = RunCommand('g.extension.py', parent = self, read = True, quiet = True,
00456                                extension = ext, operation = 'remove').splitlines()
00457             dlg = wx.MessageDialog(parent = self,
00458                                    message = _("List of files to be removed:\n%(files)s\n\n"
00459                                                "Do you want really to remove <%(ext)s> extension?") % \
00460                                        { 'files' : os.linesep.join(files), 'ext' : ext },
00461                                    caption = _("Remove extension"),
00462                                    style = wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
00463             
00464             if dlg.ShowModal() ==  wx.ID_YES:
00465                 RunCommand('g.extension.py', flags = 'f', parent = self, quiet = True,
00466                            extension = ext, operation = 'remove')
00467         
00468         self.extList.LoadData()
00469         
00470     def OnCmdDialog(self, event):
00471         """!Shows command dialog"""
00472         GUI(parent = self).ParseCommand(cmd = ['g.extension.py'])
00473 
00474 class CheckListExtension(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.CheckListCtrlMixin):
00475     """!List of mapset/owner/group"""
00476     def __init__(self, parent):
00477         self.parent = parent
00478         
00479         wx.ListCtrl.__init__(self, parent, id = wx.ID_ANY,
00480                              style = wx.LC_REPORT)
00481         listmix.CheckListCtrlMixin.__init__(self)
00482         
00483         # setup mixins
00484         listmix.ListCtrlAutoWidthMixin.__init__(self)
00485 
00486         self.InsertColumn(0, _('Extension'))
00487         self.LoadData()
00488         
00489     def LoadData(self):
00490         """!Load data into list"""
00491         self.DeleteAllItems()
00492         for ext in RunCommand('g.extension.py',
00493                               quiet = True, parent = self, read = True,
00494                               flags = 'a').splitlines():
00495             if ext:
00496                 self.InsertStringItem(sys.maxint, ext)
00497 
00498     def GetExtensions(self):
00499         """!Get extensions to be un-installed
00500         """
00501         extList = list()
00502         for i in range(self.GetItemCount()):
00503             if self.IsChecked(i):
00504                 name = self.GetItemText(i)
00505                 if name:
00506                     extList.append(name)
00507         
00508         return extList