ghelp.py 37 KB

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