GRASS Programmer's Manual  6.4.2(2012)
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
ghelp.py
Go to the documentation of this file.
1 """!
2 @package help.py
3 
4 @brief Help window
5 
6 Classes:
7  - SearchModuleWindow
8  - ItemTree
9  - MenuTreeWindow
10  - MenuTree
11  - AboutWindow
12  - InstallExtensionWindow
13  - ExtensionTree
14  - UninstallExtensionWindow
15  - CheckListExtension
16  - HelpFrame
17  - HelpWindow
18  - HelpPanel
19 
20 (C) 2008-2011 by the GRASS Development Team
21 This program is free software under the GNU General Public License
22 (>=v2). Read the file COPYING that comes with GRASS for details.
23 
24 @author Martin Landa <landa.martin gmail.com>
25 """
26 
27 import os
28 import codecs
29 import sys
30 
31 import wx
32 import wx.lib.mixins.listctrl as listmix
33 try:
34  import wx.lib.agw.customtreectrl as CT
35 # import wx.lib.agw.hyperlink as hl
36 except ImportError:
37  import wx.lib.customtreectrl as CT
38 # import wx.lib.hyperlink as hl
39 import wx.lib.flatnotebook as FN
40 import wx.lib.scrolledpanel as scrolled
41 
42 from grass.script import core as grass
43 from grass.script import task as gtask
44 
45 import menudata
46 import gcmd
47 import globalvar
48 import gdialogs
49 import utils
50 import menuform
51 
52 class HelpFrame(wx.Frame):
53  """!GRASS Quickstart help window"""
54  def __init__(self, parent, id, title, size, file):
55  wx.Frame.__init__(self, parent = parent, id = id, title = title, size = size)
56 
57  sizer = wx.BoxSizer(wx.VERTICAL)
58 
59  # text
60  content = HelpPanel(parent = self)
61  content.LoadPage(file)
62 
63  sizer.Add(item = content, proportion = 1, flag = wx.EXPAND)
64 
65  self.SetAutoLayout(True)
66  self.SetSizer(sizer)
67  self.Layout()
68 
69 class SearchModuleWindow(wx.Panel):
70  """!Search module window (used in MenuTreeWindow)"""
71  def __init__(self, parent, id = wx.ID_ANY, cmdPrompt = None,
72  showChoice = True, showTip = False, **kwargs):
73  self.showTip = showTip
74  self.showChoice = showChoice
75  self.cmdPrompt = cmdPrompt
76 
77  wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
78 
79  self._searchDict = { _('description') : 'description',
80  _('command') : 'command',
81  _('keywords') : 'keywords' }
82 
83  self.box = wx.StaticBox(parent = self, id = wx.ID_ANY,
84  label = " %s " % _("Find module(s)"))
85 
86  self.searchBy = wx.Choice(parent = self, id = wx.ID_ANY,
87  choices = [_('description'),
88  _('keywords'),
89  _('command')])
90  self.searchBy.SetSelection(0)
91 
92  self.search = wx.TextCtrl(parent = self, id = wx.ID_ANY,
93  value = "", size = (-1, 25),
94  style = wx.TE_PROCESS_ENTER)
95  self.search.Bind(wx.EVT_TEXT, self.OnSearchModule)
96 
97  if self.showTip:
98  self.searchTip = gdialogs.StaticWrapText(parent = self, id = wx.ID_ANY,
99  size = (-1, 35))
100 
101  if self.showChoice:
102  self.searchChoice = wx.Choice(parent = self, id = wx.ID_ANY)
103  if self.cmdPrompt:
104  self.searchChoice.SetItems(self.cmdPrompt.GetCommandItems())
105  self.searchChoice.Bind(wx.EVT_CHOICE, self.OnSelectModule)
106 
107  self._layout()
108 
109  def _layout(self):
110  """!Do layout"""
111  sizer = wx.StaticBoxSizer(self.box, wx.HORIZONTAL)
112  gridSizer = wx.GridBagSizer(hgap = 3, vgap = 3)
113  gridSizer.AddGrowableCol(1)
114 
115  gridSizer.Add(item = self.searchBy,
116  flag = wx.ALIGN_CENTER_VERTICAL, pos = (0, 0))
117  gridSizer.Add(item = self.search,
118  flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (0, 1))
119  row = 1
120  if self.showTip:
121  gridSizer.Add(item = self.searchTip,
122  flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (row, 0), span = (1, 2))
123  row += 1
124 
125  if self.showChoice:
126  gridSizer.Add(item = self.searchChoice,
127  flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (row, 0), span = (1, 2))
128 
129  sizer.Add(item = gridSizer, proportion = 1)
130 
131  self.SetSizer(sizer)
132  sizer.Fit(self)
133 
134  def GetSelection(self):
135  """!Get selected element"""
136  selection = self.searchBy.GetStringSelection()
137 
138  return self._searchDict[selection]
139 
140  def SetSelection(self, i):
141  """!Set selection element"""
142  self.searchBy.SetSelection(i)
143 
144  def OnSearchModule(self, event):
145  """!Search module by keywords or description"""
146  if not self.cmdPrompt:
147  event.Skip()
148  return
149 
150  text = event.GetString()
151  if not text:
152  self.cmdPrompt.SetFilter(None)
153  mList = self.cmdPrompt.GetCommandItems()
154  self.searchChoice.SetItems(mList)
155  if self.showTip:
156  self.searchTip.SetLabel(_("%d modules found") % len(mList))
157  event.Skip()
158  return
159 
160  modules = dict()
161  iFound = 0
162  for module, data in self.cmdPrompt.moduleDesc.iteritems():
163  found = False
164  sel = self.searchBy.GetSelection()
165  if sel == 0: # -> description
166  if text in data['desc']:
167  found = True
168  elif sel == 1: # keywords
169  if text in ','.join(data['keywords']):
170  found = True
171  else: # command
172  if module[:len(text)] == text:
173  found = True
174 
175  if found:
176  iFound += 1
177  try:
178  group, name = module.split('.')
179  except ValueError:
180  continue # TODO
181 
182  if group not in modules:
183  modules[group] = list()
184  modules[group].append(name)
185 
186  self.cmdPrompt.SetFilter(modules)
187  self.searchChoice.SetItems(self.cmdPrompt.GetCommandItems())
188  if self.showTip:
189  self.searchTip.SetLabel(_("%d modules found") % iFound)
190 
191  event.Skip()
192 
193  def OnSelectModule(self, event):
194  """!Module selected from choice, update command prompt"""
195  cmd = event.GetString().split(' ', 1)[0]
196  text = cmd + ' '
197  pos = len(text)
198 
199  if self.cmdPrompt:
200  self.cmdPrompt.SetText(text)
201  self.cmdPrompt.SetSelectionStart(pos)
202  self.cmdPrompt.SetCurrentPos(pos)
203  self.cmdPrompt.SetFocus()
204 
205  desc = self.cmdPrompt.GetCommandDesc(cmd)
206  if self.showTip:
207  self.searchTip.SetLabel(desc)
208 
209  def Reset(self):
210  """!Reset widget"""
211  self.searchBy.SetSelection(0)
212  self.search.SetValue('')
213  if self.showTip:
214  self.searchTip.SetLabel('')
215 
216 class MenuTreeWindow(wx.Panel):
217  """!Show menu tree"""
218  def __init__(self, parent, id = wx.ID_ANY, **kwargs):
219  self.parent = parent # LayerManager
220 
221  wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
222 
223  self.dataBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
224  label = " %s " % _("Menu tree (double-click to run command)"))
225  # tree
226  self.tree = MenuTree(parent = self, data = menudata.ManagerData())
227  self.tree.Load()
228 
229  # search widget
230  self.search = SearchModuleWindow(parent = self, showChoice = False)
231 
232  # buttons
233  self.btnRun = wx.Button(self, id = wx.ID_OK, label = _("&Run"))
234  self.btnRun.SetToolTipString(_("Run selected command"))
235  self.btnRun.Enable(False)
236 
237  # bindings
238  self.btnRun.Bind(wx.EVT_BUTTON, self.OnRun)
239  self.tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated)
240  self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnItemSelected)
241  self.search.Bind(wx.EVT_TEXT_ENTER, self.OnShowItem)
242  self.search.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
243 
244  self._layout()
245 
246  self.search.SetFocus()
247 
248  def _layout(self):
249  """!Do dialog layout"""
250  sizer = wx.BoxSizer(wx.VERTICAL)
251 
252  # body
253  dataSizer = wx.StaticBoxSizer(self.dataBox, wx.HORIZONTAL)
254  dataSizer.Add(item = self.tree, proportion =1,
255  flag = wx.EXPAND)
256 
257  # buttons
258  btnSizer = wx.BoxSizer(wx.HORIZONTAL)
259  btnSizer.Add(item = self.btnRun, proportion = 0)
260 
261  sizer.Add(item = dataSizer, proportion = 1,
262  flag = wx.EXPAND | wx.ALL, border = 5)
263 
264  sizer.Add(item = self.search, proportion = 0,
265  flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
266 
267  sizer.Add(item = btnSizer, proportion = 0,
268  flag = wx.ALIGN_RIGHT | wx.BOTTOM | wx.RIGHT, border = 5)
269 
270  sizer.Fit(self)
271  sizer.SetSizeHints(self)
272 
273  self.SetSizer(sizer)
274 
275  self.Fit()
276  self.SetAutoLayout(True)
277  self.Layout()
278 
279  def OnCloseWindow(self, event):
280  """!Close window"""
281  self.Destroy()
282 
283  def OnRun(self, event):
284  """!Run selected command"""
285  if not self.tree.GetSelected():
286  return # should not happen
287 
288  data = self.tree.GetPyData(self.tree.GetSelected())
289  if not data:
290  return
291 
292  handler = 'self.parent.' + data['handler'].lstrip('self.')
293  if data['handler'] == 'self.OnXTerm':
294  wx.MessageBox(parent = self,
295  message = _('You must run this command from the menu or command line',
296  'This command require an XTerm'),
297  caption = _('Message'), style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
298  elif data['command']:
299  eval(handler)(event = None, cmd = data['command'].split())
300  else:
301  eval(handler)(None)
302 
303  def OnShowItem(self, event):
304  """!Show selected item"""
305  self.tree.OnShowItem(event)
306  if self.tree.GetSelected():
307  self.btnRun.Enable()
308  else:
309  self.btnRun.Enable(False)
310 
311  def OnItemActivated(self, event):
312  """!Item activated (double-click)"""
313  item = event.GetItem()
314  if not item or not item.IsOk():
315  return
316 
317  data = self.tree.GetPyData(item)
318  if not data or 'command' not in data:
319  return
320 
321  self.tree.itemSelected = item
322 
323  self.OnRun(None)
324 
325  def OnItemSelected(self, event):
326  """!Item selected"""
327  item = event.GetItem()
328  if not item or not item.IsOk():
329  return
330 
331  data = self.tree.GetPyData(item)
332  if not data or 'command' not in data:
333  return
334 
335  if data['command']:
336  label = data['command'] + ' -- ' + data['description']
337  else:
338  label = data['description']
339 
340  self.parent.SetStatusText(label, 0)
341 
342  def OnUpdateStatusBar(self, event):
343  """!Update statusbar text"""
344  element = self.search.GetSelection()
345  self.tree.SearchItems(element = element,
346  value = event.GetString())
347 
348  nItems = len(self.tree.itemsMarked)
349  if event.GetString():
350  self.parent.SetStatusText(_("%d modules match") % nItems, 0)
351  else:
352  self.parent.SetStatusText("", 0)
353 
354  event.Skip()
355 
356 class ItemTree(CT.CustomTreeCtrl):
357  def __init__(self, parent, id = wx.ID_ANY,
358  ctstyle = CT.TR_HIDE_ROOT | CT.TR_FULL_ROW_HIGHLIGHT | CT.TR_HAS_BUTTONS |
359  CT.TR_LINES_AT_ROOT | CT.TR_SINGLE, **kwargs):
360  if globalvar.hasAgw:
361  super(ItemTree, self).__init__(parent, id, agwStyle = ctstyle, **kwargs)
362  else:
363  super(ItemTree, self).__init__(parent, id, style = ctstyle, **kwargs)
364 
365  self.root = self.AddRoot(_("Menu tree"))
366  self.itemsMarked = [] # list of marked items
367  self.itemSelected = None
368 
369  def SearchItems(self, element, value):
370  """!Search item
371 
372  @param element element index (see self.searchBy)
373  @param value
374 
375  @return list of found tree items
376  """
377  items = list()
378  if not value:
379  return items
380 
381  item = self.GetFirstChild(self.root)[0]
382  self._processItem(item, element, value, items)
383 
384  self.itemsMarked = items
385  self.itemSelected = None
386 
387  return items
388 
389  def _processItem(self, item, element, value, listOfItems):
390  """!Search items (used by SearchItems)
391 
392  @param item reference item
393  @param listOfItems list of found items
394  """
395  while item and item.IsOk():
396  subItem = self.GetFirstChild(item)[0]
397  if subItem:
398  self._processItem(subItem, element, value, listOfItems)
399  data = self.GetPyData(item)
400 
401  if data and element in data and \
402  value.lower() in data[element].lower():
403  listOfItems.append(item)
404 
405  item = self.GetNextSibling(item)
406 
407  def GetSelected(self):
408  """!Get selected item"""
409  return self.itemSelected
410 
411  def OnShowItem(self, event):
412  """!Highlight first found item in menu tree"""
413  if len(self.itemsMarked) > 0:
414  if self.GetSelected():
415  self.ToggleItemSelection(self.GetSelected())
416  idx = self.itemsMarked.index(self.GetSelected()) + 1
417  else:
418  idx = 0
419  try:
420  self.ToggleItemSelection(self.itemsMarked[idx])
421  self.itemSelected = self.itemsMarked[idx]
422  self.EnsureVisible(self.itemsMarked[idx])
423  except IndexError:
424  self.ToggleItemSelection(self.itemsMarked[0]) # reselect first item
425  self.EnsureVisible(self.itemsMarked[0])
426  self.itemSelected = self.itemsMarked[0]
427  else:
428  for item in self.root.GetChildren():
429  self.Collapse(item)
430  itemSelected = self.GetSelection()
431  if itemSelected:
432  self.ToggleItemSelection(itemSelected)
433  self.itemSelected = None
434 
436  """!Menu tree class"""
437  def __init__(self, parent, data, **kwargs):
438  self.parent = parent
439  self.menudata = data
440 
441  super(MenuTree, self).__init__(parent, **kwargs)
442 
443  def Load(self, data = None):
444  """!Load menu data tree
445 
446  @param data menu data (None to use self.menudata)
447  """
448  if not data:
449  data = self.menudata
450 
451  self.itemsMarked = [] # list of marked items
452  for eachMenuData in data.GetMenu():
453  for label, items in eachMenuData:
454  item = self.AppendItem(parentId = self.root,
455  text = label.replace('&', ''))
456  self.__AppendItems(item, items)
457 
458  def __AppendItems(self, item, data):
459  """!Append items into tree (used by Load()
460 
461  @param item tree item (parent)
462  @parent data menu data"""
463  for eachItem in data:
464  if len(eachItem) == 2:
465  if eachItem[0]:
466  itemSub = self.AppendItem(parentId = item,
467  text = eachItem[0])
468  self.__AppendItems(itemSub, eachItem[1])
469  else:
470  if eachItem[0]:
471  itemNew = self.AppendItem(parentId = item,
472  text = eachItem[0])
473 
474  data = { 'item' : eachItem[0],
475  'description' : eachItem[1],
476  'handler' : eachItem[2],
477  'command' : eachItem[3],
478  'keywords' : eachItem[4] }
479 
480  self.SetPyData(itemNew, data)
481 
482 class AboutWindow(wx.Frame):
483  """!Create custom About Window
484 
485  @todo improve styling
486  """
487  def __init__(self, parent, size = (750, 400),
488  title = _('About GRASS GIS'), **kwargs):
489  wx.Frame.__init__(self, parent = parent, id = wx.ID_ANY, size = size, **kwargs)
490 
491  panel = wx.Panel(parent = self, id = wx.ID_ANY)
492 
493  # icon
494  self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
495 
496  # get version and web site
497  vInfo = grass.version()
498 
499  infoTxt = wx.Panel(parent = panel, id = wx.ID_ANY)
500  infoSizer = wx.BoxSizer(wx.VERTICAL)
501  infoGridSizer = wx.GridBagSizer(vgap = 5, hgap = 5)
502  infoGridSizer.AddGrowableCol(0)
503  infoGridSizer.AddGrowableCol(1)
504  logo = os.path.join(globalvar.ETCDIR, "gui", "icons", "grass.ico")
505  logoBitmap = wx.StaticBitmap(parent = infoTxt, id = wx.ID_ANY,
506  bitmap = wx.Bitmap(name = logo,
507  type = wx.BITMAP_TYPE_ICO))
508  infoSizer.Add(item = logoBitmap, proportion = 0,
509  flag = wx.ALL | wx.ALIGN_CENTER, border = 25)
510 
511  info = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
512  label = 'GRASS GIS ' + vInfo['version'] + '\n\n')
513  info.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.BOLD, 0, ""))
514  infoSizer.Add(item = info, proportion = 0,
515  flag = wx.BOTTOM | wx.ALIGN_CENTER, border = 15)
516 
517  infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
518  label = _('Official GRASS site:')),
519  pos = (0, 0),
520  flag = wx.ALIGN_RIGHT)
521 
522  infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
523  label = 'http://grass.osgeo.org'),
524  pos = (0, 1),
525  flag = wx.ALIGN_LEFT)
526 
527  infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
528  label = _('SVN Revision:')),
529  pos = (2, 0),
530  flag = wx.ALIGN_RIGHT)
531 
532  infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
533  label = vInfo['revision']),
534  pos = (2, 1),
535  flag = wx.ALIGN_LEFT)
536 
537  infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
538  label = _('GIS Library Revision:')),
539  pos = (3, 0),
540  flag = wx.ALIGN_RIGHT)
541 
542  infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
543  label = vInfo['libgis_revision'] + ' (' +
544  vInfo['libgis_date'].split(' ')[0] + ')'),
545  pos = (3, 1),
546  flag = wx.ALIGN_LEFT)
547 
548  infoSizer.Add(item = infoGridSizer,
549  proportion = 1,
550  flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL,
551  border = 25)
552 
553  # create a flat notebook for displaying information about GRASS
554  aboutNotebook = menuform.GNotebook(panel, style = globalvar.FNPageStyle | FN.FNB_NO_X_BUTTON)
555  aboutNotebook.SetTabAreaColour(globalvar.FNPageColor)
556 
557  for title, win in ((_("Info"), infoTxt),
558  (_("Copyright"), self._pageCopyright()),
559  (_("License"), self._pageLicense()),
560  (_("Authors"), self._pageCredit()),
561  (_("Contributors"), self._pageContributors()),
562  (_("Extra contributors"), self._pageContributors(extra = True)),
563  (_("Translators"), self._pageTranslators())):
564  aboutNotebook.AddPage(page = win, text = title)
565  wx.CallAfter(aboutNotebook.SetSelection, 0)
566 
567  # buttons
568  btnClose = wx.Button(parent = panel, id = wx.ID_CLOSE)
569  btnSizer = wx.BoxSizer(wx.HORIZONTAL)
570  btnSizer.Add(item = btnClose, proportion = 0,
571  flag = wx.ALL | wx.ALIGN_RIGHT,
572  border = 5)
573  # bindings
574  btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
575 
576  infoTxt.SetSizer(infoSizer)
577  infoSizer.Fit(infoTxt)
578 
579  sizer = wx.BoxSizer(wx.VERTICAL)
580  sizer.Add(item = aboutNotebook, proportion = 1,
581  flag = wx.EXPAND | wx.ALL, border = 1)
582  sizer.Add(item = btnSizer, proportion = 0,
583  flag = wx.ALL | wx.ALIGN_RIGHT, border = 1)
584  panel.SetSizer(sizer)
585  self.Layout()
586 
587  def _pageCopyright(self):
588  """Copyright information"""
589  copyfile = os.path.join(os.getenv("GISBASE"), "COPYING")
590  if os.path.exists(copyfile):
591  copyrightFile = open(copyfile, 'r')
592  copytext = copyrightFile.read()
593  copyrightFile.close()
594  else:
595  copytext = _('%s file missing') % 'COPYING'
596 
597  # put text into a scrolling panel
598  copyrightwin = scrolled.ScrolledPanel(self, id = wx.ID_ANY,
599  size = wx.DefaultSize,
600  style = wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER)
601  copyrighttxt = wx.StaticText(copyrightwin, id = wx.ID_ANY, label = copytext)
602  copyrightwin.SetAutoLayout(True)
603  copyrightwin.sizer = wx.BoxSizer(wx.VERTICAL)
604  copyrightwin.sizer.Add(item = copyrighttxt, proportion = 1,
605  flag = wx.EXPAND | wx.ALL, border = 3)
606  copyrightwin.SetSizer(copyrightwin.sizer)
607  copyrightwin.Layout()
608  copyrightwin.SetupScrolling()
609 
610  return copyrightwin
611 
612  def _pageLicense(self):
613  """Licence about"""
614  licfile = os.path.join(os.getenv("GISBASE"), "GPL.TXT")
615  if os.path.exists(licfile):
616  licenceFile = open(licfile, 'r')
617  license = ''.join(licenceFile.readlines())
618  licenceFile.close()
619  else:
620  license = _('%s file missing') % 'GPL.TXT'
621  # put text into a scrolling panel
622  licensewin = scrolled.ScrolledPanel(self, id = wx.ID_ANY,
623  style = wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER)
624  licensetxt = wx.StaticText(licensewin, id = wx.ID_ANY, label = license)
625  licensewin.SetAutoLayout(True)
626  licensewin.sizer = wx.BoxSizer(wx.VERTICAL)
627  licensewin.sizer.Add(item = licensetxt, proportion = 1,
628  flag = wx.EXPAND | wx.ALL, border = 3)
629  licensewin.SetSizer(licensewin.sizer)
630  licensewin.Layout()
631  licensewin.SetupScrolling()
632 
633  return licensewin
634 
635  def _pageCredit(self):
636  """Credit about"""
637  # credits
638  authfile = os.path.join(os.getenv("GISBASE"), "AUTHORS")
639  if os.path.exists(authfile):
640  authorsFile = open(authfile, 'r')
641  authors = unicode(''.join(authorsFile.readlines()), "utf-8")
642  authorsFile.close()
643  else:
644  authors = _('%s file missing') % 'AUTHORS'
645  authorwin = scrolled.ScrolledPanel(self, id = wx.ID_ANY,
646  style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
647  authortxt = wx.StaticText(authorwin, id = wx.ID_ANY, label = authors)
648  authorwin.SetAutoLayout(1)
649  authorwin.SetupScrolling()
650  authorwin.sizer = wx.BoxSizer(wx.VERTICAL)
651  authorwin.sizer.Add(item = authortxt, proportion = 1,
652  flag = wx.EXPAND | wx.ALL, border = 3)
653  authorwin.SetSizer(authorwin.sizer)
654  authorwin.Layout()
655 
656  return authorwin
657 
658  def _pageContributors(self, extra = False):
659  """Contributors info"""
660  if extra:
661  contribfile = os.path.join(os.getenv("GISBASE"), "contributors_extra.csv")
662  else:
663  contribfile = os.path.join(os.getenv("GISBASE"), "contributors.csv")
664  if os.path.exists(contribfile):
665  contribFile = codecs.open(contribfile, encoding = 'utf-8', mode = 'r')
666  contribs = list()
667  errLines = list()
668  for line in contribFile.readlines()[1:]:
669  line = line.rstrip('\n')
670  try:
671  if extra:
672  name, email, rfc2_agreed = line.split(',')
673  else:
674  cvs_id, name, email, country, osgeo_id, rfc2_agreed = line.split(',')
675  except ValueError:
676  errLines.append(line)
677  continue
678  if extra:
679  contribs.append((name, email))
680  else:
681  contribs.append((name, email, country, osgeo_id))
682 
683  contribFile.close()
684 
685  if errLines:
686  gcmd.GError(parent = self,
687  message = _("Error when reading file '%s'.") % contribfile + \
688  "\n\n" + _("Lines:") + " %s" % \
689  os.linesep.join(map(utils.UnicodeString, errLines)))
690  else:
691  contribs = None
692 
693  contribwin = scrolled.ScrolledPanel(self, id = wx.ID_ANY,
694  style = wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER)
695  contribwin.SetAutoLayout(True)
696  contribwin.SetupScrolling()
697  contribwin.sizer = wx.BoxSizer(wx.VERTICAL)
698 
699  if not contribs:
700  contribtxt = wx.StaticText(contribwin, id = wx.ID_ANY,
701  label = _('%s file missing') % contribfile)
702  contribwin.sizer.Add(item = contribtxt, proportion = 1,
703  flag = wx.EXPAND | wx.ALL, border = 3)
704  else:
705  if extra:
706  items = (_('Name'), _('E-mail'))
707  else:
708  items = (_('Name'), _('E-mail'), _('Country'), _('OSGeo_ID'))
709  contribBox = wx.FlexGridSizer(cols = len(items), vgap = 5, hgap = 5)
710  for item in items:
711  contribBox.Add(item = wx.StaticText(parent = contribwin, id = wx.ID_ANY,
712  label = item))
713  for vals in contribs:
714  for item in vals:
715  contribBox.Add(item = wx.StaticText(parent = contribwin, id = wx.ID_ANY,
716  label = item))
717  contribwin.sizer.Add(item = contribBox, proportion = 1,
718  flag = wx.EXPAND | wx.ALL, border = 3)
719 
720  contribwin.SetSizer(contribwin.sizer)
721  contribwin.Layout()
722 
723  return contribwin
724 
725  def _pageTranslators(self):
726  """Translators info"""
727  translatorsfile = os.path.join(os.getenv("GISBASE"), "translators.csv")
728  if os.path.exists(translatorsfile):
729  translatorsFile = open(translatorsfile, 'r')
730  translators = dict()
731  errLines = list()
732  for line in translatorsFile.readlines()[1:]:
733  line = line.rstrip('\n')
734  try:
735  name, email, languages = line.split(',')
736  except ValueError:
737  errLines.append(line)
738  continue
739  for language in languages.split(' '):
740  if language not in translators:
741  translators[language] = list()
742  translators[language].append((name, email))
743  translatorsFile.close()
744 
745  if errLines:
746  gcmd.GError(parent = self,
747  message = _("Error when reading file '%s'.") % translatorsfile + \
748  "\n\n" + _("Lines:") + " %s" % \
749  os.linesep.join(map(utils.UnicodeString, errLines)))
750  else:
751  translators = None
752 
753  translatorswin = scrolled.ScrolledPanel(self, id = wx.ID_ANY,
754  style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
755  translatorswin.SetAutoLayout(1)
756  translatorswin.SetupScrolling()
757  translatorswin.sizer = wx.BoxSizer(wx.VERTICAL)
758 
759  if not translators:
760  translatorstxt = wx.StaticText(translatorswin, id = wx.ID_ANY,
761  label = _('%s file missing') % 'translators.csv')
762  translatorswin.sizer.Add(item = translatorstxt, proportion = 1,
763  flag = wx.EXPAND | wx.ALL, border = 3)
764  else:
765  translatorsBox = wx.FlexGridSizer(cols = 3, vgap = 5, hgap = 5)
766  languages = translators.keys()
767  languages.sort()
768  translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
769  label = _('Name')))
770  translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
771  label = _('E-mail')))
772  translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
773  label = _('Language')))
774  for lang in languages:
775  for translator in translators[lang]:
776  name, email = translator
777  translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
778  label = unicode(name, "utf-8")))
779  translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
780  label = email))
781  translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
782  label = lang))
783 
784  translatorswin.sizer.Add(item = translatorsBox, proportion = 1,
785  flag = wx.EXPAND | wx.ALL, border = 3)
786 
787  translatorswin.SetSizer(translatorswin.sizer)
788  translatorswin.Layout()
789 
790  return translatorswin
791 
792  def OnCloseWindow(self, event):
793  """!Close window"""
794  self.Close()
795 
796 class InstallExtensionWindow(wx.Frame):
797  def __init__(self, parent, id = wx.ID_ANY,
798  title = _("Fetch & install extension from GRASS Addons"), **kwargs):
799  self.parent = parent
800  self.options = dict() # list of options
801 
802  wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
803  self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
804 
805  self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
806 
807  self.repoBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
808  label = " %s " % _("Repository"))
809  self.treeBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
810  label = " %s " % _("List of extensions"))
811 
812  self.repo = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY)
813  self.fullDesc = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
814  label = _("Fetch full info including description and keywords"))
815  self.fullDesc.SetValue(True)
816 
817  self.search = SearchModuleWindow(parent = self.panel)
818  self.search.SetSelection(0)
819 
820  self.tree = ExtensionTree(parent = self.panel, log = parent.GetLogWindow())
821 
822  self.optionBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
823  label = " %s " % _("Options"))
824 
825  task = gtask.parse_interface('g.extension.py')
826 
827  ignoreFlags = ['l', 'c', 'g', 'a', 'f', 'quiet', 'verbose']
828  if sys.platform == 'win32':
829  ignoreFlags.append('d')
830  ignoreFlags.append('i')
831 
832  for f in task.get_options()['flags']:
833  name = f.get('name', '')
834  desc = f.get('label', '')
835  if not desc:
836  desc = f.get('description', '')
837  if not name and not desc:
838  continue
839  if name in ignoreFlags:
840  continue
841  self.options[name] = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
842  label = desc)
843  self.repo.SetValue(task.get_param(value = 'svnurl').get('default',
844  'http://svn.osgeo.org/grass/grass-addons'))
845 
846  self.statusbar = self.CreateStatusBar(number = 1)
847 
848  self.btnFetch = wx.Button(parent = self.panel, id = wx.ID_ANY,
849  label = _("&Fetch"))
850  self.btnFetch.SetToolTipString(_("Fetch list of available modules from GRASS Addons SVN repository"))
851  self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
852  self.btnInstall = wx.Button(parent = self.panel, id = wx.ID_ANY,
853  label = _("&Install"))
854  self.btnInstall.SetToolTipString(_("Install selected add-ons GRASS module"))
855  self.btnInstall.Enable(False)
856  self.btnCmd = wx.Button(parent = self.panel, id = wx.ID_ANY,
857  label = _("Command dialog"))
858  self.btnCmd.SetToolTipString(_('Open %s dialog') % 'g.extension.py')
859 
860  self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
861  self.btnFetch.Bind(wx.EVT_BUTTON, self.OnFetch)
862  self.btnInstall.Bind(wx.EVT_BUTTON, self.OnInstall)
863  self.btnCmd.Bind(wx.EVT_BUTTON, self.OnCmdDialog)
864  self.tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated)
865  self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnItemSelected)
866  self.search.Bind(wx.EVT_TEXT_ENTER, self.OnShowItem)
867  self.search.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
868 
869  self._layout()
870 
871  def _layout(self):
872  """!Do layout"""
873  sizer = wx.BoxSizer(wx.VERTICAL)
874  repoSizer = wx.StaticBoxSizer(self.repoBox, wx.VERTICAL)
875  repo1Sizer = wx.BoxSizer(wx.HORIZONTAL)
876  repo1Sizer.Add(item = self.repo, proportion = 1,
877  flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
878  repo1Sizer.Add(item = self.btnFetch, proportion = 0,
879  flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
880  repoSizer.Add(item = repo1Sizer,
881  flag = wx.EXPAND)
882  repoSizer.Add(item = self.fullDesc)
883 
884  findSizer = wx.BoxSizer(wx.HORIZONTAL)
885  findSizer.Add(item = self.search, proportion = 1)
886 
887  treeSizer = wx.StaticBoxSizer(self.treeBox, wx.HORIZONTAL)
888  treeSizer.Add(item = self.tree, proportion = 1,
889  flag = wx.ALL | wx.EXPAND, border = 1)
890 
891  # options
892  optionSizer = wx.StaticBoxSizer(self.optionBox, wx.VERTICAL)
893  for key in self.options.keys():
894  optionSizer.Add(item = self.options[key], proportion = 0)
895 
896  btnSizer = wx.BoxSizer(wx.HORIZONTAL)
897  btnSizer.Add(item = self.btnCmd, proportion = 0,
898  flag = wx.RIGHT, border = 5)
899  btnSizer.AddSpacer(10)
900  btnSizer.Add(item = self.btnClose, proportion = 0,
901  flag = wx.RIGHT, border = 5)
902  btnSizer.Add(item = self.btnInstall, proportion = 0)
903 
904  sizer.Add(item = repoSizer, proportion = 0,
905  flag = wx.ALL | wx.EXPAND, border = 3)
906  sizer.Add(item = findSizer, proportion = 0,
907  flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
908  sizer.Add(item = treeSizer, proportion = 1,
909  flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
910  sizer.Add(item = optionSizer, proportion = 0,
911  flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
912  sizer.Add(item = btnSizer, proportion = 0,
913  flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
914 
915  self.panel.SetSizer(sizer)
916  sizer.Fit(self.panel)
917 
918  self.Layout()
919 
920  def _getCmd(self):
921  item = self.tree.GetSelected()
922  if not item or not item.IsOk():
923  return ['g.extension.py']
924 
925  name = self.tree.GetItemText(item)
926  if not name:
927  gcmd.GError(_("Extension not defined"), parent = self)
928  return
929  flags = list()
930  for key in self.options.keys():
931  if self.options[key].IsChecked():
932  flags.append('-%s' % key)
933 
934  return ['g.extension.py'] + flags + ['extension=' + name,
935  'svnurl=' + self.repo.GetValue().strip()]
936 
937  def OnUpdateStatusBar(self, event):
938  """!Update statusbar text"""
939  element = self.search.GetSelection()
940  if not self.tree.IsLoaded():
941  self.SetStatusText(_("Fetch list of available extensions by clicking on 'Fetch' button"), 0)
942  return
943 
944  self.tree.SearchItems(element = element,
945  value = event.GetString())
946 
947  nItems = len(self.tree.itemsMarked)
948  if event.GetString():
949  self.SetStatusText(_("%d items match") % nItems, 0)
950  else:
951  self.SetStatusText("", 0)
952 
953  event.Skip()
954 
955  def OnCloseWindow(self, event):
956  """!Close window"""
957  self.Destroy()
958 
959  def OnFetch(self, event):
960  """!Fetch list of available extensions"""
961  wx.BeginBusyCursor()
962  self.SetStatusText(_("Fetching list of modules from GRASS-Addons SVN (be patient)..."), 0)
963  self.tree.Load(url = self.repo.GetValue().strip(), full = self.fullDesc.IsChecked())
964  self.SetStatusText("", 0)
965  wx.EndBusyCursor()
966 
967  def OnItemActivated(self, event):
968  item = event.GetItem()
969  data = self.tree.GetPyData(item)
970  if data and 'command' in data:
971  self.OnInstall(event = None)
972 
973  def OnInstall(self, event):
974  """!Install selected extension"""
975  log = self.parent.GetLogWindow()
976  log.RunCmd(self._getCmd(), onDone = self.OnDone)
977 
978  def OnDone(self, cmd, returncode):
979  item = self.tree.GetSelected()
980  if not item or not item.IsOk() or \
981  returncode != 0 or \
982  not os.getenv('GRASS_ADDON_PATH'):
983  return
984 
985  name = self.tree.GetItemText(item)
986  globalvar.grassCmd['all'].append(name)
987 
988  def OnItemSelected(self, event):
989  """!Item selected"""
990  item = event.GetItem()
991  self.tree.itemSelected = item
992  data = self.tree.GetPyData(item)
993  if data is None:
994  self.SetStatusText('', 0)
995  self.btnInstall.Enable(False)
996  else:
997  self.SetStatusText(data.get('description', ''), 0)
998  self.btnInstall.Enable(True)
999 
1000  def OnShowItem(self, event):
1001  """!Show selected item"""
1002  self.tree.OnShowItem(event)
1003  if self.tree.GetSelected():
1004  self.btnInstall.Enable()
1005  else:
1006  self.btnInstall.Enable(False)
1007 
1008  def OnCmdDialog(self, event):
1009  """!Shows command dialog"""
1010  menuform.GUI(parent = self).ParseCommand(cmd = self._getCmd())
1011 
1013  """!List of available extensions"""
1014  def __init__(self, parent, log, id = wx.ID_ANY,
1015  ctstyle = CT.TR_HIDE_ROOT | CT.TR_FULL_ROW_HIGHLIGHT | CT.TR_HAS_BUTTONS |
1016  CT.TR_LINES_AT_ROOT | CT.TR_SINGLE,
1017  **kwargs):
1018  self.parent = parent # GMFrame
1019  self.log = log
1020 
1021  super(ExtensionTree, self).__init__(parent, id, ctstyle = ctstyle, **kwargs)
1022 
1023  self._initTree()
1024 
1025  def _initTree(self):
1026  for prefix in ('display', 'database',
1027  'general', 'imagery',
1028  'misc', 'postscript', 'paint',
1029  'raster', 'raster3d', 'sites', 'vector', 'wxGUI', 'other'):
1030  self.AppendItem(parentId = self.root,
1031  text = prefix)
1032  self._loaded = False
1033 
1034  def _expandPrefix(self, c):
1035  name = { 'd' : 'display',
1036  'db' : 'database',
1037  'g' : 'general',
1038  'i' : 'imagery',
1039  'm' : 'misc',
1040  'ps' : 'postscript',
1041  'p' : 'paint',
1042  'r' : 'raster',
1043  'r3' : 'raster3d',
1044  's' : 'sites',
1045  'v' : 'vector',
1046  'wx' : 'wxGUI',
1047  '' : 'other' }
1048 
1049  if c in name:
1050  return name[c]
1051 
1052  return c
1053 
1054  def _findItem(self, text):
1055  """!Find item"""
1056  item = self.GetFirstChild(self.root)[0]
1057  while item and item.IsOk():
1058  if text == self.GetItemText(item):
1059  return item
1060 
1061  item = self.GetNextSibling(item)
1062 
1063  return None
1064 
1065  def Load(self, url, full = False):
1066  """!Load list of extensions"""
1067  self.DeleteAllItems()
1068  self.root = self.AddRoot(_("Menu tree"))
1069  self._initTree()
1070 
1071  if full:
1072  flags = 'g'
1073  else:
1074  flags = 'l'
1075  ret = gcmd.RunCommand('g.extension.py', read = True, parent = self,
1076  svnurl = url,
1077  flags = flags, quiet = True)
1078  if not ret:
1079  return
1080 
1081  mdict = dict()
1082  for line in ret.splitlines():
1083  if full:
1084  try:
1085  key, value = line.split('=', 1)
1086  except ValueError:
1087  key = 'name'
1088  value = line
1089 
1090  if key == 'name':
1091  try:
1092  prefix, name = value.split('.', 1)
1093  except ValueError:
1094  prefix = ''
1095  name = value
1096  if prefix not in mdict:
1097  mdict[prefix] = dict()
1098  mdict[prefix][name] = dict()
1099  else:
1100  mdict[prefix][name][key] = value
1101  else:
1102  try:
1103  prefix, name = line.strip().split('.', 1)
1104  except:
1105  prefix = ''
1106  name = line.strip()
1107 
1108  if self._expandPrefix(prefix) == prefix:
1109  prefix = ''
1110 
1111  if prefix not in mdict:
1112  mdict[prefix] = dict()
1113 
1114  mdict[prefix][name] = { 'command' : prefix + '.' + name }
1115 
1116  for prefix in mdict.keys():
1117  prefixName = self._expandPrefix(prefix)
1118  item = self._findItem(prefixName)
1119  names = mdict[prefix].keys()
1120  names.sort()
1121  for name in names:
1122  if prefix:
1123  text = prefix + '.' + name
1124  else:
1125  text = name
1126  new = self.AppendItem(parentId = item,
1127  text = text)
1128  data = dict()
1129  for key in mdict[prefix][name].keys():
1130  data[key] = mdict[prefix][name][key]
1131 
1132  self.SetPyData(new, data)
1133 
1134  self._loaded = True
1135 
1136  def IsLoaded(self):
1137  """Check if items are loaded"""
1138  return self._loaded
1139 
1141  def __init__(self, parent, id = wx.ID_ANY,
1142  title = _("Uninstall GRASS Addons extensions"), **kwargs):
1143  self.parent = parent
1144 
1145  wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
1146  self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
1147 
1148  self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
1149 
1150  self.extBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
1151  label = " %s " % _("List of installed extensions"))
1152 
1153  self.extList = CheckListExtension(parent = self.panel)
1154 
1155  # buttons
1156  self.btnUninstall = wx.Button(parent = self.panel, id = wx.ID_ANY,
1157  label = _("&Uninstall"))
1158  self.btnUninstall.SetToolTipString(_("Uninstall selected AddOns extensions"))
1159  self.btnCmd = wx.Button(parent = self.panel, id = wx.ID_ANY,
1160  label = _("Command dialog"))
1161  self.btnCmd.SetToolTipString(_('Open %s dialog') % 'g.extension')
1162  self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
1163 
1164  self.btnUninstall.Bind(wx.EVT_BUTTON, self.OnUninstall)
1165  self.btnCmd.Bind(wx.EVT_BUTTON, self.OnCmdDialog)
1166  self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
1167 
1168  self._layout()
1169 
1170  def _layout(self):
1171  """!Do layout"""
1172  sizer = wx.BoxSizer(wx.VERTICAL)
1173 
1174  extSizer = wx.StaticBoxSizer(self.extBox, wx.HORIZONTAL)
1175  extSizer.Add(item = self.extList, proportion = 1,
1176  flag = wx.ALL | wx.EXPAND, border = 1)
1177 
1178  btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1179  btnSizer.Add(item = self.btnCmd, proportion = 0,
1180  flag = wx.RIGHT, border = 5)
1181  btnSizer.AddSpacer(10)
1182  btnSizer.Add(item = self.btnClose, proportion = 0,
1183  flag = wx.RIGHT, border = 5)
1184  btnSizer.Add(item = self.btnUninstall, proportion = 0)
1185 
1186  sizer.Add(item = extSizer, proportion = 1,
1187  flag = wx.ALL | wx.EXPAND, border = 3)
1188  sizer.Add(item = btnSizer, proportion = 0,
1189  flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
1190 
1191  self.panel.SetSizer(sizer)
1192  sizer.Fit(self.panel)
1193 
1194  self.Layout()
1195 
1196  def OnCloseWindow(self, event):
1197  """!Close window"""
1198  self.Destroy()
1199 
1200  def OnUninstall(self, event):
1201  """!Uninstall selected extensions"""
1202  log = self.parent.GetLogWindow()
1203  eList = self.extList.GetExtensions()
1204  if not eList:
1205  gcmd.GError(_("No extension selected for removal. "
1206  "Operation canceled."),
1207  parent = self)
1208  return
1209 
1210  for ext in eList:
1211  files = gcmd.RunCommand('g.extension.py', parent = self, read = True, quiet = True,
1212  extension = ext, operation = 'remove').splitlines()
1213  dlg = wx.MessageDialog(parent = self,
1214  message = _("List of files to be removed:\n%(files)s\n\n"
1215  "Do you want really to remove <%(ext)s> extension?") % \
1216  { 'files' : os.linesep.join(files), 'ext' : ext },
1217  caption = _("Remove extension"),
1218  style = wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
1219 
1220  if dlg.ShowModal() == wx.ID_YES:
1221  gcmd.RunCommand('g.extension.py', flags = 'f', parent = self, quiet = True,
1222  extension = ext, operation = 'remove')
1223 
1224  self.extList.LoadData()
1225 
1226  def OnCmdDialog(self, event):
1227  """!Shows command dialog"""
1228  menuform.GUI(parent = self).ParseCommand(cmd = ['g.extension.py'])
1229 
1230 class CheckListExtension(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.CheckListCtrlMixin):
1231  """!List of mapset/owner/group"""
1232  def __init__(self, parent):
1233  self.parent = parent
1234 
1235  wx.ListCtrl.__init__(self, parent, id = wx.ID_ANY,
1236  style = wx.LC_REPORT)
1237  listmix.CheckListCtrlMixin.__init__(self)
1238 
1239  # setup mixins
1240  listmix.ListCtrlAutoWidthMixin.__init__(self)
1241 
1242  self.InsertColumn(0, _('Extension'))
1243  self.LoadData()
1244 
1245  def LoadData(self):
1246  """!Load data into list"""
1247  self.DeleteAllItems()
1248  for ext in gcmd.RunCommand('g.extension.py',
1249  quiet = True, parent = self, read = True,
1250  flags = 'a').splitlines():
1251  if ext:
1252  self.InsertStringItem(sys.maxint, ext)
1253 
1254  def GetExtensions(self):
1255  """!Get extensions to be un-installed
1256  """
1257  extList = list()
1258  for i in range(self.GetItemCount()):
1259  if self.IsChecked(i):
1260  name = self.GetItemText(i)
1261  if name:
1262  extList.append(name)
1263 
1264  return extList
1265 
1266 class HelpWindow(wx.html.HtmlWindow):
1267  """!This panel holds the text from GRASS docs.
1268 
1269  GISBASE must be set in the environment to find the html docs dir.
1270  The SYNOPSIS section is skipped, since this Panel is supposed to
1271  be integrated into the cmdPanel and options are obvious there.
1272  """
1273  def __init__(self, parent, grass_command, text, skip_description,
1274  **kwargs):
1275  """!If grass_command is given, the corresponding HTML help
1276  file will be presented, with all links pointing to absolute
1277  paths of local files.
1278 
1279  If 'skip_description' is True, the HTML corresponding to
1280  SYNOPSIS will be skipped, thus only presenting the help file
1281  from the DESCRIPTION section onwards.
1282 
1283  If 'text' is given, it must be the HTML text to be presented
1284  in the Panel.
1285  """
1286  self.parent = parent
1287  wx.InitAllImageHandlers()
1288  wx.html.HtmlWindow.__init__(self, parent = parent, **kwargs)
1289 
1290  gisbase = os.getenv("GISBASE")
1291  self.loaded = False
1292  self.history = list()
1293  self.historyIdx = 0
1294  self.fspath = os.path.join(gisbase, "docs", "html")
1295 
1296  self.SetStandardFonts (size = 10)
1297  self.SetBorders(10)
1298 
1299  if text is None:
1300  if skip_description:
1301  url = os.path.join(self.fspath, grass_command + ".html")
1302  self.fillContentsFromFile(url,
1303  skip_description = skip_description)
1304  self.history.append(url)
1305  self.loaded = True
1306  else:
1307  ### FIXME: calling LoadPage() is strangely time-consuming (only first call)
1308  # self.LoadPage(self.fspath + grass_command + ".html")
1309  self.loaded = False
1310  else:
1311  self.SetPage(text)
1312  self.loaded = True
1313 
1314  def OnLinkClicked(self, linkinfo):
1315  url = linkinfo.GetHref()
1316  if url[:4] != 'http':
1317  url = os.path.join(self.fspath, url)
1318  self.history.append(url)
1319  self.historyIdx += 1
1320  self.parent.OnHistory()
1321 
1322  super(HelpWindow, self).OnLinkClicked(linkinfo)
1323 
1324  def fillContentsFromFile(self, htmlFile, skip_description = True):
1325  """!Load content from file"""
1326  aLink = re.compile(r'(<a href="?)(.+\.html?["\s]*>)', re.IGNORECASE)
1327  imgLink = re.compile(r'(<img src="?)(.+\.[png|gif])', re.IGNORECASE)
1328  try:
1329  contents = []
1330  skip = False
1331  for l in file(htmlFile, "rb").readlines():
1332  if "DESCRIPTION" in l:
1333  skip = False
1334  if not skip:
1335  # do skip the options description if requested
1336  if "SYNOPSIS" in l:
1337  skip = skip_description
1338  else:
1339  # FIXME: find only first item
1340  findALink = aLink.search(l)
1341  if findALink is not None:
1342  contents.append(aLink.sub(findALink.group(1)+
1343  self.fspath+findALink.group(2),l))
1344  findImgLink = imgLink.search(l)
1345  if findImgLink is not None:
1346  contents.append(imgLink.sub(findImgLink.group(1)+
1347  self.fspath+findImgLink.group(2),l))
1348 
1349  if findALink is None and findImgLink is None:
1350  contents.append(l)
1351  self.SetPage("".join(contents))
1352  self.loaded = True
1353  except: # The Manual file was not found
1354  self.loaded = False
1355 
1356 class HelpPanel(wx.Panel):
1357  def __init__(self, parent, grass_command = "index", text = None,
1358  skip_description = False, **kwargs):
1359  self.grass_command = grass_command
1360  wx.Panel.__init__(self, parent = parent, id = wx.ID_ANY)
1361 
1362  self.content = HelpWindow(self, grass_command, text,
1363  skip_description)
1364 
1365  self.btnNext = wx.Button(parent = self, id = wx.ID_ANY,
1366  label = _("&Next"))
1367  self.btnNext.Enable(False)
1368  self.btnPrev = wx.Button(parent = self, id = wx.ID_ANY,
1369  label = _("&Previous"))
1370  self.btnPrev.Enable(False)
1371 
1372  self.btnNext.Bind(wx.EVT_BUTTON, self.OnNext)
1373  self.btnPrev.Bind(wx.EVT_BUTTON, self.OnPrev)
1374 
1375  self._layout()
1376 
1377  def _layout(self):
1378  """!Do layout"""
1379  sizer = wx.BoxSizer(wx.VERTICAL)
1380  btnSizer = wx.BoxSizer(wx.HORIZONTAL)
1381 
1382  btnSizer.Add(item = self.btnPrev, proportion = 0,
1383  flag = wx.ALL, border = 5)
1384  btnSizer.Add(item = wx.Size(1, 1), proportion = 1)
1385  btnSizer.Add(item = self.btnNext, proportion = 0,
1386  flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
1387 
1388  sizer.Add(item = self.content, proportion = 1,
1389  flag = wx.EXPAND)
1390  sizer.Add(item = btnSizer, proportion = 0,
1391  flag = wx.EXPAND)
1392 
1393  self.SetSizer(sizer)
1394  sizer.Fit(self)
1395 
1396  def LoadPage(self, path = None):
1397  """!Load page"""
1398  if not path:
1399  path = os.path.join(self.content.fspath, self.grass_command + ".html")
1400  self.content.history.append(path)
1401  self.content.LoadPage(path)
1402 
1403  def IsFile(self):
1404  """!Check if file exists"""
1405  return os.path.isfile(os.path.join(self.content.fspath, self.grass_command + ".html"))
1406 
1407  def IsLoaded(self):
1408  return self.content.loaded
1409 
1410  def OnHistory(self):
1411  """!Update buttons"""
1412  nH = len(self.content.history)
1413  iH = self.content.historyIdx
1414  if iH == nH - 1:
1415  self.btnNext.Enable(False)
1416  elif iH > -1:
1417  self.btnNext.Enable(True)
1418  if iH < 1:
1419  self.btnPrev.Enable(False)
1420  else:
1421  self.btnPrev.Enable(True)
1422 
1423  def OnNext(self, event):
1424  """Load next page"""
1425  self.content.historyIdx += 1
1426  idx = self.content.historyIdx
1427  path = self.content.history[idx]
1428  self.content.LoadPage(path)
1429  self.OnHistory()
1430 
1431  event.Skip()
1432 
1433  def OnPrev(self, event):
1434  """Load previous page"""
1435  self.content.historyIdx -= 1
1436  idx = self.content.historyIdx
1437  path = self.content.history[idx]
1438  self.content.LoadPage(path)
1439  self.OnHistory()
1440 
1441  event.Skip()