ghelp.py 34 KB

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