ghelp.py 42 KB

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