ghelp.py 35 KB

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