ghelp.py 42 KB

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