ghelp.py 35 KB

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