ghelp.py 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064
  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. 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. 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 = ScrolledPanel(parent = panel)
  313. infoTxt.SetupScrolling()
  314. infoSizer = wx.BoxSizer(wx.VERTICAL)
  315. infoGridSizer = wx.GridBagSizer(vgap = 5, hgap = 5)
  316. infoGridSizer.AddGrowableCol(0)
  317. infoGridSizer.AddGrowableCol(1)
  318. logo = os.path.join(globalvar.ETCDIR, "gui", "icons", "grass-64x64.png")
  319. logoBitmap = wx.StaticBitmap(parent = infoTxt, id = wx.ID_ANY,
  320. bitmap = wx.Bitmap(name = logo,
  321. type = wx.BITMAP_TYPE_PNG))
  322. infoSizer.Add(item = logoBitmap, proportion = 0,
  323. flag = wx.ALL | wx.ALIGN_CENTER, border = 20)
  324. info = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  325. label = 'GRASS GIS ' + vInfo['version'] + '\n\n')
  326. info.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.BOLD, 0, ""))
  327. info.SetForegroundColour(wx.Colour(35, 142, 35))
  328. infoSizer.Add(item = info, proportion = 0,
  329. flag = wx.BOTTOM | wx.ALIGN_CENTER, border = 1)
  330. row = 0
  331. infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  332. label = _('Official GRASS site:')),
  333. pos = (row, 0),
  334. flag = wx.ALIGN_RIGHT)
  335. infoGridSizer.Add(item = HyperLinkCtrl(parent = infoTxt, id = wx.ID_ANY,
  336. label = 'http://grass.osgeo.org'),
  337. pos = (row, 1),
  338. flag = wx.ALIGN_LEFT)
  339. row += 2
  340. infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  341. label = _('SVN Revision:')),
  342. pos = (row, 0),
  343. flag = wx.ALIGN_RIGHT)
  344. infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  345. label = vInfo['revision']),
  346. pos = (row, 1),
  347. flag = wx.ALIGN_LEFT)
  348. row += 1
  349. infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  350. label = _('GIS Library Revision:')),
  351. pos = (row, 0),
  352. flag = wx.ALIGN_RIGHT)
  353. infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  354. label = vInfo['libgis_revision'] + ' (' +
  355. vInfo['libgis_date'].split(' ')[0] + ')'),
  356. pos = (row, 1),
  357. flag = wx.ALIGN_LEFT)
  358. row += 2
  359. infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  360. label = _('Python:')),
  361. pos = (row, 0),
  362. flag = wx.ALIGN_RIGHT)
  363. infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  364. label = platform.python_version()),
  365. pos = (row, 1),
  366. flag = wx.ALIGN_LEFT)
  367. row += 1
  368. infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  369. label = _('wxPython:')),
  370. pos = (row, 0),
  371. flag = wx.ALIGN_RIGHT)
  372. infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  373. label = wx.__version__),
  374. pos = (row, 1),
  375. flag = wx.ALIGN_LEFT)
  376. infoSizer.Add(item = infoGridSizer,
  377. proportion = 1,
  378. flag = wx.EXPAND | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL)
  379. row += 2
  380. infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  381. label = _('Language:')),
  382. pos = (row, 0),
  383. flag = wx.ALIGN_RIGHT)
  384. self.langUsed = grass.gisenv().get('LANG', None)
  385. if not self.langUsed:
  386. import locale
  387. self.langUsed = '.'.join(locale.getdefaultlocale())
  388. infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  389. label = self.langUsed),
  390. pos = (row, 1),
  391. flag = wx.ALIGN_LEFT)
  392. # create a flat notebook for displaying information about GRASS
  393. aboutNotebook = GNotebook(panel, style = globalvar.FNPageStyle | FN.FNB_NO_X_BUTTON)
  394. aboutNotebook.SetTabAreaColour(globalvar.FNPageColor)
  395. for title, win in ((_("Info"), infoTxt),
  396. (_("Copyright"), self._pageCopyright()),
  397. (_("License"), self._pageLicense()),
  398. (_("Authors"), self._pageCredit()),
  399. (_("Contributors"), self._pageContributors()),
  400. (_("Extra contributors"), self._pageContributors(extra = True)),
  401. (_("Translators"), self._pageTranslators()),
  402. (_("Translation status"), self._pageStats())):
  403. aboutNotebook.AddPage(page = win, text = title)
  404. wx.CallAfter(aboutNotebook.SetSelection, 0)
  405. # buttons
  406. btnClose = wx.Button(parent = panel, id = wx.ID_CLOSE)
  407. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  408. btnSizer.Add(item = btnClose, proportion = 0,
  409. flag = wx.ALL | wx.ALIGN_RIGHT,
  410. border = 5)
  411. # bindings
  412. btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
  413. infoTxt.SetSizer(infoSizer)
  414. infoSizer.Fit(infoTxt)
  415. sizer = wx.BoxSizer(wx.VERTICAL)
  416. sizer.Add(item = aboutNotebook, proportion = 1,
  417. flag = wx.EXPAND | wx.ALL, border = 1)
  418. sizer.Add(item = btnSizer, proportion = 0,
  419. flag = wx.ALL | wx.ALIGN_RIGHT, border = 1)
  420. panel.SetSizer(sizer)
  421. self.Layout()
  422. self.SetMinSize((400, 400))
  423. def _pageCopyright(self):
  424. """Copyright information"""
  425. copyfile = os.path.join(os.getenv("GISBASE"), "COPYING")
  426. if os.path.exists(copyfile):
  427. copyrightFile = open(copyfile, 'r')
  428. copytext = copyrightFile.read()
  429. copyrightFile.close()
  430. else:
  431. copytext = _('%s file missing') % 'COPYING'
  432. # put text into a scrolling panel
  433. copyrightwin = ScrolledPanel(self)
  434. copyrighttxt = wx.StaticText(copyrightwin, id = wx.ID_ANY, label = copytext)
  435. copyrightwin.SetAutoLayout(True)
  436. copyrightwin.sizer = wx.BoxSizer(wx.VERTICAL)
  437. copyrightwin.sizer.Add(item = copyrighttxt, proportion = 1,
  438. flag = wx.EXPAND | wx.ALL, border = 3)
  439. copyrightwin.SetSizer(copyrightwin.sizer)
  440. copyrightwin.Layout()
  441. copyrightwin.SetupScrolling()
  442. return copyrightwin
  443. def _pageLicense(self):
  444. """Licence about"""
  445. licfile = os.path.join(os.getenv("GISBASE"), "GPL.TXT")
  446. if os.path.exists(licfile):
  447. licenceFile = open(licfile, 'r')
  448. license = ''.join(licenceFile.readlines())
  449. licenceFile.close()
  450. else:
  451. license = _('%s file missing') % 'GPL.TXT'
  452. # put text into a scrolling panel
  453. licensewin = ScrolledPanel(self)
  454. licensetxt = wx.StaticText(licensewin, id = wx.ID_ANY, label = license)
  455. licensewin.SetAutoLayout(True)
  456. licensewin.sizer = wx.BoxSizer(wx.VERTICAL)
  457. licensewin.sizer.Add(item = licensetxt, proportion = 1,
  458. flag = wx.EXPAND | wx.ALL, border = 3)
  459. licensewin.SetSizer(licensewin.sizer)
  460. licensewin.Layout()
  461. licensewin.SetupScrolling()
  462. return licensewin
  463. def _pageCredit(self):
  464. """Credit about"""
  465. # credits
  466. authfile = os.path.join(os.getenv("GISBASE"), "AUTHORS")
  467. if os.path.exists(authfile):
  468. authorsFile = open(authfile, 'r')
  469. authors = unicode(''.join(authorsFile.readlines()), "utf-8")
  470. authorsFile.close()
  471. else:
  472. authors = _('%s file missing') % 'AUTHORS'
  473. authorwin = ScrolledPanel(self)
  474. authortxt = wx.StaticText(authorwin, id = wx.ID_ANY, label = authors)
  475. authorwin.SetAutoLayout(True)
  476. authorwin.SetupScrolling()
  477. authorwin.sizer = wx.BoxSizer(wx.VERTICAL)
  478. authorwin.sizer.Add(item = authortxt, proportion = 1,
  479. flag = wx.EXPAND | wx.ALL, border = 3)
  480. authorwin.SetSizer(authorwin.sizer)
  481. authorwin.Layout()
  482. return authorwin
  483. def _pageContributors(self, extra = False):
  484. """Contributors info"""
  485. if extra:
  486. contribfile = os.path.join(os.getenv("GISBASE"), "contributors_extra.csv")
  487. else:
  488. contribfile = os.path.join(os.getenv("GISBASE"), "contributors.csv")
  489. if os.path.exists(contribfile):
  490. contribFile = codecs.open(contribfile, encoding = 'utf-8', mode = 'r')
  491. contribs = list()
  492. errLines = list()
  493. for line in contribFile.readlines()[1:]:
  494. line = line.rstrip('\n')
  495. try:
  496. if extra:
  497. name, email, country, rfc2_agreed = line.split(',')
  498. else:
  499. cvs_id, name, email, country, osgeo_id, rfc2_agreed = line.split(',')
  500. except ValueError:
  501. errLines.append(line)
  502. continue
  503. if extra:
  504. contribs.append((name, email, country))
  505. else:
  506. contribs.append((name, email, country, osgeo_id))
  507. contribFile.close()
  508. if errLines:
  509. GError(parent = self,
  510. message = _("Error when reading file '%s'.") % contribfile + \
  511. "\n\n" + _("Lines:") + " %s" % \
  512. os.linesep.join(map(DecodeString, errLines)))
  513. else:
  514. contribs = None
  515. contribwin = ScrolledPanel(self)
  516. contribwin.SetAutoLayout(True)
  517. contribwin.SetupScrolling()
  518. contribwin.sizer = wx.BoxSizer(wx.VERTICAL)
  519. if not contribs:
  520. contribtxt = wx.StaticText(contribwin, id = wx.ID_ANY,
  521. label = _('%s file missing') % contribfile)
  522. contribwin.sizer.Add(item = contribtxt, proportion = 1,
  523. flag = wx.EXPAND | wx.ALL, border = 3)
  524. else:
  525. if extra:
  526. items = (_('Name'), _('E-mail'), _('Country'))
  527. else:
  528. items = (_('Name'), _('E-mail'), _('Country'), _('OSGeo_ID'))
  529. contribBox = wx.FlexGridSizer(cols = len(items), vgap = 5, hgap = 5)
  530. for item in items:
  531. text = wx.StaticText(parent = contribwin, id = wx.ID_ANY,
  532. label = item)
  533. text.SetFont(wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD, 0, ""))
  534. contribBox.Add(item = text)
  535. for vals in sorted(contribs, key = lambda x: x[0]):
  536. for item in vals:
  537. contribBox.Add(item = wx.StaticText(parent = contribwin, id = wx.ID_ANY,
  538. label = item))
  539. contribwin.sizer.Add(item = contribBox, proportion = 1,
  540. flag = wx.EXPAND | wx.ALL, border = 3)
  541. contribwin.SetSizer(contribwin.sizer)
  542. contribwin.Layout()
  543. return contribwin
  544. def _pageTranslators(self):
  545. """Translators info"""
  546. translatorsfile = os.path.join(os.getenv("GISBASE"), "translators.csv")
  547. if os.path.exists(translatorsfile):
  548. translatorsFile = open(translatorsfile, 'r')
  549. translators = dict()
  550. errLines = list()
  551. for line in translatorsFile.readlines()[1:]:
  552. line = line.rstrip('\n')
  553. try:
  554. name, email, languages = line.split(',')
  555. except ValueError:
  556. errLines.append(line)
  557. continue
  558. for language in languages.split(' '):
  559. if language not in translators:
  560. translators[language] = list()
  561. translators[language].append((name, email))
  562. translatorsFile.close()
  563. if errLines:
  564. GError(parent = self,
  565. message = _("Error when reading file '%s'.") % translatorsfile + \
  566. "\n\n" + _("Lines:") + " %s" % \
  567. os.linesep.join(map(DecodeString, errLines)))
  568. else:
  569. translators = None
  570. translatorswin = ScrolledPanel(self)
  571. translatorswin.SetAutoLayout(True)
  572. translatorswin.SetupScrolling()
  573. translatorswin.sizer = wx.BoxSizer(wx.VERTICAL)
  574. if not translators:
  575. translatorstxt = wx.StaticText(translatorswin, id = wx.ID_ANY,
  576. label = _('%s file missing') % 'translators.csv')
  577. translatorswin.sizer.Add(item = translatorstxt, proportion = 1,
  578. flag = wx.EXPAND | wx.ALL, border = 3)
  579. else:
  580. translatorsBox = wx.FlexGridSizer(cols = 4, vgap = 5, hgap = 5)
  581. languages = translators.keys()
  582. languages.sort()
  583. tname = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  584. label = _('Name'))
  585. tname.SetFont(wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD, 0, ""))
  586. translatorsBox.Add(item = tname)
  587. temail = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  588. label = _('E-mail'))
  589. temail.SetFont(wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD, 0, ""))
  590. translatorsBox.Add(item = temail)
  591. tlang = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  592. label = _('Language'))
  593. tlang.SetFont(wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD, 0, ""))
  594. translatorsBox.Add(item = tlang)
  595. tnat = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  596. label = _('Nation'))
  597. tnat.SetFont(wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD, 0, ""))
  598. translatorsBox.Add(item = tnat)
  599. for lang in languages:
  600. for translator in translators[lang]:
  601. name, email = translator
  602. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  603. label = unicode(name, "utf-8")))
  604. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  605. label = email))
  606. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  607. label = lang))
  608. flag = os.path.join(os.getenv("GISBASE"), "etc", "gui",
  609. "icons", "flags", "%s.png" % lang.lower())
  610. if os.path.exists(flag):
  611. flagBitmap = wx.StaticBitmap(parent = translatorswin, id = wx.ID_ANY,
  612. bitmap = wx.Bitmap(name = flag,
  613. type = wx.BITMAP_TYPE_PNG))
  614. translatorsBox.Add(item = flagBitmap)
  615. else:
  616. translatorsBox.Add(item = wx.StaticText(parent = translatorswin,
  617. id = wx.ID_ANY, label = lang))
  618. translatorswin.sizer.Add(item = translatorsBox, proportion = 1,
  619. flag = wx.EXPAND | wx.ALL, border = 3)
  620. translatorswin.SetSizer(translatorswin.sizer)
  621. translatorswin.Layout()
  622. return translatorswin
  623. def _langString(self, k, v):
  624. """Return string for the status of translation"""
  625. allStr = "%s :" % k.upper()
  626. try:
  627. allStr += _(" %d translated" % v['good'])
  628. except:
  629. pass
  630. try:
  631. allStr += _(" %d fuzzy" % v['fuzzy'])
  632. except:
  633. pass
  634. try:
  635. allStr += _(" %d untranslated" % v['bad'])
  636. except:
  637. pass
  638. return allStr
  639. def _langBox(self, par, k, v):
  640. """Return box"""
  641. langBox = wx.FlexGridSizer(cols = 4, vgap = 5, hgap = 5)
  642. tkey = wx.StaticText(parent = par, id = wx.ID_ANY,
  643. label = k.upper())
  644. langBox.Add(item = tkey)
  645. try:
  646. tgood = wx.StaticText(parent = par, id = wx.ID_ANY,
  647. label = _("%d translated" % v['good']))
  648. tgood.SetForegroundColour(wx.Colour(35, 142, 35))
  649. langBox.Add(item = tgood)
  650. except:
  651. tgood = wx.StaticText(parent = par, id = wx.ID_ANY,
  652. label = "")
  653. langBox.Add(item = tgood)
  654. try:
  655. tfuzzy = wx.StaticText(parent = par, id = wx.ID_ANY,
  656. label = _(" %d fuzzy" % v['fuzzy']))
  657. tfuzzy.SetForegroundColour(wx.Colour(255, 142, 0))
  658. langBox.Add(item = tfuzzy)
  659. except:
  660. tfuzzy = wx.StaticText(parent = par, id = wx.ID_ANY,
  661. label = "")
  662. langBox.Add(item = tfuzzy)
  663. try:
  664. tbad = wx.StaticText(parent = par, id = wx.ID_ANY,
  665. label = _(" %d untranslated" % v['bad']))
  666. tbad.SetForegroundColour(wx.Colour(255, 0, 0))
  667. langBox.Add(item = tbad)
  668. except:
  669. tbad = wx.StaticText(parent = par, id = wx.ID_ANY,
  670. label = "")
  671. langBox.Add(item = tbad)
  672. return langBox
  673. def _langPanel(self, lang, js):
  674. """Create panel for each languages"""
  675. text = self._langString(lang, js['total'])
  676. panel = wx.CollapsiblePane(self.statswin, -1, label=text, style=wx.CP_DEFAULT_STYLE|wx.CP_NO_TLW_RESIZE)
  677. panel.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnPaneChanged)
  678. win = panel.GetPane()
  679. # TODO IT DOESN'T WORK
  680. # TO ADD ONLY WHEN TAB IS OPENED
  681. #if lang == self.langUsed.split('_')[0]:
  682. #panel.Collapse(False)
  683. #else:
  684. #panel.Collapse(True)
  685. pageSizer = wx.BoxSizer(wx.VERTICAL)
  686. for k,v in js.iteritems():
  687. if k != 'total' and k!= 'name':
  688. box = self._langBox(win, k,v)
  689. pageSizer.Add(item = box, proportion = 1,
  690. flag = wx.EXPAND | wx.ALL, border = 3)
  691. win.SetSizer(pageSizer)
  692. pageSizer.SetSizeHints(win)
  693. return panel
  694. def OnPaneChanged(self, evt):
  695. """Redo the layout"""
  696. # TODO better to test on Windows
  697. self.statswin.SetupScrolling(scrollToTop = False)
  698. def _pageStats(self):
  699. """Translation statistics info"""
  700. fname = "translation_status.json"
  701. statsfile = os.path.join(os.getenv("GISBASE"), fname)
  702. if os.path.exists(statsfile):
  703. statsFile = open(statsfile)
  704. import json
  705. jsStats = json.load(statsFile)
  706. else:
  707. jsStats = None
  708. self.statswin = ScrolledPanel(self)
  709. self.statswin.SetAutoLayout(True)
  710. if not jsStats:
  711. Debug.msg(5, _("File <%s> not found") % fname)
  712. statsSizer = wx.BoxSizer(wx.VERTICAL)
  713. statstext = wx.StaticText(self.statswin, id = wx.ID_ANY,
  714. label = _('%s file missing') % fname)
  715. statsSizer.Add(item = statstext, proportion = 1,
  716. flag = wx.EXPAND | wx.ALL, border = 3)
  717. else:
  718. languages = jsStats['langs'].keys()
  719. languages.sort()
  720. statsSizer = wx.BoxSizer(wx.VERTICAL)
  721. for lang in languages:
  722. v = jsStats['langs'][lang]
  723. panel = self._langPanel(lang, v)
  724. statsSizer.Add(panel)
  725. self.statswin.SetSizer(statsSizer)
  726. self.statswin.SetupScrolling(scroll_x = False, scroll_y = True)
  727. self.statswin.Layout()
  728. self.statswin.Fit()
  729. return self.statswin
  730. def OnCloseWindow(self, event):
  731. """!Close window"""
  732. self.Close()
  733. class HelpFrame(wx.Frame):
  734. """!GRASS Quickstart help window"""
  735. def __init__(self, parent, id, title, size, file):
  736. wx.Frame.__init__(self, parent = parent, id = id, title = title, size = size)
  737. sizer = wx.BoxSizer(wx.VERTICAL)
  738. # text
  739. content = HelpPanel(parent = self)
  740. content.LoadPage(file)
  741. sizer.Add(item = content, proportion = 1, flag = wx.EXPAND)
  742. self.SetAutoLayout(True)
  743. self.SetSizer(sizer)
  744. self.Layout()
  745. class HelpWindow(HtmlWindow):
  746. """!This panel holds the text from GRASS docs.
  747. GISBASE must be set in the environment to find the html docs dir.
  748. The SYNOPSIS section is skipped, since this Panel is supposed to
  749. be integrated into the cmdPanel and options are obvious there.
  750. """
  751. def __init__(self, parent, grass_command, text, skip_description,
  752. **kwargs):
  753. """!If grass_command is given, the corresponding HTML help
  754. file will be presented, with all links pointing to absolute
  755. paths of local files.
  756. If 'skip_description' is True, the HTML corresponding to
  757. SYNOPSIS will be skipped, thus only presenting the help file
  758. from the DESCRIPTION section onwards.
  759. If 'text' is given, it must be the HTML text to be presented
  760. in the Panel.
  761. """
  762. self.parent = parent
  763. wx.InitAllImageHandlers()
  764. HtmlWindow.__init__(self, parent = parent, **kwargs)
  765. gisbase = os.getenv("GISBASE")
  766. self.loaded = False
  767. self.history = list()
  768. self.historyIdx = 0
  769. self.fspath = os.path.join(gisbase, "docs", "html")
  770. self.SetStandardFonts (size = 10)
  771. self.SetBorders(10)
  772. if text is None:
  773. if skip_description:
  774. url = os.path.join(self.fspath, grass_command + ".html")
  775. self.fillContentsFromFile(url,
  776. skip_description = skip_description)
  777. self.history.append(url)
  778. self.loaded = True
  779. else:
  780. ### FIXME: calling LoadPage() is strangely time-consuming (only first call)
  781. # self.LoadPage(self.fspath + grass_command + ".html")
  782. self.loaded = False
  783. else:
  784. self.SetPage(text)
  785. self.loaded = True
  786. def OnLinkClicked(self, linkinfo):
  787. url = linkinfo.GetHref()
  788. if url[:4] != 'http':
  789. url = os.path.join(self.fspath, url)
  790. self.history.append(url)
  791. self.historyIdx += 1
  792. self.parent.OnHistory()
  793. super(HelpWindow, self).OnLinkClicked(linkinfo)
  794. def fillContentsFromFile(self, htmlFile, skip_description = True):
  795. """!Load content from file"""
  796. aLink = re.compile(r'(<a href="?)(.+\.html?["\s]*>)', re.IGNORECASE)
  797. imgLink = re.compile(r'(<img src="?)(.+\.[png|gif])', re.IGNORECASE)
  798. try:
  799. contents = []
  800. skip = False
  801. for l in file(htmlFile, "rb").readlines():
  802. if "DESCRIPTION" in l:
  803. skip = False
  804. if not skip:
  805. # do skip the options description if requested
  806. if "SYNOPSIS" in l:
  807. skip = skip_description
  808. else:
  809. # FIXME: find only first item
  810. findALink = aLink.search(l)
  811. if findALink is not None:
  812. contents.append(aLink.sub(findALink.group(1)+
  813. self.fspath+findALink.group(2),l))
  814. findImgLink = imgLink.search(l)
  815. if findImgLink is not None:
  816. contents.append(imgLink.sub(findImgLink.group(1)+
  817. self.fspath+findImgLink.group(2),l))
  818. if findALink is None and findImgLink is None:
  819. contents.append(l)
  820. self.SetPage("".join(contents))
  821. self.loaded = True
  822. except: # The Manual file was not found
  823. self.loaded = False
  824. class HelpPanel(wx.Panel):
  825. def __init__(self, parent, grass_command = "index", text = None,
  826. skip_description = False, **kwargs):
  827. self.grass_command = grass_command
  828. wx.Panel.__init__(self, parent = parent, id = wx.ID_ANY)
  829. self.content = HelpWindow(self, grass_command, text,
  830. skip_description)
  831. self.btnNext = wx.Button(parent = self, id = wx.ID_ANY,
  832. label = _("&Next"))
  833. self.btnNext.Enable(False)
  834. self.btnPrev = wx.Button(parent = self, id = wx.ID_ANY,
  835. label = _("&Previous"))
  836. self.btnPrev.Enable(False)
  837. self.btnNext.Bind(wx.EVT_BUTTON, self.OnNext)
  838. self.btnPrev.Bind(wx.EVT_BUTTON, self.OnPrev)
  839. self._layout()
  840. def _layout(self):
  841. """!Do layout"""
  842. sizer = wx.BoxSizer(wx.VERTICAL)
  843. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  844. btnSizer.Add(item = self.btnPrev, proportion = 0,
  845. flag = wx.ALL, border = 5)
  846. btnSizer.Add(item = wx.Size(1, 1), proportion = 1)
  847. btnSizer.Add(item = self.btnNext, proportion = 0,
  848. flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
  849. sizer.Add(item = self.content, proportion = 1,
  850. flag = wx.EXPAND)
  851. sizer.Add(item = btnSizer, proportion = 0,
  852. flag = wx.EXPAND)
  853. self.SetSizer(sizer)
  854. sizer.Fit(self)
  855. def LoadPage(self, path = None):
  856. """!Load page"""
  857. if not path:
  858. path = os.path.join(self.content.fspath, self.grass_command + ".html")
  859. self.content.history.append(path)
  860. self.content.LoadPage(path)
  861. def IsFile(self):
  862. """!Check if file exists"""
  863. return os.path.isfile(os.path.join(self.content.fspath, self.grass_command + ".html"))
  864. def IsLoaded(self):
  865. return self.content.loaded
  866. def OnHistory(self):
  867. """!Update buttons"""
  868. nH = len(self.content.history)
  869. iH = self.content.historyIdx
  870. if iH == nH - 1:
  871. self.btnNext.Enable(False)
  872. elif iH > -1:
  873. self.btnNext.Enable(True)
  874. if iH < 1:
  875. self.btnPrev.Enable(False)
  876. else:
  877. self.btnPrev.Enable(True)
  878. def OnNext(self, event):
  879. """Load next page"""
  880. self.content.historyIdx += 1
  881. idx = self.content.historyIdx
  882. path = self.content.history[idx]
  883. self.content.LoadPage(path)
  884. self.OnHistory()
  885. event.Skip()
  886. def OnPrev(self, event):
  887. """Load previous page"""
  888. self.content.historyIdx -= 1
  889. idx = self.content.historyIdx
  890. path = self.content.history[idx]
  891. self.content.LoadPage(path)
  892. self.OnHistory()
  893. event.Skip()