GRASS Programmer's Manual  6.4.3(2013)-r
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Pages
ghelp.py
Go to the documentation of this file.
1 """!
2 @package gui_core.ghelp
3 
4 @brief Help window
5 
6 Classes:
7  - ghelp::SearchModuleWindow
8  - ghelp::MenuTreeWindow
9  - ghelp::MenuTree
10  - ghelp::AboutWindow
11  - ghelp::HelpFrame
12  - ghelp::HelpWindow
13  - ghelp::HelpPanel
14 
15 (C) 2008-2011 by the GRASS Development Team
16 
17 This program is free software under the GNU General Public License
18 (>=v2). Read the file COPYING that comes with GRASS for details.
19 
20 @author Martin Landa <landa.martin gmail.com>
21 """
22 
23 import os
24 import sys
25 import re
26 import codecs
27 import platform
28 
29 import wx
30 from wx.html import HtmlWindow
31 try:
32  import wx.lib.agw.customtreectrl as CT
33  from wx.lib.agw.hyperlink import HyperLinkCtrl
34 except ImportError:
35  import wx.lib.customtreectrl as CT
36  from wx.lib.hyperlink import HyperLinkCtrl
37 import wx.lib.flatnotebook as FN
38 
39 import grass.script as grass
40 
41 from core import globalvar
42 from core import utils
43 from lmgr.menudata import ManagerData
44 from core.gcmd import GError, DecodeString
45 from gui_core.widgets import GNotebook, StaticWrapText, ItemTree, ScrolledPanel
46 
47 class SearchModuleWindow(wx.Panel):
48  """!Search module window (used in MenuTreeWindow)"""
49  def __init__(self, parent, id = wx.ID_ANY, cmdPrompt = None,
50  showChoice = True, showTip = False, **kwargs):
51  self.showTip = showTip
52  self.showChoice = showChoice
53  self.cmdPrompt = cmdPrompt
54 
55  wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
56 
57  self._searchDict = { _('description') : 'description',
58  _('command') : 'command',
59  _('keywords') : 'keywords' }
60 
61  self.box = wx.StaticBox(parent = self, id = wx.ID_ANY,
62  label = " %s " % _("Find module(s)"))
63 
64  self.searchBy = wx.Choice(parent = self, id = wx.ID_ANY,
65  choices = [_('description'),
66  _('keywords'),
67  _('command')])
68  self.searchBy.SetSelection(0)
69 
70  self.search = wx.TextCtrl(parent = self, id = wx.ID_ANY,
71  value = "", size = (-1, 25),
72  style = wx.TE_PROCESS_ENTER)
73  self.search.Bind(wx.EVT_TEXT, self.OnSearchModule)
74 
75  if self.showTip:
76  self.searchTip = StaticWrapText(parent = self, id = wx.ID_ANY,
77  size = (-1, 35))
78 
79  if self.showChoice:
80  self.searchChoice = wx.Choice(parent = self, id = wx.ID_ANY)
81  if self.cmdPrompt:
82  self.searchChoice.SetItems(self.cmdPrompt.GetCommandItems())
83  self.searchChoice.Bind(wx.EVT_CHOICE, self.OnSelectModule)
84 
85  self._layout()
86 
87  def _layout(self):
88  """!Do layout"""
89  sizer = wx.StaticBoxSizer(self.box, wx.HORIZONTAL)
90  gridSizer = wx.GridBagSizer(hgap = 3, vgap = 3)
91  gridSizer.AddGrowableCol(1)
92 
93  gridSizer.Add(item = self.searchBy,
94  flag = wx.ALIGN_CENTER_VERTICAL, pos = (0, 0))
95  gridSizer.Add(item = self.search,
96  flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (0, 1))
97  row = 1
98  if self.showTip:
99  gridSizer.Add(item = self.searchTip,
100  flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (row, 0), span = (1, 2))
101  row += 1
102 
103  if self.showChoice:
104  gridSizer.Add(item = self.searchChoice,
105  flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (row, 0), span = (1, 2))
106 
107  sizer.Add(item = gridSizer, proportion = 1)
108 
109  self.SetSizer(sizer)
110  sizer.Fit(self)
111 
112  def GetSelection(self):
113  """!Get selected element"""
114  selection = self.searchBy.GetStringSelection()
115 
116  return self._searchDict[selection]
117 
118  def SetSelection(self, i):
119  """!Set selection element"""
120  self.searchBy.SetSelection(i)
121 
122  def OnSearchModule(self, event):
123  """!Search module by keywords or description"""
124  if not self.cmdPrompt:
125  event.Skip()
126  return
127 
128  text = event.GetString()
129  if not text:
130  self.cmdPrompt.SetFilter(None)
131  mList = self.cmdPrompt.GetCommandItems()
132  self.searchChoice.SetItems(mList)
133  if self.showTip:
134  self.searchTip.SetLabel(_("%d modules found") % len(mList))
135  event.Skip()
136  return
137 
138  modules = dict()
139  iFound = 0
140  for module, data in self.cmdPrompt.moduleDesc.iteritems():
141  found = False
142  sel = self.searchBy.GetSelection()
143  if sel == 0: # -> description
144  if text in data['desc']:
145  found = True
146  elif sel == 1: # keywords
147  if text in ','.join(data['keywords']):
148  found = True
149  else: # command
150  if module[:len(text)] == text:
151  found = True
152 
153  if found:
154  iFound += 1
155  try:
156  group, name = module.split('.')
157  except ValueError:
158  continue # TODO
159 
160  if group not in modules:
161  modules[group] = list()
162  modules[group].append(name)
163 
164  self.cmdPrompt.SetFilter(modules)
165  self.searchChoice.SetItems(self.cmdPrompt.GetCommandItems())
166  if self.showTip:
167  self.searchTip.SetLabel(_("%d modules found") % iFound)
168 
169  event.Skip()
170 
171  def OnSelectModule(self, event):
172  """!Module selected from choice, update command prompt"""
173  cmd = event.GetString().split(' ', 1)[0]
174  text = cmd + ' '
175  pos = len(text)
176 
177  if self.cmdPrompt:
178  self.cmdPrompt.SetText(text)
179  self.cmdPrompt.SetSelectionStart(pos)
180  self.cmdPrompt.SetCurrentPos(pos)
181  self.cmdPrompt.SetFocus()
182 
183  desc = self.cmdPrompt.GetCommandDesc(cmd)
184  if self.showTip:
185  self.searchTip.SetLabel(desc)
186 
187  def Reset(self):
188  """!Reset widget"""
189  self.searchBy.SetSelection(0)
190  self.search.SetValue('')
191  if self.showTip:
192  self.searchTip.SetLabel('')
193 
194 class MenuTreeWindow(wx.Panel):
195  """!Show menu tree"""
196  def __init__(self, parent, id = wx.ID_ANY, **kwargs):
197  self.parent = parent # LayerManager
198 
199  wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
200 
201  self.dataBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
202  label = " %s " % _("Menu tree (double-click to run command)"))
203  # tree
204  self.tree = MenuTree(parent = self, data = ManagerData())
205  self.tree.Load()
206 
207  # search widget
208  self.search = SearchModuleWindow(parent = self, showChoice = False)
209 
210  # buttons
211  self.btnRun = wx.Button(self, id = wx.ID_OK, label = _("&Run"))
212  self.btnRun.SetToolTipString(_("Run selected command"))
213  self.btnRun.Enable(False)
214 
215  # bindings
216  self.btnRun.Bind(wx.EVT_BUTTON, self.OnRun)
217  self.tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated)
218  self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnItemSelected)
219  self.search.Bind(wx.EVT_TEXT_ENTER, self.OnShowItem)
220  self.search.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
221 
222  self._layout()
223 
224  self.search.SetFocus()
225 
226  def _layout(self):
227  """!Do dialog layout"""
228  sizer = wx.BoxSizer(wx.VERTICAL)
229 
230  # body
231  dataSizer = wx.StaticBoxSizer(self.dataBox, wx.HORIZONTAL)
232  dataSizer.Add(item = self.tree, proportion =1,
233  flag = wx.EXPAND)
234 
235  # buttons
236  btnSizer = wx.BoxSizer(wx.HORIZONTAL)
237  btnSizer.Add(item = self.btnRun, proportion = 0)
238 
239  sizer.Add(item = dataSizer, proportion = 1,
240  flag = wx.EXPAND | wx.ALL, border = 5)
241 
242  sizer.Add(item = self.search, proportion = 0,
243  flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
244 
245  sizer.Add(item = btnSizer, proportion = 0,
246  flag = wx.ALIGN_RIGHT | wx.BOTTOM | wx.RIGHT, border = 5)
247 
248  sizer.Fit(self)
249  sizer.SetSizeHints(self)
250 
251  self.SetSizer(sizer)
252 
253  self.Fit()
254  self.SetAutoLayout(True)
255  self.Layout()
256 
257  def OnCloseWindow(self, event):
258  """!Close window"""
259  self.Destroy()
260 
261  def OnRun(self, event):
262  """!Run selected command"""
263  if not self.tree.GetSelected():
264  return # should not happen
265 
266  data = self.tree.GetPyData(self.tree.GetSelected())
267  if not data:
268  return
269 
270  handler = 'self.parent.' + data['handler'].lstrip('self.')
271  if data['handler'] == 'self.OnXTerm':
272  wx.MessageBox(parent = self,
273  message = _('You must run this command from the menu or command line',
274  'This command require an XTerm'),
275  caption = _('Message'), style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
276  elif data['command']:
277  eval(handler)(event = None, cmd = data['command'].split())
278  else:
279  eval(handler)(None)
280 
281  def OnShowItem(self, event):
282  """!Show selected item"""
283  self.tree.OnShowItem(event)
284  if self.tree.GetSelected():
285  self.btnRun.Enable()
286  else:
287  self.btnRun.Enable(False)
288 
289  def OnItemActivated(self, event):
290  """!Item activated (double-click)"""
291  item = event.GetItem()
292  if not item or not item.IsOk():
293  return
294 
295  data = self.tree.GetPyData(item)
296  if not data or 'command' not in data:
297  return
298 
299  self.tree.itemSelected = item
300 
301  self.OnRun(None)
302 
303  def OnItemSelected(self, event):
304  """!Item selected"""
305  item = event.GetItem()
306  if not item or not item.IsOk():
307  return
308 
309  data = self.tree.GetPyData(item)
310  if not data or 'command' not in data:
311  return
312 
313  if data['command']:
314  label = data['command'] + ' -- ' + data['description']
315  else:
316  label = data['description']
317 
318  self.parent.SetStatusText(label, 0)
319 
320  def OnUpdateStatusBar(self, event):
321  """!Update statusbar text"""
322  element = self.search.GetSelection()
323  self.tree.SearchItems(element = element,
324  value = event.GetString())
325 
326  nItems = len(self.tree.itemsMarked)
327  if event.GetString():
328  self.parent.SetStatusText(_("%d modules match") % nItems, 0)
329  else:
330  self.parent.SetStatusText("", 0)
331 
332  event.Skip()
333 
334 class MenuTree(ItemTree):
335  """!Menu tree class"""
336  def __init__(self, parent, data, **kwargs):
337  self.parent = parent
338  self.menudata = data
339 
340  super(MenuTree, self).__init__(parent, **kwargs)
341 
342  def Load(self, data = None):
343  """!Load menu data tree
344 
345  @param data menu data (None to use self.menudata)
346  """
347  if not data:
348  data = self.menudata
349 
350  self.itemsMarked = [] # list of marked items
351  for eachMenuData in data.GetMenu():
352  for label, items in eachMenuData:
353  item = self.AppendItem(parentId = self.root,
354  text = label.replace('&', ''))
355  self.__AppendItems(item, items)
356 
357  def __AppendItems(self, item, data):
358  """!Append items into tree (used by Load()
359 
360  @param item tree item (parent)
361  @parent data menu data"""
362  for eachItem in data:
363  if len(eachItem) == 2:
364  if eachItem[0]:
365  itemSub = self.AppendItem(parentId = item,
366  text = eachItem[0])
367  self.__AppendItems(itemSub, eachItem[1])
368  else:
369  if eachItem[0]:
370  itemNew = self.AppendItem(parentId = item,
371  text = eachItem[0])
372 
373  data = { 'item' : eachItem[0],
374  'description' : eachItem[1],
375  'handler' : eachItem[2],
376  'command' : eachItem[3],
377  'keywords' : eachItem[4] }
378 
379  self.SetPyData(itemNew, data)
380 
381 class AboutWindow(wx.Frame):
382  """!Create custom About Window
383  """
384  def __init__(self, parent, size = (650, 460),
385  title = _('About GRASS GIS'), **kwargs):
386  wx.Frame.__init__(self, parent = parent, id = wx.ID_ANY, title = title, size = size, **kwargs)
387 
388  panel = wx.Panel(parent = self, id = wx.ID_ANY)
389 
390  # icon
391  self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
392 
393  # get version and web site
394  vInfo = grass.version()
395 
396  infoTxt = ScrolledPanel(parent = panel)
397  infoTxt.SetupScrolling()
398  infoSizer = wx.BoxSizer(wx.VERTICAL)
399  infoGridSizer = wx.GridBagSizer(vgap = 5, hgap = 5)
400  infoGridSizer.AddGrowableCol(0)
401  infoGridSizer.AddGrowableCol(1)
402  logo = os.path.join(globalvar.ETCDIR, "gui", "icons", "grass-64x64.png")
403  logoBitmap = wx.StaticBitmap(parent = infoTxt, id = wx.ID_ANY,
404  bitmap = wx.Bitmap(name = logo,
405  type = wx.BITMAP_TYPE_PNG))
406  infoSizer.Add(item = logoBitmap, proportion = 0,
407  flag = wx.ALL | wx.ALIGN_CENTER, border = 20)
408 
409  info = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
410  label = 'GRASS GIS ' + vInfo['version'] + '\n\n')
411  info.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.BOLD, 0, ""))
412  info.SetForegroundColour(wx.Colour(35, 142, 35))
413  infoSizer.Add(item = info, proportion = 0,
414  flag = wx.BOTTOM | wx.ALIGN_CENTER, border = 1)
415 
416  row = 0
417  infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
418  label = _('Official GRASS site:')),
419  pos = (row, 0),
420  flag = wx.ALIGN_RIGHT)
421 
422  infoGridSizer.Add(item = HyperLinkCtrl(parent = infoTxt, id = wx.ID_ANY,
423  label = 'http://grass.osgeo.org'),
424  pos = (row, 1),
425  flag = wx.ALIGN_LEFT)
426 
427  row += 2
428  infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
429  label = '%s:' % _('SVN Revision')),
430  pos = (row, 0),
431  flag = wx.ALIGN_RIGHT)
432 
433  infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
434  label = vInfo['revision']),
435  pos = (row, 1),
436  flag = wx.ALIGN_LEFT)
437 
438  row += 1
439  infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
440  label = '%s:' % _('GIS Library Revision')),
441  pos = (row, 0),
442  flag = wx.ALIGN_RIGHT)
443 
444  infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
445  label = vInfo['libgis_revision'] + ' (' +
446  vInfo['libgis_date'].split(' ')[0] + ')'),
447  pos = (row, 1),
448  flag = wx.ALIGN_LEFT)
449 
450  row += 2
451  infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
452  label = 'Python:'),
453  pos = (row, 0),
454  flag = wx.ALIGN_RIGHT)
455 
456  infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
457  label = platform.python_version()),
458  pos = (row, 1),
459  flag = wx.ALIGN_LEFT)
460 
461  row += 1
462  infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
463  label = 'wxPython:'),
464  pos = (row, 0),
465  flag = wx.ALIGN_RIGHT)
466 
467  infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
468  label = wx.__version__),
469  pos = (row, 1),
470  flag = wx.ALIGN_LEFT)
471 
472  infoSizer.Add(item = infoGridSizer,
473  proportion = 1,
474  flag = wx.EXPAND | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL)
475 
476  row += 2
477  infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
478  label = "%s:" % _('Language')),
479  pos = (row, 0),
480  flag = wx.ALIGN_RIGHT)
481  lang = grass.gisenv().get('LANG', None)
482  if not lang:
483  import locale
484  loc = locale.getdefaultlocale()
485  if loc == (None, None):
486  lang = _('unknown')
487  else:
488  lang = u'%s.%s' % (loc[0], loc[1])
489  infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
490  label = lang),
491  pos = (row, 1),
492  flag = wx.ALIGN_LEFT)
493 
494  # create a flat notebook for displaying information about GRASS
495  aboutNotebook = GNotebook(panel, style = globalvar.FNPageStyle | FN.FNB_NO_X_BUTTON)
496  aboutNotebook.SetTabAreaColour(globalvar.FNPageColor)
497 
498  for title, win in ((_("Info"), infoTxt),
499  (_("Copyright"), self._pageCopyright()),
500  (_("License"), self._pageLicense()),
501  (_("Authors"), self._pageCredit()),
502  (_("Contributors"), self._pageContributors()),
503  (_("Extra contributors"), self._pageContributors(extra = True)),
504  (_("Translators"), self._pageTranslators())):
505  aboutNotebook.AddPage(page = win, text = title)
506  wx.CallAfter(aboutNotebook.SetSelection, 0)
507 
508  # buttons
509  btnClose = wx.Button(parent = panel, id = wx.ID_CLOSE)
510  btnSizer = wx.BoxSizer(wx.HORIZONTAL)
511  btnSizer.Add(item = btnClose, proportion = 0,
512  flag = wx.ALL | wx.ALIGN_RIGHT,
513  border = 5)
514  # bindings
515  btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
516 
517  infoTxt.SetSizer(infoSizer)
518  infoSizer.Fit(infoTxt)
519 
520  sizer = wx.BoxSizer(wx.VERTICAL)
521  sizer.Add(item = aboutNotebook, proportion = 1,
522  flag = wx.EXPAND | wx.ALL, border = 1)
523  sizer.Add(item = btnSizer, proportion = 0,
524  flag = wx.ALL | wx.ALIGN_RIGHT, border = 1)
525  panel.SetSizer(sizer)
526 
527  self.Layout()
528  self.SetMinSize((400, 400))
529 
530  def _pageCopyright(self):
531  """Copyright information"""
532  copyfile = os.path.join(os.getenv("GISBASE"), "COPYING")
533  if os.path.exists(copyfile):
534  copyrightFile = open(copyfile, 'r')
535  copytext = copyrightFile.read()
536  copyrightFile.close()
537  else:
538  copytext = _('%s file missing') % 'COPYING'
539 
540  # put text into a scrolling panel
541  copyrightwin = ScrolledPanel(self)
542 
543  copyrighttxt = wx.StaticText(copyrightwin, id = wx.ID_ANY, label = copytext)
544  copyrightwin.SetAutoLayout(True)
545  copyrightwin.sizer = wx.BoxSizer(wx.VERTICAL)
546  copyrightwin.sizer.Add(item = copyrighttxt, proportion = 1,
547  flag = wx.EXPAND | wx.ALL, border = 3)
548  copyrightwin.SetSizer(copyrightwin.sizer)
549  copyrightwin.Layout()
550  copyrightwin.SetupScrolling()
551 
552  return copyrightwin
553 
554  def _pageLicense(self):
555  """Licence about"""
556  licfile = os.path.join(os.getenv("GISBASE"), "GPL.TXT")
557  if os.path.exists(licfile):
558  licenceFile = open(licfile, 'r')
559  license = ''.join(licenceFile.readlines())
560  licenceFile.close()
561  else:
562  license = _('%s file missing') % 'GPL.TXT'
563  # put text into a scrolling panel
564  licensewin = ScrolledPanel(self)
565  licensetxt = wx.StaticText(licensewin, id = wx.ID_ANY, label = license)
566  licensewin.SetAutoLayout(True)
567  licensewin.sizer = wx.BoxSizer(wx.VERTICAL)
568  licensewin.sizer.Add(item = licensetxt, proportion = 1,
569  flag = wx.EXPAND | wx.ALL, border = 3)
570  licensewin.SetSizer(licensewin.sizer)
571  licensewin.Layout()
572  licensewin.SetupScrolling()
573 
574  return licensewin
575 
576  def _pageCredit(self):
577  """Credit about"""
578  # credits
579  authfile = os.path.join(os.getenv("GISBASE"), "AUTHORS")
580  if os.path.exists(authfile):
581  authorsFile = open(authfile, 'r')
582  authors = unicode(''.join(authorsFile.readlines()), "utf-8")
583  authorsFile.close()
584  else:
585  authors = _('%s file missing') % 'AUTHORS'
586  authorwin = ScrolledPanel(self)
587  authortxt = wx.StaticText(authorwin, id = wx.ID_ANY, label = authors)
588  authorwin.SetAutoLayout(True)
589  authorwin.SetupScrolling()
590  authorwin.sizer = wx.BoxSizer(wx.VERTICAL)
591  authorwin.sizer.Add(item = authortxt, proportion = 1,
592  flag = wx.EXPAND | wx.ALL, border = 3)
593  authorwin.SetSizer(authorwin.sizer)
594  authorwin.Layout()
595 
596  return authorwin
597 
598  def _pageContributors(self, extra = False):
599  """Contributors info"""
600  if extra:
601  contribfile = os.path.join(os.getenv("GISBASE"), "contributors_extra.csv")
602  else:
603  contribfile = os.path.join(os.getenv("GISBASE"), "contributors.csv")
604  if os.path.exists(contribfile):
605  contribFile = codecs.open(contribfile, encoding = 'utf-8', mode = 'r')
606  contribs = list()
607  errLines = list()
608  for line in contribFile.readlines()[1:]:
609  line = line.rstrip('\n')
610  try:
611  if extra:
612  name, email, rfc2_agreed = line.split(',')
613  else:
614  cvs_id, name, email, country, osgeo_id, rfc2_agreed = line.split(',')
615  except ValueError:
616  errLines.append(line)
617  continue
618  if extra:
619  contribs.append((name, email))
620  else:
621  contribs.append((name, email, country, osgeo_id))
622 
623  contribFile.close()
624 
625  if errLines:
626  GError(parent = self,
627  message = _("Error when reading file '%s'.") % contribfile + \
628  "\n\n" + _("Lines:") + " %s" % \
629  os.linesep.join(map(DecodeString, errLines)))
630  else:
631  contribs = None
632 
633  contribwin = ScrolledPanel(self)
634  contribwin.SetAutoLayout(True)
635  contribwin.SetupScrolling()
636  contribwin.sizer = wx.BoxSizer(wx.VERTICAL)
637 
638  if not contribs:
639  contribtxt = wx.StaticText(contribwin, id = wx.ID_ANY,
640  label = _('%s file missing') % contribfile)
641  contribwin.sizer.Add(item = contribtxt, proportion = 1,
642  flag = wx.EXPAND | wx.ALL, border = 3)
643  else:
644  if extra:
645  items = (_('Name'), _('E-mail'))
646  else:
647  items = (_('Name'), _('E-mail'), _('Country'), _('OSGeo_ID'))
648  contribBox = wx.FlexGridSizer(cols = len(items), vgap = 5, hgap = 5)
649  for item in items:
650  contribBox.Add(item = wx.StaticText(parent = contribwin, id = wx.ID_ANY,
651  label = item))
652  for vals in sorted(contribs, key = lambda x: x[0]):
653  for item in vals:
654  contribBox.Add(item = wx.StaticText(parent = contribwin, id = wx.ID_ANY,
655  label = item))
656  contribwin.sizer.Add(item = contribBox, proportion = 1,
657  flag = wx.EXPAND | wx.ALL, border = 3)
658 
659  contribwin.SetSizer(contribwin.sizer)
660  contribwin.Layout()
661 
662  return contribwin
663 
664  def _pageTranslators(self):
665  """Translators info"""
666  translatorsfile = os.path.join(os.getenv("GISBASE"), "translators.csv")
667  if os.path.exists(translatorsfile):
668  translatorsFile = open(translatorsfile, 'r')
669  translators = dict()
670  errLines = list()
671  for line in translatorsFile.readlines()[1:]:
672  line = line.rstrip('\n')
673  try:
674  name, email, languages = line.split(',')
675  except ValueError:
676  errLines.append(line)
677  continue
678  for language in languages.split(' '):
679  if language not in translators:
680  translators[language] = list()
681  translators[language].append((name, email))
682  translatorsFile.close()
683 
684  if errLines:
685  GError(parent = self,
686  message = _("Error when reading file '%s'.") % translatorsfile + \
687  "\n\n" + _("Lines:") + " %s" % \
688  os.linesep.join(map(DecodeString, errLines)))
689  else:
690  translators = None
691 
692  translatorswin = ScrolledPanel(self)
693  translatorswin.SetAutoLayout(True)
694  translatorswin.SetupScrolling()
695  translatorswin.sizer = wx.BoxSizer(wx.VERTICAL)
696 
697  if not translators:
698  translatorstxt = wx.StaticText(translatorswin, id = wx.ID_ANY,
699  label = _('%s file missing') % 'translators.csv')
700  translatorswin.sizer.Add(item = translatorstxt, proportion = 1,
701  flag = wx.EXPAND | wx.ALL, border = 3)
702  else:
703  translatorsBox = wx.FlexGridSizer(cols = 3, vgap = 5, hgap = 5)
704  languages = translators.keys()
705  languages.sort()
706  translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
707  label = _('Name')))
708  translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
709  label = _('E-mail')))
710  translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
711  label = _('Language')))
712  for lang in languages:
713  for translator in translators[lang]:
714  name, email = translator
715  translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
716  label = unicode(name, "utf-8")))
717  translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
718  label = email))
719  translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
720  label = lang))
721 
722  translatorswin.sizer.Add(item = translatorsBox, proportion = 1,
723  flag = wx.EXPAND | wx.ALL, border = 3)
724 
725  translatorswin.SetSizer(translatorswin.sizer)
726  translatorswin.Layout()
727 
728  return translatorswin
729 
730  def OnCloseWindow(self, event):
731  """!Close window"""
732  self.Close()
733 
734 class HelpFrame(wx.Dialog):
735  """!GRASS Quickstart help window
736 
737  As a base class wx.Dialog is used, because of not working
738  close button with wx.Frame when dialog is called from wizard.
739  If parent is None, application TopLevelWindow is used (wxPython standard behaviour).
740  """
741  def __init__(self, parent, id, title, size, file):
742  wx.Dialog.__init__(self, parent = parent, id = id, title = title,
743  size = size, style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER | wx.MINIMIZE_BOX)
744 
745  sizer = wx.BoxSizer(wx.VERTICAL)
746 
747  # text
748  content = HelpPanel(parent = self)
749  content.LoadPage(file)
750 
751  sizer.Add(item = content, proportion = 1, flag = wx.EXPAND)
752 
753  self.SetAutoLayout(True)
754  self.SetSizer(sizer)
755  self.Layout()
756 
757 
758 class HelpWindow(wx.html.HtmlWindow):
759  """!This panel holds the text from GRASS docs.
760 
761  GISBASE must be set in the environment to find the html docs dir.
762  The SYNOPSIS section is skipped, since this Panel is supposed to
763  be integrated into the cmdPanel and options are obvious there.
764  """
765  def __init__(self, parent, grass_command, text, skip_description,
766  **kwargs):
767  """!If grass_command is given, the corresponding HTML help
768  file will be presented, with all links pointing to absolute
769  paths of local files.
770 
771  If 'skip_description' is True, the HTML corresponding to
772  SYNOPSIS will be skipped, thus only presenting the help file
773  from the DESCRIPTION section onwards.
774 
775  If 'text' is given, it must be the HTML text to be presented
776  in the Panel.
777  """
778  self.parent = parent
779  wx.InitAllImageHandlers()
780  wx.html.HtmlWindow.__init__(self, parent = parent, **kwargs)
781 
782  gisbase = os.getenv("GISBASE")
783  self.loaded = False
784  self.history = list()
785  self.historyIdx = 0
786  self.fspath = os.path.join(gisbase, "docs", "html")
787 
788  self.SetStandardFonts (size = 10)
789  self.SetBorders(10)
790 
791  if text is None:
792  if skip_description:
793  url = os.path.join(self.fspath, grass_command + ".html")
794  self.fillContentsFromFile(url,
795  skip_description = skip_description)
796  self.history.append(url)
797  self.loaded = True
798  else:
799  ### FIXME: calling LoadPage() is strangely time-consuming (only first call)
800  # self.LoadPage(self.fspath + grass_command + ".html")
801  self.loaded = False
802  else:
803  self.SetPage(text)
804  self.loaded = True
805 
806  def OnLinkClicked(self, linkinfo):
807  url = linkinfo.GetHref()
808  if url[:4] != 'http':
809  url = os.path.join(self.fspath, url)
810  self.history.append(url)
811  self.historyIdx += 1
812  self.parent.OnHistory()
813 
814  super(HelpWindow, self).OnLinkClicked(linkinfo)
815 
816  def fillContentsFromFile(self, htmlFile, skip_description = True):
817  """!Load content from file"""
818  aLink = re.compile(r'(<a href="?)(.+\.html?["\s]*>)', re.IGNORECASE)
819  imgLink = re.compile(r'(<img src="?)(.+\.[png|gif])', re.IGNORECASE)
820  try:
821  contents = []
822  skip = False
823  for l in file(htmlFile, "rb").readlines():
824  if "DESCRIPTION" in l:
825  skip = False
826  if not skip:
827  # do skip the options description if requested
828  if "SYNOPSIS" in l:
829  skip = skip_description
830  else:
831  # FIXME: find only first item
832  findALink = aLink.search(l)
833  if findALink is not None:
834  contents.append(aLink.sub(findALink.group(1)+
835  self.fspath+findALink.group(2),l))
836  findImgLink = imgLink.search(l)
837  if findImgLink is not None:
838  contents.append(imgLink.sub(findImgLink.group(1)+
839  self.fspath+findImgLink.group(2),l))
840 
841  if findALink is None and findImgLink is None:
842  contents.append(l)
843  self.SetPage("".join(contents))
844  self.loaded = True
845  except: # The Manual file was not found
846  self.loaded = False
847 
848 class HelpPanel(wx.Panel):
849  def __init__(self, parent, grass_command = "index", text = None,
850  skip_description = False, **kwargs):
851  self.grass_command = grass_command
852  wx.Panel.__init__(self, parent = parent, id = wx.ID_ANY)
853 
854  self.content = HelpWindow(self, grass_command, text,
855  skip_description)
856 
857  self.btnNext = wx.Button(parent = self, id = wx.ID_ANY,
858  label = _("&Next"))
859  self.btnNext.Enable(False)
860  self.btnPrev = wx.Button(parent = self, id = wx.ID_ANY,
861  label = _("&Previous"))
862  self.btnPrev.Enable(False)
863 
864  self.btnNext.Bind(wx.EVT_BUTTON, self.OnNext)
865  self.btnPrev.Bind(wx.EVT_BUTTON, self.OnPrev)
866 
867  self._layout()
868 
869  def _layout(self):
870  """!Do layout"""
871  sizer = wx.BoxSizer(wx.VERTICAL)
872  btnSizer = wx.BoxSizer(wx.HORIZONTAL)
873 
874  btnSizer.Add(item = self.btnPrev, proportion = 0,
875  flag = wx.ALL, border = 5)
876  btnSizer.Add(item = wx.Size(1, 1), proportion = 1)
877  btnSizer.Add(item = self.btnNext, proportion = 0,
878  flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
879 
880  sizer.Add(item = self.content, proportion = 1,
881  flag = wx.EXPAND)
882  sizer.Add(item = btnSizer, proportion = 0,
883  flag = wx.EXPAND)
884 
885  self.SetSizer(sizer)
886  sizer.Fit(self)
887 
888  def LoadPage(self, path = None):
889  """!Load page"""
890  if not path:
891  path = self.GetFile()
892  self.content.history.append(path)
893  self.content.LoadPage(path)
894 
895  def GetFile(self):
896  """!Get HTML file"""
897  fMan = os.path.join(self.content.fspath, self.grass_command + ".html")
898  if os.path.isfile(fMan):
899  return fMan
900 
901  # check also addons
902  aPath = os.getenv('GRASS_ADDON_PATH')
903  if aPath:
904  for path in aPath.split(os.pathsep):
905  faMan = os.path.join(path, "docs", "html",
906  self.grass_command + ".html")
907  if os.path.isfile(faMan):
908  return faMan
909 
910  return None
911 
912  def IsLoaded(self):
913  return self.content.loaded
914 
915  def OnHistory(self):
916  """!Update buttons"""
917  nH = len(self.content.history)
918  iH = self.content.historyIdx
919  if iH == nH - 1:
920  self.btnNext.Enable(False)
921  elif iH > -1:
922  self.btnNext.Enable(True)
923  if iH < 1:
924  self.btnPrev.Enable(False)
925  else:
926  self.btnPrev.Enable(True)
927 
928  def OnNext(self, event):
929  """Load next page"""
930  self.content.historyIdx += 1
931  idx = self.content.historyIdx
932  path = self.content.history[idx]
933  self.content.LoadPage(path)
934  self.OnHistory()
935 
936  event.Skip()
937 
938  def OnPrev(self, event):
939  """Load previous page"""
940  self.content.historyIdx -= 1
941  idx = self.content.historyIdx
942  path = self.content.history[idx]
943  self.content.LoadPage(path)
944  self.OnHistory()
945 
946  event.Skip()