ghelp.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909
  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.AddGrowableCol(1)
  72. gridSizer.Add(item = self.searchBy,
  73. flag = wx.ALIGN_CENTER_VERTICAL, pos = (0, 0))
  74. gridSizer.Add(item = self.search,
  75. flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (0, 1))
  76. row = 1
  77. if self.showTip:
  78. gridSizer.Add(item = self.searchTip,
  79. flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (row, 0), span = (1, 2))
  80. row += 1
  81. if self.showChoice:
  82. gridSizer.Add(item = self.searchChoice,
  83. flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (row, 0), span = (1, 2))
  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. @todo improve styling
  303. """
  304. def __init__(self, parent, size = (750, 450),
  305. title = _('About GRASS GIS'), **kwargs):
  306. wx.Frame.__init__(self, parent = parent, id = wx.ID_ANY, title = title, size = size, **kwargs)
  307. panel = wx.Panel(parent = self, id = wx.ID_ANY)
  308. # icon
  309. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  310. # get version and web site
  311. vInfo = grass.version()
  312. infoTxt = wx.Panel(parent = panel, id = wx.ID_ANY)
  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 = 25)
  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 = 15)
  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. # create a flat notebook for displaying information about GRASS
  379. aboutNotebook = GNotebook(panel, style = globalvar.FNPageStyle | FN.FNB_NO_X_BUTTON)
  380. aboutNotebook.SetTabAreaColour(globalvar.FNPageColor)
  381. for title, win in ((_("Info"), infoTxt),
  382. (_("Copyright"), self._pageCopyright()),
  383. (_("License"), self._pageLicense()),
  384. (_("Authors"), self._pageCredit()),
  385. (_("Contributors"), self._pageContributors()),
  386. (_("Extra contributors"), self._pageContributors(extra = True)),
  387. (_("Translators"), self._pageTranslators())):
  388. aboutNotebook.AddPage(page = win, text = title)
  389. wx.CallAfter(aboutNotebook.SetSelection, 0)
  390. # buttons
  391. btnClose = wx.Button(parent = panel, id = wx.ID_CLOSE)
  392. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  393. btnSizer.Add(item = btnClose, proportion = 0,
  394. flag = wx.ALL | wx.ALIGN_RIGHT,
  395. border = 5)
  396. # bindings
  397. btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
  398. infoTxt.SetSizer(infoSizer)
  399. infoSizer.Fit(infoTxt)
  400. sizer = wx.BoxSizer(wx.VERTICAL)
  401. sizer.Add(item = aboutNotebook, proportion = 1,
  402. flag = wx.EXPAND | wx.ALL, border = 1)
  403. sizer.Add(item = btnSizer, proportion = 0,
  404. flag = wx.ALL | wx.ALIGN_RIGHT, border = 1)
  405. panel.SetSizer(sizer)
  406. self.Layout()
  407. self.SetMinSize((500, 400))
  408. def _pageCopyright(self):
  409. """Copyright information"""
  410. copyfile = os.path.join(os.getenv("GISBASE"), "COPYING")
  411. if os.path.exists(copyfile):
  412. copyrightFile = open(copyfile, 'r')
  413. copytext = copyrightFile.read()
  414. copyrightFile.close()
  415. else:
  416. copytext = _('%s file missing') % 'COPYING'
  417. # put text into a scrolling panel
  418. copyrightwin = ScrolledPanel(self)
  419. copyrighttxt = wx.StaticText(copyrightwin, id = wx.ID_ANY, label = copytext)
  420. copyrightwin.SetAutoLayout(True)
  421. copyrightwin.sizer = wx.BoxSizer(wx.VERTICAL)
  422. copyrightwin.sizer.Add(item = copyrighttxt, proportion = 1,
  423. flag = wx.EXPAND | wx.ALL, border = 3)
  424. copyrightwin.SetSizer(copyrightwin.sizer)
  425. copyrightwin.Layout()
  426. copyrightwin.SetupScrolling()
  427. return copyrightwin
  428. def _pageLicense(self):
  429. """Licence about"""
  430. licfile = os.path.join(os.getenv("GISBASE"), "GPL.TXT")
  431. if os.path.exists(licfile):
  432. licenceFile = open(licfile, 'r')
  433. license = ''.join(licenceFile.readlines())
  434. licenceFile.close()
  435. else:
  436. license = _('%s file missing') % 'GPL.TXT'
  437. # put text into a scrolling panel
  438. licensewin = ScrolledPanel(self)
  439. licensetxt = wx.StaticText(licensewin, id = wx.ID_ANY, label = license)
  440. licensewin.SetAutoLayout(True)
  441. licensewin.sizer = wx.BoxSizer(wx.VERTICAL)
  442. licensewin.sizer.Add(item = licensetxt, proportion = 1,
  443. flag = wx.EXPAND | wx.ALL, border = 3)
  444. licensewin.SetSizer(licensewin.sizer)
  445. licensewin.Layout()
  446. licensewin.SetupScrolling()
  447. return licensewin
  448. def _pageCredit(self):
  449. """Credit about"""
  450. # credits
  451. authfile = os.path.join(os.getenv("GISBASE"), "AUTHORS")
  452. if os.path.exists(authfile):
  453. authorsFile = open(authfile, 'r')
  454. authors = unicode(''.join(authorsFile.readlines()), "utf-8")
  455. authorsFile.close()
  456. else:
  457. authors = _('%s file missing') % 'AUTHORS'
  458. authorwin = ScrolledPanel(self)
  459. authortxt = wx.StaticText(authorwin, id = wx.ID_ANY, label = authors)
  460. authorwin.SetAutoLayout(True)
  461. authorwin.SetupScrolling()
  462. authorwin.sizer = wx.BoxSizer(wx.VERTICAL)
  463. authorwin.sizer.Add(item = authortxt, proportion = 1,
  464. flag = wx.EXPAND | wx.ALL, border = 3)
  465. authorwin.SetSizer(authorwin.sizer)
  466. authorwin.Layout()
  467. return authorwin
  468. def _pageContributors(self, extra = False):
  469. """Contributors info"""
  470. if extra:
  471. contribfile = os.path.join(os.getenv("GISBASE"), "contributors_extra.csv")
  472. else:
  473. contribfile = os.path.join(os.getenv("GISBASE"), "contributors.csv")
  474. if os.path.exists(contribfile):
  475. contribFile = codecs.open(contribfile, encoding = 'utf-8', mode = 'r')
  476. contribs = list()
  477. errLines = list()
  478. for line in contribFile.readlines()[1:]:
  479. line = line.rstrip('\n')
  480. try:
  481. if extra:
  482. name, email, country, rfc2_agreed = line.split(',')
  483. else:
  484. cvs_id, name, email, country, osgeo_id, rfc2_agreed = line.split(',')
  485. except ValueError:
  486. errLines.append(line)
  487. continue
  488. if extra:
  489. contribs.append((name, email, country))
  490. else:
  491. contribs.append((name, email, country, osgeo_id))
  492. contribFile.close()
  493. if errLines:
  494. GError(parent = self,
  495. message = _("Error when reading file '%s'.") % contribfile + \
  496. "\n\n" + _("Lines:") + " %s" % \
  497. os.linesep.join(map(DecodeString, errLines)))
  498. else:
  499. contribs = None
  500. contribwin = ScrolledPanel(self)
  501. contribwin.SetAutoLayout(True)
  502. contribwin.SetupScrolling()
  503. contribwin.sizer = wx.BoxSizer(wx.VERTICAL)
  504. if not contribs:
  505. contribtxt = wx.StaticText(contribwin, id = wx.ID_ANY,
  506. label = _('%s file missing') % contribfile)
  507. contribwin.sizer.Add(item = contribtxt, proportion = 1,
  508. flag = wx.EXPAND | wx.ALL, border = 3)
  509. else:
  510. if extra:
  511. items = (_('Name'), _('E-mail'), _('Country'))
  512. else:
  513. items = (_('Name'), _('E-mail'), _('Country'), _('OSGeo_ID'))
  514. contribBox = wx.FlexGridSizer(cols = len(items), vgap = 5, hgap = 5)
  515. for item in items:
  516. contribBox.Add(item = wx.StaticText(parent = contribwin, id = wx.ID_ANY,
  517. label = item))
  518. for vals in sorted(contribs, key = lambda x: x[0]):
  519. for item in vals:
  520. contribBox.Add(item = wx.StaticText(parent = contribwin, id = wx.ID_ANY,
  521. label = item))
  522. contribwin.sizer.Add(item = contribBox, proportion = 1,
  523. flag = wx.EXPAND | wx.ALL, border = 3)
  524. contribwin.SetSizer(contribwin.sizer)
  525. contribwin.Layout()
  526. return contribwin
  527. def _pageTranslators(self):
  528. """Translators info"""
  529. translatorsfile = os.path.join(os.getenv("GISBASE"), "translators.csv")
  530. if os.path.exists(translatorsfile):
  531. translatorsFile = open(translatorsfile, 'r')
  532. translators = dict()
  533. errLines = list()
  534. for line in translatorsFile.readlines()[1:]:
  535. line = line.rstrip('\n')
  536. try:
  537. name, email, languages = line.split(',')
  538. except ValueError:
  539. errLines.append(line)
  540. continue
  541. for language in languages.split(' '):
  542. if language not in translators:
  543. translators[language] = list()
  544. translators[language].append((name, email))
  545. translatorsFile.close()
  546. if errLines:
  547. GError(parent = self,
  548. message = _("Error when reading file '%s'.") % translatorsfile + \
  549. "\n\n" + _("Lines:") + " %s" % \
  550. os.linesep.join(map(DecodeString, errLines)))
  551. else:
  552. translators = None
  553. translatorswin = ScrolledPanel(self)
  554. translatorswin.SetAutoLayout(True)
  555. translatorswin.SetupScrolling()
  556. translatorswin.sizer = wx.BoxSizer(wx.VERTICAL)
  557. if not translators:
  558. translatorstxt = wx.StaticText(translatorswin, id = wx.ID_ANY,
  559. label = _('%s file missing') % 'translators.csv')
  560. translatorswin.sizer.Add(item = translatorstxt, proportion = 1,
  561. flag = wx.EXPAND | wx.ALL, border = 3)
  562. else:
  563. translatorsBox = wx.FlexGridSizer(cols = 3, vgap = 5, hgap = 5)
  564. languages = translators.keys()
  565. languages.sort()
  566. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  567. label = _('Name')))
  568. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  569. label = _('E-mail')))
  570. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  571. label = _('Language')))
  572. for lang in languages:
  573. for translator in translators[lang]:
  574. name, email = translator
  575. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  576. label = unicode(name, "utf-8")))
  577. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  578. label = email))
  579. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  580. label = lang))
  581. translatorswin.sizer.Add(item = translatorsBox, proportion = 1,
  582. flag = wx.EXPAND | wx.ALL, border = 3)
  583. translatorswin.SetSizer(translatorswin.sizer)
  584. translatorswin.Layout()
  585. return translatorswin
  586. def OnCloseWindow(self, event):
  587. """!Close window"""
  588. self.Close()
  589. class HelpFrame(wx.Frame):
  590. """!GRASS Quickstart help window"""
  591. def __init__(self, parent, id, title, size, file):
  592. wx.Frame.__init__(self, parent = parent, id = id, title = title, size = size)
  593. sizer = wx.BoxSizer(wx.VERTICAL)
  594. # text
  595. content = HelpPanel(parent = self)
  596. content.LoadPage(file)
  597. sizer.Add(item = content, proportion = 1, flag = wx.EXPAND)
  598. self.SetAutoLayout(True)
  599. self.SetSizer(sizer)
  600. self.Layout()
  601. class HelpWindow(HtmlWindow):
  602. """!This panel holds the text from GRASS docs.
  603. GISBASE must be set in the environment to find the html docs dir.
  604. The SYNOPSIS section is skipped, since this Panel is supposed to
  605. be integrated into the cmdPanel and options are obvious there.
  606. """
  607. def __init__(self, parent, grass_command, text, skip_description,
  608. **kwargs):
  609. """!If grass_command is given, the corresponding HTML help
  610. file will be presented, with all links pointing to absolute
  611. paths of local files.
  612. If 'skip_description' is True, the HTML corresponding to
  613. SYNOPSIS will be skipped, thus only presenting the help file
  614. from the DESCRIPTION section onwards.
  615. If 'text' is given, it must be the HTML text to be presented
  616. in the Panel.
  617. """
  618. self.parent = parent
  619. wx.InitAllImageHandlers()
  620. HtmlWindow.__init__(self, parent = parent, **kwargs)
  621. gisbase = os.getenv("GISBASE")
  622. self.loaded = False
  623. self.history = list()
  624. self.historyIdx = 0
  625. self.fspath = os.path.join(gisbase, "docs", "html")
  626. self.SetStandardFonts (size = 10)
  627. self.SetBorders(10)
  628. if text is None:
  629. if skip_description:
  630. url = os.path.join(self.fspath, grass_command + ".html")
  631. self.fillContentsFromFile(url,
  632. skip_description = skip_description)
  633. self.history.append(url)
  634. self.loaded = True
  635. else:
  636. ### FIXME: calling LoadPage() is strangely time-consuming (only first call)
  637. # self.LoadPage(self.fspath + grass_command + ".html")
  638. self.loaded = False
  639. else:
  640. self.SetPage(text)
  641. self.loaded = True
  642. def OnLinkClicked(self, linkinfo):
  643. url = linkinfo.GetHref()
  644. if url[:4] != 'http':
  645. url = os.path.join(self.fspath, url)
  646. self.history.append(url)
  647. self.historyIdx += 1
  648. self.parent.OnHistory()
  649. super(HelpWindow, self).OnLinkClicked(linkinfo)
  650. def fillContentsFromFile(self, htmlFile, skip_description = True):
  651. """!Load content from file"""
  652. aLink = re.compile(r'(<a href="?)(.+\.html?["\s]*>)', re.IGNORECASE)
  653. imgLink = re.compile(r'(<img src="?)(.+\.[png|gif])', re.IGNORECASE)
  654. try:
  655. contents = []
  656. skip = False
  657. for l in file(htmlFile, "rb").readlines():
  658. if "DESCRIPTION" in l:
  659. skip = False
  660. if not skip:
  661. # do skip the options description if requested
  662. if "SYNOPSIS" in l:
  663. skip = skip_description
  664. else:
  665. # FIXME: find only first item
  666. findALink = aLink.search(l)
  667. if findALink is not None:
  668. contents.append(aLink.sub(findALink.group(1)+
  669. self.fspath+findALink.group(2),l))
  670. findImgLink = imgLink.search(l)
  671. if findImgLink is not None:
  672. contents.append(imgLink.sub(findImgLink.group(1)+
  673. self.fspath+findImgLink.group(2),l))
  674. if findALink is None and findImgLink is None:
  675. contents.append(l)
  676. self.SetPage("".join(contents))
  677. self.loaded = True
  678. except: # The Manual file was not found
  679. self.loaded = False
  680. class HelpPanel(wx.Panel):
  681. def __init__(self, parent, grass_command = "index", text = None,
  682. skip_description = False, **kwargs):
  683. self.grass_command = grass_command
  684. wx.Panel.__init__(self, parent = parent, id = wx.ID_ANY)
  685. self.content = HelpWindow(self, grass_command, text,
  686. skip_description)
  687. self.btnNext = wx.Button(parent = self, id = wx.ID_ANY,
  688. label = _("&Next"))
  689. self.btnNext.Enable(False)
  690. self.btnPrev = wx.Button(parent = self, id = wx.ID_ANY,
  691. label = _("&Previous"))
  692. self.btnPrev.Enable(False)
  693. self.btnNext.Bind(wx.EVT_BUTTON, self.OnNext)
  694. self.btnPrev.Bind(wx.EVT_BUTTON, self.OnPrev)
  695. self._layout()
  696. def _layout(self):
  697. """!Do layout"""
  698. sizer = wx.BoxSizer(wx.VERTICAL)
  699. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  700. btnSizer.Add(item = self.btnPrev, proportion = 0,
  701. flag = wx.ALL, border = 5)
  702. btnSizer.Add(item = wx.Size(1, 1), proportion = 1)
  703. btnSizer.Add(item = self.btnNext, proportion = 0,
  704. flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
  705. sizer.Add(item = self.content, proportion = 1,
  706. flag = wx.EXPAND)
  707. sizer.Add(item = btnSizer, proportion = 0,
  708. flag = wx.EXPAND)
  709. self.SetSizer(sizer)
  710. sizer.Fit(self)
  711. def LoadPage(self, path = None):
  712. """!Load page"""
  713. if not path:
  714. path = os.path.join(self.content.fspath, self.grass_command + ".html")
  715. self.content.history.append(path)
  716. self.content.LoadPage(path)
  717. def IsFile(self):
  718. """!Check if file exists"""
  719. return os.path.isfile(os.path.join(self.content.fspath, self.grass_command + ".html"))
  720. def IsLoaded(self):
  721. return self.content.loaded
  722. def OnHistory(self):
  723. """!Update buttons"""
  724. nH = len(self.content.history)
  725. iH = self.content.historyIdx
  726. if iH == nH - 1:
  727. self.btnNext.Enable(False)
  728. elif iH > -1:
  729. self.btnNext.Enable(True)
  730. if iH < 1:
  731. self.btnPrev.Enable(False)
  732. else:
  733. self.btnPrev.Enable(True)
  734. def OnNext(self, event):
  735. """Load next page"""
  736. self.content.historyIdx += 1
  737. idx = self.content.historyIdx
  738. path = self.content.history[idx]
  739. self.content.LoadPage(path)
  740. self.OnHistory()
  741. event.Skip()
  742. def OnPrev(self, event):
  743. """Load previous page"""
  744. self.content.historyIdx -= 1
  745. idx = self.content.historyIdx
  746. path = self.content.history[idx]
  747. self.content.LoadPage(path)
  748. self.OnHistory()
  749. event.Skip()