ghelp.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944
  1. """
  2. @package gui_core.ghelp
  3. @brief Help/about window, menu tree, search module tree
  4. Classes:
  5. - ghelp::AboutWindow
  6. - ghelp::HelpFrame
  7. - ghelp::HelpWindow
  8. - ghelp::HelpPanel
  9. (C) 2008-2019 by the GRASS Development Team
  10. This program is free software under the GNU General Public License
  11. (>=v2). Read the file COPYING that comes with GRASS for details.
  12. @author Martin Landa <landa.martin gmail.com>
  13. """
  14. import os
  15. import codecs
  16. import platform
  17. import re
  18. import textwrap
  19. import sys
  20. import six
  21. if sys.version_info.major == 2:
  22. _unichr = unichr
  23. else:
  24. _unichr = chr
  25. import wx
  26. from wx.html import HtmlWindow
  27. try:
  28. from wx.lib.agw.hyperlink import HyperLinkCtrl
  29. except ImportError:
  30. from wx.lib.hyperlink import HyperLinkCtrl
  31. import grass.script as grass
  32. # needed just for testing
  33. if __name__ == '__main__':
  34. from grass.script.setup import set_gui_path
  35. set_gui_path()
  36. from core import globalvar
  37. from core.gcmd import GError, DecodeString
  38. from gui_core.widgets import FormNotebook, ScrolledPanel
  39. from gui_core.wrap import Button, StaticText, TextCtrl
  40. from core.debug import Debug
  41. class AboutWindow(wx.Frame):
  42. """Create custom About Window
  43. """
  44. def __init__(self, parent, size=(770, 460),
  45. title=_('About GRASS GIS'), **kwargs):
  46. wx.Frame.__init__(
  47. self,
  48. parent=parent,
  49. id=wx.ID_ANY,
  50. title=title,
  51. size=size,
  52. **kwargs)
  53. self.panel = wx.Panel(parent=self, id=wx.ID_ANY)
  54. # icon
  55. self.SetIcon(
  56. wx.Icon(
  57. os.path.join(
  58. globalvar.ICONDIR,
  59. 'grass.ico'),
  60. wx.BITMAP_TYPE_ICO))
  61. # notebook
  62. self.aboutNotebook = FormNotebook(self.panel, style=wx.BK_LEFT)
  63. for title, win in ((_("Info"), self._pageInfo()),
  64. (_("Copyright"), self._pageCopyright()),
  65. (_("License"), self._pageLicense()),
  66. (_("Citation"), self._pageCitation()),
  67. (_("Authors"), self._pageCredit()),
  68. (_("Contributors"), self._pageContributors()),
  69. (_("Extra contributors"), self._pageContributors(extra=True)),
  70. (_("Translators"), self._pageTranslators()),
  71. (_("Translation status"), self._pageStats())):
  72. self.aboutNotebook.AddPage(page=win, text=title)
  73. wx.CallAfter(self.aboutNotebook.SetSelection, 0)
  74. wx.CallAfter(self.aboutNotebook.Refresh)
  75. # buttons
  76. self.btnClose = Button(parent=self.panel, id=wx.ID_CLOSE)
  77. self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
  78. self._doLayout()
  79. def _doLayout(self):
  80. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  81. btnSizer.Add(self.btnClose, proportion=0,
  82. flag=wx.ALL | wx.ALIGN_RIGHT,
  83. border=5)
  84. sizer = wx.BoxSizer(wx.VERTICAL)
  85. sizer.Add(self.aboutNotebook, proportion=1,
  86. flag=wx.EXPAND | wx.ALL, border=1)
  87. sizer.Add(btnSizer, proportion=0,
  88. flag=wx.ALL | wx.ALIGN_RIGHT, border=1)
  89. self.SetMinSize((400, 400))
  90. self.panel.SetSizer(sizer)
  91. sizer.Fit(self.panel)
  92. self.Layout()
  93. def _pageInfo(self):
  94. """Info page"""
  95. # get version and web site
  96. vInfo = grass.version()
  97. if not vInfo:
  98. sys.stderr.write(_("Unable to get GRASS version\n"))
  99. infoTxt = ScrolledPanel(self.aboutNotebook)
  100. infoTxt.SetBackgroundColour('WHITE')
  101. infoTxt.SetupScrolling()
  102. infoSizer = wx.BoxSizer(wx.VERTICAL)
  103. infoGridSizer = wx.GridBagSizer(vgap=5, hgap=5)
  104. logo = os.path.join(globalvar.ICONDIR, "grass-64x64.png")
  105. logoBitmap = wx.StaticBitmap(infoTxt, wx.ID_ANY,
  106. wx.Bitmap(name=logo, type=wx.BITMAP_TYPE_PNG))
  107. infoSizer.Add(logoBitmap, proportion=0,
  108. flag=wx.ALL | wx.ALIGN_CENTER, border=20)
  109. infoLabel = 'GRASS GIS %s' % vInfo.get('version', _('unknown version'))
  110. if 'x86_64' in vInfo.get('build_platform', ''):
  111. infoLabel += ' (64bit)'
  112. info = StaticText(parent=infoTxt, id=wx.ID_ANY,
  113. label=infoLabel + os.linesep)
  114. info.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.BOLD, 0, ""))
  115. info.SetForegroundColour(wx.Colour(35, 142, 35))
  116. infoSizer.Add(info, proportion=0,
  117. flag=wx.BOTTOM | wx.ALIGN_CENTER, border=1)
  118. team = StaticText(parent=infoTxt, label=_grassDevTeam(1999) + '\n')
  119. infoSizer.Add(team, proportion=0,
  120. flag=wx.BOTTOM | wx.ALIGN_CENTER, border=1)
  121. row = 0
  122. infoGridSizer.Add(StaticText(parent=infoTxt, id=wx.ID_ANY,
  123. label=_('Official GRASS site:')),
  124. pos=(row, 0),
  125. flag=wx.ALIGN_RIGHT)
  126. infoGridSizer.Add(HyperLinkCtrl(parent=infoTxt, id=wx.ID_ANY,
  127. label='https://grass.osgeo.org'),
  128. pos=(row, 1),
  129. flag=wx.ALIGN_LEFT)
  130. row += 2
  131. infoGridSizer.Add(StaticText(parent=infoTxt, id=wx.ID_ANY,
  132. label='%s:' % _('Code Revision')),
  133. pos=(row, 0),
  134. flag=wx.ALIGN_RIGHT)
  135. infoGridSizer.Add(HyperLinkCtrl(parent=infoTxt, id=wx.ID_ANY,
  136. label=vInfo.get('revision', '?'),
  137. URL='https://github.com/OSGeo/grass.git'),
  138. pos=(row, 1),
  139. flag=wx.ALIGN_LEFT)
  140. row += 1
  141. infoGridSizer.Add(StaticText(parent=infoTxt, id=wx.ID_ANY,
  142. label='%s:' % _('Build Date')),
  143. pos=(row, 0),
  144. flag=wx.ALIGN_RIGHT)
  145. infoGridSizer.Add(
  146. StaticText(
  147. parent=infoTxt, id=wx.ID_ANY, label=vInfo.get(
  148. 'build_date', '?')), pos=(
  149. row, 1), flag=wx.ALIGN_LEFT)
  150. # show only basic info
  151. # row += 1
  152. # infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  153. # label = '%s:' % _('GIS Library Revision')),
  154. # pos = (row, 0),
  155. # flag = wx.ALIGN_RIGHT)
  156. # infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  157. # label = vInfo['libgis_revision'] + ' (' +
  158. # vInfo['libgis_date'].split(' ')[0] + ')'),
  159. # pos = (row, 1),
  160. # flag = wx.ALIGN_LEFT)
  161. row += 2
  162. infoGridSizer.Add(StaticText(parent=infoTxt, id=wx.ID_ANY,
  163. label='Python:'),
  164. pos=(row, 0),
  165. flag=wx.ALIGN_RIGHT)
  166. infoGridSizer.Add(StaticText(parent=infoTxt, id=wx.ID_ANY,
  167. label=platform.python_version()),
  168. pos=(row, 1),
  169. flag=wx.ALIGN_LEFT)
  170. row += 1
  171. infoGridSizer.Add(StaticText(parent=infoTxt, id=wx.ID_ANY,
  172. label='wxPython:'),
  173. pos=(row, 0),
  174. flag=wx.ALIGN_RIGHT)
  175. infoGridSizer.Add(StaticText(parent=infoTxt, id=wx.ID_ANY,
  176. label=wx.__version__),
  177. pos=(row, 1),
  178. flag=wx.ALIGN_LEFT)
  179. infoGridSizer.AddGrowableCol(0)
  180. infoGridSizer.AddGrowableCol(1)
  181. infoSizer.Add(
  182. infoGridSizer,
  183. proportion=1,
  184. flag=wx.EXPAND | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL)
  185. row += 2
  186. infoGridSizer.Add(StaticText(parent=infoTxt, id=wx.ID_ANY,
  187. label="%s:" % _('Language')),
  188. pos=(row, 0),
  189. flag=wx.ALIGN_RIGHT)
  190. self.langUsed = grass.gisenv().get('LANG', None)
  191. if not self.langUsed:
  192. import locale
  193. loc = locale.getdefaultlocale()
  194. if loc == (None, None):
  195. self.langUsed = _('unknown')
  196. else:
  197. self.langUsed = u'%s.%s' % (loc[0], loc[1])
  198. infoGridSizer.Add(StaticText(parent=infoTxt, id=wx.ID_ANY,
  199. label=self.langUsed),
  200. pos=(row, 1),
  201. flag=wx.ALIGN_LEFT)
  202. infoTxt.SetSizer(infoSizer)
  203. infoSizer.Fit(infoTxt)
  204. return infoTxt
  205. def _pageCopyright(self):
  206. """Copyright information"""
  207. copyfile = os.path.join(os.getenv("GISBASE"), "COPYING")
  208. if os.path.exists(copyfile):
  209. copyrightFile = open(copyfile, 'r')
  210. copytext = copyrightFile.read()
  211. copyrightFile.close()
  212. else:
  213. copytext = _('%s file missing') % 'COPYING'
  214. # put text into a scrolling panel
  215. copyrightwin = ScrolledPanel(self.aboutNotebook)
  216. copyrighttxt = TextCtrl(
  217. copyrightwin, id=wx.ID_ANY, value=copytext,
  218. style=wx.TE_MULTILINE | wx.TE_READONLY)
  219. copyrightwin.SetAutoLayout(True)
  220. copyrightwin.sizer = wx.BoxSizer(wx.VERTICAL)
  221. copyrightwin.sizer.Add(copyrighttxt, proportion=1,
  222. flag=wx.EXPAND | wx.ALL, border=3)
  223. copyrightwin.SetSizer(copyrightwin.sizer)
  224. copyrightwin.Layout()
  225. copyrightwin.SetupScrolling()
  226. return copyrightwin
  227. def _pageLicense(self):
  228. """Licence about"""
  229. licfile = os.path.join(os.getenv("GISBASE"), "GPL.TXT")
  230. if os.path.exists(licfile):
  231. licenceFile = open(licfile, 'r')
  232. license = ''.join(licenceFile.readlines())
  233. licenceFile.close()
  234. else:
  235. license = _('%s file missing') % 'GPL.TXT'
  236. # put text into a scrolling panel
  237. licensewin = ScrolledPanel(self.aboutNotebook)
  238. licensetxt = TextCtrl(
  239. licensewin, id=wx.ID_ANY, value=license,
  240. style=wx.TE_MULTILINE | wx.TE_READONLY)
  241. licensewin.SetAutoLayout(True)
  242. licensewin.sizer = wx.BoxSizer(wx.VERTICAL)
  243. licensewin.sizer.Add(licensetxt, proportion=1,
  244. flag=wx.EXPAND | wx.ALL, border=3)
  245. licensewin.SetSizer(licensewin.sizer)
  246. licensewin.Layout()
  247. licensewin.SetupScrolling()
  248. return licensewin
  249. def _pageCitation(self):
  250. """Citation information"""
  251. try:
  252. # import only when needed
  253. import grass.script as gscript
  254. text = gscript.read_command('g.version', flags='x')
  255. except CalledModuleError as error:
  256. text = _("Unable to provide citation suggestion,"
  257. " see GRASS GIS website instead."
  258. " The error was: {0}").format(error)
  259. # put text into a scrolling panel
  260. window = ScrolledPanel(self.aboutNotebook)
  261. stat_text = TextCtrl(
  262. window, id=wx.ID_ANY, value=text,
  263. style=wx.TE_MULTILINE | wx.TE_READONLY)
  264. window.SetAutoLayout(True)
  265. window.sizer = wx.BoxSizer(wx.VERTICAL)
  266. window.sizer.Add(stat_text, proportion=1,
  267. flag=wx.EXPAND | wx.ALL, border=3)
  268. window.SetSizer(window.sizer)
  269. window.Layout()
  270. window.SetupScrolling()
  271. return window
  272. def _pageCredit(self):
  273. """Credit about"""
  274. # credits
  275. authfile = os.path.join(os.getenv("GISBASE"), "AUTHORS")
  276. if os.path.exists(authfile):
  277. with codecs.open(authfile, encoding='utf-8', mode='r') as authorsFile:
  278. authors = ''.join(authorsFile.readlines())
  279. else:
  280. authors = _('%s file missing') % 'AUTHORS'
  281. authorwin = ScrolledPanel(self.aboutNotebook)
  282. authortxt = TextCtrl(
  283. authorwin, id=wx.ID_ANY, value=authors,
  284. style=wx.TE_MULTILINE | wx.TE_READONLY)
  285. authorwin.SetAutoLayout(True)
  286. authorwin.SetupScrolling()
  287. authorwin.sizer = wx.BoxSizer(wx.VERTICAL)
  288. authorwin.sizer.Add(authortxt, proportion=1,
  289. flag=wx.EXPAND | wx.ALL, border=3)
  290. authorwin.SetSizer(authorwin.sizer)
  291. authorwin.Layout()
  292. return authorwin
  293. def _pageContributors(self, extra=False):
  294. """Contributors info"""
  295. if extra:
  296. contribfile = os.path.join(
  297. os.getenv("GISBASE"),
  298. "contributors_extra.csv")
  299. else:
  300. contribfile = os.path.join(
  301. os.getenv("GISBASE"),
  302. "contributors.csv")
  303. if os.path.exists(contribfile):
  304. contribFile = codecs.open(contribfile, encoding='utf-8', mode='r')
  305. contribs = list()
  306. errLines = list()
  307. for line in contribFile.readlines()[1:]:
  308. line = line.rstrip('\n')
  309. try:
  310. if extra:
  311. name, email, country, rfc2_agreed = line.split(',')
  312. else:
  313. cvs_id, name, email, country, osgeo_id, rfc2_agreed = line.split(
  314. ',')
  315. except ValueError:
  316. errLines.append(line)
  317. continue
  318. if extra:
  319. contribs.append((name, email, country))
  320. else:
  321. contribs.append((name, email, country, osgeo_id))
  322. contribFile.close()
  323. if errLines:
  324. GError(parent=self, message=_("Error when reading file '%s'.") %
  325. contribfile + "\n\n" + _("Lines:") + " %s" %
  326. os.linesep.join(map(DecodeString, errLines)))
  327. else:
  328. contribs = None
  329. contribwin = ScrolledPanel(self.aboutNotebook)
  330. contribwin.SetAutoLayout(True)
  331. contribwin.SetupScrolling()
  332. contribwin.sizer = wx.BoxSizer(wx.VERTICAL)
  333. if not contribs:
  334. contribtxt = StaticText(
  335. contribwin,
  336. id=wx.ID_ANY,
  337. label=_('%s file missing') %
  338. contribfile)
  339. contribwin.sizer.Add(contribtxt, proportion=1,
  340. flag=wx.EXPAND | wx.ALL, border=3)
  341. else:
  342. if extra:
  343. items = (_('Name'), _('E-mail'), _('Country'))
  344. else:
  345. items = (_('Name'), _('E-mail'), _('Country'), _('OSGeo_ID'))
  346. contribBox = wx.FlexGridSizer(cols=len(items), vgap=5, hgap=5)
  347. for item in items:
  348. text = StaticText(parent=contribwin, id=wx.ID_ANY,
  349. label=item)
  350. text.SetFont(
  351. wx.Font(
  352. 10,
  353. wx.DEFAULT,
  354. wx.NORMAL,
  355. wx.BOLD,
  356. 0,
  357. ""))
  358. contribBox.Add(text)
  359. for vals in sorted(contribs, key=lambda x: x[0]):
  360. for item in vals:
  361. contribBox.Add(
  362. StaticText(
  363. parent=contribwin,
  364. id=wx.ID_ANY,
  365. label=item))
  366. contribwin.sizer.Add(contribBox, proportion=1,
  367. flag=wx.EXPAND | wx.ALL, border=3)
  368. contribwin.SetSizer(contribwin.sizer)
  369. contribwin.Layout()
  370. return contribwin
  371. def _pageTranslators(self):
  372. """Translators info"""
  373. translatorsfile = os.path.join(os.getenv("GISBASE"), "translators.csv")
  374. if os.path.exists(translatorsfile):
  375. translatorsFile = codecs.open(translatorsfile, encoding='utf-8', mode='r')
  376. translators = dict()
  377. errLines = list()
  378. for line in translatorsFile.readlines()[1:]:
  379. line = line.rstrip('\n')
  380. try:
  381. name, email, languages = line.split(',')
  382. except ValueError:
  383. errLines.append(line)
  384. continue
  385. for language in languages.split(' '):
  386. if language not in translators:
  387. translators[language] = list()
  388. translators[language].append((name, email))
  389. translatorsFile.close()
  390. if errLines:
  391. GError(parent=self, message=_("Error when reading file '%s'.") %
  392. translatorsfile + "\n\n" + _("Lines:") + " %s" %
  393. os.linesep.join(map(DecodeString, errLines)))
  394. else:
  395. translators = None
  396. translatorswin = ScrolledPanel(self.aboutNotebook)
  397. translatorswin.SetBackgroundColour('WHITE')
  398. translatorswin.SetAutoLayout(True)
  399. translatorswin.SetupScrolling()
  400. translatorswin.sizer = wx.BoxSizer(wx.VERTICAL)
  401. if not translators:
  402. translatorstxt = StaticText(
  403. translatorswin,
  404. id=wx.ID_ANY,
  405. label=_('%s file missing') %
  406. 'translators.csv')
  407. translatorswin.sizer.Add(translatorstxt, proportion=1,
  408. flag=wx.EXPAND | wx.ALL, border=3)
  409. else:
  410. translatorsBox = wx.FlexGridSizer(cols=4, vgap=5, hgap=5)
  411. languages = sorted(translators.keys())
  412. tname = StaticText(parent=translatorswin, id=wx.ID_ANY,
  413. label=_('Name'))
  414. tname.SetFont(wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD, 0, ""))
  415. translatorsBox.Add(tname)
  416. temail = StaticText(parent=translatorswin, id=wx.ID_ANY,
  417. label=_('E-mail'))
  418. temail.SetFont(wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD, 0, ""))
  419. translatorsBox.Add(temail)
  420. tlang = StaticText(parent=translatorswin, id=wx.ID_ANY,
  421. label=_('Language'))
  422. tlang.SetFont(wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD, 0, ""))
  423. translatorsBox.Add(tlang)
  424. tnat = StaticText(parent=translatorswin, id=wx.ID_ANY,
  425. label=_('Nation'))
  426. tnat.SetFont(wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD, 0, ""))
  427. translatorsBox.Add(tnat)
  428. for lang in languages:
  429. for translator in translators[lang]:
  430. name, email = translator
  431. translatorsBox.Add(
  432. StaticText(
  433. parent=translatorswin,
  434. id=wx.ID_ANY,
  435. label=name))
  436. translatorsBox.Add(
  437. StaticText(
  438. parent=translatorswin,
  439. id=wx.ID_ANY,
  440. label=email))
  441. translatorsBox.Add(
  442. StaticText(
  443. parent=translatorswin,
  444. id=wx.ID_ANY,
  445. label=lang))
  446. flag = os.path.join(
  447. globalvar.ICONDIR, "flags", "%s.png" %
  448. lang.lower())
  449. if os.path.exists(flag):
  450. flagBitmap = wx.StaticBitmap(translatorswin, wx.ID_ANY, wx.Bitmap(
  451. name=flag, type=wx.BITMAP_TYPE_PNG))
  452. translatorsBox.Add(flagBitmap)
  453. else:
  454. translatorsBox.Add(
  455. StaticText(
  456. parent=translatorswin,
  457. id=wx.ID_ANY,
  458. label=lang))
  459. translatorswin.sizer.Add(translatorsBox, proportion=1,
  460. flag=wx.EXPAND | wx.ALL, border=3)
  461. translatorswin.SetSizer(translatorswin.sizer)
  462. translatorswin.Layout()
  463. return translatorswin
  464. def _langString(self, k, v):
  465. """Return string for the status of translation"""
  466. allStr = "%s :" % k.upper()
  467. try:
  468. allStr += _(" %d translated" % v['good'])
  469. except:
  470. pass
  471. try:
  472. allStr += _(" %d fuzzy" % v['fuzzy'])
  473. except:
  474. pass
  475. try:
  476. allStr += _(" %d untranslated" % v['bad'])
  477. except:
  478. pass
  479. return allStr
  480. def _langBox(self, par, k, v):
  481. """Return box"""
  482. langBox = wx.FlexGridSizer(cols=4, vgap=5, hgap=5)
  483. tkey = StaticText(parent=par, id=wx.ID_ANY,
  484. label=k.upper())
  485. langBox.Add(tkey)
  486. try:
  487. tgood = StaticText(parent=par, id=wx.ID_ANY,
  488. label=_("%d translated" % v['good']))
  489. tgood.SetForegroundColour(wx.Colour(35, 142, 35))
  490. langBox.Add(tgood)
  491. except:
  492. tgood = StaticText(parent=par, id=wx.ID_ANY,
  493. label="")
  494. langBox.Add(tgood)
  495. try:
  496. tfuzzy = StaticText(parent=par, id=wx.ID_ANY,
  497. label=_(" %d fuzzy" % v['fuzzy']))
  498. tfuzzy.SetForegroundColour(wx.Colour(255, 142, 0))
  499. langBox.Add(tfuzzy)
  500. except:
  501. tfuzzy = StaticText(parent=par, id=wx.ID_ANY,
  502. label="")
  503. langBox.Add(tfuzzy)
  504. try:
  505. tbad = StaticText(parent=par, id=wx.ID_ANY,
  506. label=_(" %d untranslated" % v['bad']))
  507. tbad.SetForegroundColour(wx.Colour(255, 0, 0))
  508. langBox.Add(tbad)
  509. except:
  510. tbad = StaticText(parent=par, id=wx.ID_ANY,
  511. label="")
  512. langBox.Add(tbad)
  513. return langBox
  514. def _langPanel(self, lang, js):
  515. """Create panel for each languages"""
  516. text = self._langString(lang, js['total'])
  517. panel = wx.CollapsiblePane(
  518. self.statswin, -1, label=text, style=wx.CP_DEFAULT_STYLE | wx.CP_NO_TLW_RESIZE)
  519. panel.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnPaneChanged)
  520. win = panel.GetPane()
  521. # TODO IT DOESN'T WORK
  522. # TO ADD ONLY WHEN TAB IS OPENED
  523. # if lang == self.langUsed.split('_')[0]:
  524. # panel.Collapse(False)
  525. # else:
  526. # panel.Collapse(True)
  527. pageSizer = wx.BoxSizer(wx.VERTICAL)
  528. for k, v in six.iteritems(js):
  529. if k != 'total' and k != 'name':
  530. box = self._langBox(win, k, v)
  531. pageSizer.Add(box, proportion=1,
  532. flag=wx.EXPAND | wx.ALL, border=3)
  533. win.SetSizer(pageSizer)
  534. pageSizer.SetSizeHints(win)
  535. return panel
  536. def OnPaneChanged(self, evt):
  537. """Redo the layout"""
  538. # TODO better to test on Windows
  539. self.statswin.SetupScrolling(scrollToTop=False)
  540. def _pageStats(self):
  541. """Translation statistics info"""
  542. fname = "translation_status.json"
  543. statsfile = os.path.join(os.getenv("GISBASE"), fname)
  544. if os.path.exists(statsfile):
  545. statsFile = open(statsfile)
  546. import json
  547. jsStats = json.load(statsFile)
  548. else:
  549. jsStats = None
  550. self.statswin = ScrolledPanel(self.aboutNotebook)
  551. self.statswin.SetBackgroundColour('WHITE')
  552. self.statswin.SetAutoLayout(True)
  553. if not jsStats:
  554. Debug.msg(5, _("File <%s> not found") % fname)
  555. statsSizer = wx.BoxSizer(wx.VERTICAL)
  556. statstext = StaticText(self.statswin, id=wx.ID_ANY,
  557. label=_('%s file missing') % fname)
  558. statsSizer.Add(statstext, proportion=1,
  559. flag=wx.EXPAND | wx.ALL, border=3)
  560. else:
  561. languages = sorted(jsStats['langs'].keys())
  562. statsSizer = wx.BoxSizer(wx.VERTICAL)
  563. for lang in languages:
  564. v = jsStats['langs'][lang]
  565. panel = self._langPanel(lang, v)
  566. statsSizer.Add(panel)
  567. self.statswin.SetSizer(statsSizer)
  568. self.statswin.SetupScrolling(scroll_x=False, scroll_y=True)
  569. self.statswin.Layout()
  570. self.statswin.Fit()
  571. return self.statswin
  572. def OnCloseWindow(self, event):
  573. """Close window"""
  574. self.Close()
  575. class HelpFrame(wx.Dialog):
  576. """GRASS Quickstart help window
  577. As a base class wx.Dialog is used, because of not working
  578. close button with wx.Frame when dialog is called from wizard.
  579. If parent is None, application TopLevelWindow is used (wxPython
  580. standard behaviour).
  581. Currently not used (was in location wizard before)
  582. due to unsolved problems - window sometimes does not respond.
  583. """
  584. def __init__(self, parent, id, title, size, file):
  585. wx.Dialog.__init__(
  586. self, parent=parent, id=id, title=title, size=size,
  587. style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER | wx.MINIMIZE_BOX)
  588. sizer = wx.BoxSizer(wx.VERTICAL)
  589. # text
  590. content = HelpPanel(parent=self)
  591. content.LoadPage(file)
  592. sizer.Add(content, proportion=1, flag=wx.EXPAND)
  593. self.SetAutoLayout(True)
  594. self.SetSizer(sizer)
  595. self.Layout()
  596. class HelpWindow(HtmlWindow):
  597. """This panel holds the text from GRASS docs.
  598. GISBASE must be set in the environment to find the html docs dir.
  599. The SYNOPSIS section is skipped, since this Panel is supposed to
  600. be integrated into the cmdPanel and options are obvious there.
  601. """
  602. def __init__(self, parent, command, text, skipDescription,
  603. **kwargs):
  604. """If command is given, the corresponding HTML help
  605. file will be presented, with all links pointing to absolute
  606. paths of local files.
  607. If 'skipDescription' is True, the HTML corresponding to
  608. SYNOPSIS will be skipped, thus only presenting the help file
  609. from the DESCRIPTION section onwards.
  610. If 'text' is given, it must be the HTML text to be presented
  611. in the Panel.
  612. """
  613. self.parent = parent
  614. HtmlWindow.__init__(self, parent=parent, **kwargs)
  615. self.loaded = False
  616. self.history = list()
  617. self.historyIdx = 0
  618. self.fspath = os.path.join(os.getenv("GISBASE"), "docs", "html")
  619. self.SetStandardFonts(size=10)
  620. self.SetBorders(10)
  621. if text is None:
  622. if skipDescription:
  623. url = os.path.join(self.fspath, command + ".html")
  624. self.fillContentsFromFile(url,
  625. skipDescription=skipDescription)
  626. self.history.append(url)
  627. self.loaded = True
  628. else:
  629. # FIXME: calling LoadPage() is strangely time-consuming (only first call)
  630. # self.LoadPage(self.fspath + command + ".html")
  631. self.loaded = False
  632. else:
  633. self.SetPage(text)
  634. self.loaded = True
  635. def OnLinkClicked(self, linkinfo):
  636. url = linkinfo.GetHref()
  637. if url[:4] != 'http':
  638. url = os.path.join(self.fspath, url)
  639. self.history.append(url)
  640. self.historyIdx += 1
  641. self.parent.OnHistory()
  642. super(HelpWindow, self).OnLinkClicked(linkinfo)
  643. def LoadPage(self, path):
  644. super(HelpWindow, self).LoadPage(path)
  645. self.loaded = True
  646. def fillContentsFromFile(self, htmlFile, skipDescription=True):
  647. """Load content from file.
  648. Currently not used.
  649. """
  650. aLink = re.compile(r'(<a href="?)(.+\.html?["\s]*>)', re.IGNORECASE)
  651. imgLink = re.compile(r'(<img src="?)(.+\.[png|gif])', re.IGNORECASE)
  652. try:
  653. contents = []
  654. skip = False
  655. for l in open(htmlFile, "rb").readlines():
  656. if "DESCRIPTION" in l:
  657. skip = False
  658. if not skip:
  659. # do skip the options description if requested
  660. if "SYNOPSIS" in l:
  661. skip = skipDescription
  662. else:
  663. # FIXME: find only first item
  664. findALink = aLink.search(l)
  665. if findALink is not None:
  666. contents.append(
  667. aLink.sub(
  668. findALink.group(1) +
  669. self.fspath +
  670. findALink.group(2),
  671. l))
  672. findImgLink = imgLink.search(l)
  673. if findImgLink is not None:
  674. contents.append(
  675. imgLink.sub(
  676. findImgLink.group(1) +
  677. self.fspath +
  678. findImgLink.group(2),
  679. l))
  680. if findALink is None and findImgLink is None:
  681. contents.append(l)
  682. self.SetPage("".join(contents))
  683. self.loaded = True
  684. except: # The Manual file was not found
  685. self.loaded = False
  686. class HelpPanel(wx.Panel):
  687. def __init__(self, parent, command="index", text=None,
  688. skipDescription=False, **kwargs):
  689. self.command = command
  690. wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
  691. self.content = HelpWindow(self, command, text, skipDescription)
  692. self.btnNext = Button(parent=self, id=wx.ID_ANY,
  693. label=_("&Next"))
  694. self.btnNext.Enable(False)
  695. self.btnPrev = Button(parent=self, id=wx.ID_ANY,
  696. label=_("&Previous"))
  697. self.btnPrev.Enable(False)
  698. self.btnNext.Bind(wx.EVT_BUTTON, self.OnNext)
  699. self.btnPrev.Bind(wx.EVT_BUTTON, self.OnPrev)
  700. self._layout()
  701. def _layout(self):
  702. """Do layout"""
  703. sizer = wx.BoxSizer(wx.VERTICAL)
  704. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  705. btnSizer.Add(self.btnPrev, proportion=0,
  706. flag=wx.ALL, border=5)
  707. btnSizer.Add(wx.Size(1, 1), proportion=1)
  708. btnSizer.Add(self.btnNext, proportion=0,
  709. flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
  710. sizer.Add(self.content, proportion=1,
  711. flag=wx.EXPAND)
  712. sizer.Add(btnSizer, proportion=0,
  713. flag=wx.EXPAND)
  714. self.SetSizer(sizer)
  715. sizer.Fit(self)
  716. def LoadPage(self, path=None):
  717. """Load page"""
  718. if not path:
  719. path = self.GetFile()
  720. self.content.history.append(path)
  721. self.content.LoadPage(path)
  722. def GetFile(self):
  723. """Get HTML file"""
  724. fMan = os.path.join(self.content.fspath, self.command + ".html")
  725. if os.path.isfile(fMan):
  726. return fMan
  727. # check also addons
  728. faMan = os.path.join(os.getenv('GRASS_ADDON_BASE'), "docs", "html",
  729. self.command + ".html")
  730. if os.getenv('GRASS_ADDON_BASE') and \
  731. os.path.isfile(faMan):
  732. return faMan
  733. return None
  734. def IsLoaded(self):
  735. return self.content.loaded
  736. def OnHistory(self):
  737. """Update buttons"""
  738. nH = len(self.content.history)
  739. iH = self.content.historyIdx
  740. if iH == nH - 1:
  741. self.btnNext.Enable(False)
  742. elif iH > -1:
  743. self.btnNext.Enable(True)
  744. if iH < 1:
  745. self.btnPrev.Enable(False)
  746. else:
  747. self.btnPrev.Enable(True)
  748. def OnNext(self, event):
  749. """Load next page"""
  750. self.content.historyIdx += 1
  751. idx = self.content.historyIdx
  752. path = self.content.history[idx]
  753. self.content.LoadPage(path)
  754. self.OnHistory()
  755. event.Skip()
  756. def OnPrev(self, event):
  757. """Load previous page"""
  758. self.content.historyIdx -= 1
  759. idx = self.content.historyIdx
  760. path = self.content.history[idx]
  761. self.content.LoadPage(path)
  762. self.OnHistory()
  763. event.Skip()
  764. def ShowAboutDialog(prgName, startYear):
  765. """Displays About window.
  766. :param prgName: name of the program
  767. :param startYear: the first year of existence of the program
  768. """
  769. info = wx.AboutDialogInfo()
  770. info.SetIcon(
  771. wx.Icon(
  772. os.path.join(
  773. globalvar.ICONDIR,
  774. 'grass.ico'),
  775. wx.BITMAP_TYPE_ICO))
  776. info.SetName(prgName)
  777. info.SetWebSite('http://grass.osgeo.org')
  778. info.SetDescription(
  779. _grassDevTeam(startYear) +
  780. '\n\n' +
  781. '\n'.join(
  782. textwrap.wrap(
  783. 'This program is free software under the GNU General Public License'
  784. '(>=v2). Read the file COPYING that comes with GRASS for details.',
  785. 75)))
  786. wx.AboutBox(info)
  787. def _grassDevTeam(start):
  788. try:
  789. end = grass.version()['date']
  790. except KeyError:
  791. sys.stderr.write(_("Unable to get GRASS version\n"))
  792. from datetime import date
  793. end = date.today().year
  794. return '%(c)s %(start)s-%(end)s by the GRASS Development Team' % {
  795. 'c': _unichr(169), 'start': start, 'end': end}
  796. def main():
  797. """Test application (potentially useful as g.gui.gmanual)"""
  798. app = wx.App()
  799. frame = HelpFrame(parent=None, id=wx.ID_ANY,
  800. title="Test help application",
  801. size=(600, 800), file=sys.argv[1])
  802. frame.Show()
  803. app.MainLoop()
  804. if __name__ == '__main__':
  805. main()